Traces & Tracing Context
Every agent run and every direct LLM call in Distri emits an OpenTelemetry
trace — a tree of spans (agent → steps → LLM calls → tools) with token usage,
cost, latency, inputs/outputs, and provenance. Traces show up in the Traces
view in Distri Cloud and via distri traces list.
There are two ways to invoke Distri, and both are traced:
| API | What it is | Use it for |
|---|---|---|
execute | Run a full agent (planning, steps, tools, sub-agents) over the A2A protocol | Agentic workflows |
llm_execute | A single LLM call (optionally with tools) routed through Distri | Lightweight, high-volume calls (grading, extraction, classification) |
Invoking execute (run an agent)
execute is the A2A agent endpoint: POST /v1/agents/{agentId} with the
JSON-RPC method message/send (buffered) or message/stream (SSE). Routing,
tags, and tracing context travel in the request metadata (the
ExecutorContextMetadata, snake_case keys).
HTTP
curl -X POST https://api.distri.dev/v1/agents/scoring_agent \
-H "Authorization: Bearer $DISTRI_API_KEY" \
-H "X-Workspace-Id: $WORKSPACE_ID" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0", "id": "1", "method": "message/send",
"params": {
"message": { "role": "user", "messageId": "m1",
"parts": [{ "kind": "text", "text": "grade: 2+2=4" }],
"contextId": "eval-run-42" },
"metadata": {
"tags": { "team": "growth", "eval_run": "42" },
"trace_context": { "trace_id": "<32-hex>", "parent_span_id": "<16-hex>" }
}
}
}'
JavaScript (@distri/core)
import { Agent, DistriClient } from '@distri/core'
const client = new DistriClient({ baseUrl, apiKey, workspaceId })
const agent = await Agent.create('scoring_agent', client)
await agent.invoke({
// messages / parts …
contextId: 'eval-run-42', // groups by thread (see below)
tags: { team: 'growth', eval_run: '42' }, // searchable tags
trace_context: { trace_id, parent_span_id }, // optional remote parent
})
Rust (distri client)
use distri::run::{run_agent, RunOptions};
run_agent(&client, RunOptions {
agent: Some("scoring_agent".into()),
task: "grade: 2+2=4".into(),
thread_id: Some("eval-run-42".into()), // groups by thread
tags: Some([("team","growth")].into()), // searchable tags
trace_context: None, // or Some(TraceContext { .. })
..Default::default()
}, on_event).await?;
CLI:
distri run --agent scoring_agent --task "grade: 2+2=4" --tag team=growth
Invoking llm_execute (single LLM call)
llm_execute is POST /v1/llm/execute. It creates/uses a thread + task and
runs one LLM turn. The thread_id is the grouping key (see below); title
sets the span/trace name.
HTTP
curl -X POST https://api.distri.dev/v1/llm/execute \
-H "Authorization: Bearer $DISTRI_API_KEY" -H "X-Workspace-Id: $WORKSPACE_ID" \
-H "Content-Type: application/json" \
-d '{
"messages": [{ "role": "user", "parts": [{ "kind": "text", "text": "grade: 2+2=4" }] }],
"agent_id": "scoring_agent",
"thread_id": "activity-123",
"title": "Grade Q4"
}'
JavaScript
const res = await client.llm(messages, tools, {
agent_id: 'scoring_agent',
thread_id: 'activity-123', // groups by thread
title: 'Grade Q4', // span/trace name
})
Rust
use distri::{LlmExecuteOptions, LLmContext};
let opts = LlmExecuteOptions::new(LLmContext {
thread_id: Some("activity-123".into()), // groups by thread
label: Some("Grade Q4".into()), // span/trace name
messages, ..Default::default()
})
.with_agent_id("scoring_agent".into());
let res = client.llm_execute(opts).await?;
Grouping many calls into one trace
By default each invocation is its own root trace. To group related calls (e.g. all the grading calls of one evaluation run) into a single trace, give them all the same thread / context id:
execute: set the samecontextId(JS) /thread_id(Rust) /message.contextId(HTTP).llm_execute: set the samethread_id.
Distri derives the trace id deterministically from the thread id, so every call sharing it collapses into one trace in the Traces view — one row, all the executions nested inside.
// Bulk grading: every answer of one run shares the activity id → one trace
const contextId = `activity-${activityId}`
await Promise.all(answers.map(a =>
client.llm(a.messages, [], { agent_id: 'scoring_agent', thread_id: contextId })))
Cross-process / remote parent
When a parent service already owns a trace and wants the agent's spans to attach
underneath it (instead of starting a new one), pass an explicit
trace_context on execute:
trace_context: { trace_id: '<32-hex>', parent_span_id: '<16-hex>' }
The agent's root span and all descendants inherit that trace_id, so the work
shows up inside the caller's trace.
Provenance, tags & naming
- Provenance — each trace records the agent id, name, and version. In the Traces view the agent badge links to the agent definition.
- Tags — arbitrary
key=valuetags (passed onexecute) are stored on the trace and filterable in the UI and viadistri traces list --tag key=value. - Filtering —
distri traces list --agent <id> --tag key=value, or the Agent/Tags filters in the Traces view. - Naming — the trace/span name defaults to a snippet of the first message
(falling back to
execute {agent}). Pass an explicit name viatitle(llm_execute) to label it. No schema/migration is involved — the name is just the root span's name.