Skip to main content

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:

APIWhat it isUse it for
executeRun a full agent (planning, steps, tools, sub-agents) over the A2A protocolAgentic workflows
llm_executeA single LLM call (optionally with tools) routed through DistriLightweight, 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 same contextId (JS) / thread_id (Rust) / message.contextId (HTTP).
  • llm_execute: set the same thread_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=value tags (passed on execute) are stored on the trace and filterable in the UI and via distri traces list --tag key=value.
  • Filteringdistri 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 via title (llm_execute) to label it. No schema/migration is involved — the name is just the root span's name.