How a trace is structured
A single agent execution produces a tree of spans.wrapAgent creates the root — the run — and everything that runs inside it becomes a child span: provider SDK calls, tool executions, retrieval steps, nested sub-steps.
llm, tool, and retrieval child spans automatically when you use supported frameworks (OpenAI, Anthropic, LangChain, LlamaIndex, Vercel AI SDK, Bedrock, Cohere, Gemini, Vertex, Mistral). For everything else, you wrap calls with the typed helpers — trodo.tool(), trodo.llm(), trodo.retrieval(), trodo.trace() — or emit spans directly with withSpan.
Runs
A run is a single execution of your agent from start to finish. One call towrapAgent (Node) or wrap_agent (Python) produces one run. It captures:
- Inputs — the initial state and parameters passed to your agent.
- Outputs — the final result produced by your agent.
- Spans — the nested operations within the execution.
- Timing — duration and timing of each operation.
- Metadata — additional context like environment, version, custom tags.
- Rollups — total tokens, total cost, span count, tool count, error count — all summed from child spans, never written by you.
run_id and contains one or more spans organised hierarchically.
What you set vs. what’s rolled up
| You set on the run | Auto-rolled from spans |
|---|---|
agentName, distinctId, conversationId | duration_ms |
setInput / setOutput / setMetadata | total_tokens_in, total_tokens_out |
setErrorSummary | total_cost |
| — | span_count, tool_count, error_count |
| — | status (ok / error / running) |
Run ID
Therun_id is Trodo’s identifier for a specific agent execution. wrapAgent returns it alongside the result:
- Query and filter traces in the dashboard.
- Attach feedback after a run finishes.
- Link multi-turn conversations via a shared
conversationId. - Propagate the run across service boundaries (see Propagation).
Distinct ID
AdistinctId is the identifier of the end-user whose action triggered the run. Pass it to wrapAgent and every run for that user is filterable in the dashboard, attributable in the user timeline, and groupable for cohort analysis.
Conversation ID
AconversationId is an optional string you attach to multiple runs so Trodo can treat them as part of the same multi-turn flow. When you pass the same non-empty conversationId across several runs, the dashboard surfaces a Conversation · N indicator on the runs list, and the conversation view shows every turn stacked in order.
Conversation IDs are never inferred — you must pass them explicitly:
Spans
Spans are the building blocks of a run. Each span represents a single operation within your agent’s execution:- An LLM generation call.
- A tool or function invocation.
- A retrieval / vector search.
- A nested agent stage.
- Any custom operation you want to track.
- Which operations happened in what order.
- How long each operation took.
- Where errors occurred in the execution path.
- What inputs each step received and what outputs it produced.
Span kinds
A span’skind is a type tag that determines which fields the dashboard renders and which rollups the run inherits. Six kinds:
| Kind | Used for | Feeds run rollups |
|---|---|---|
llm | Model calls (OpenAI, Anthropic, Bedrock, …) | total_tokens_in, total_tokens_out, total_cost |
tool | Functions the agent invoked (search, DB, API) | tool_count |
retrieval | Vector search, KB lookup, RAG retriever | — |
agent | A nested sub-step rendered as an “agent stage” | — |
chain | A chain / graph step (LangChain, LangGraph) | — |
function | Generic instrumented function call | — |
LLM span fields
LLM spans carry the most structured data. For every supported provider, Trodo auto-populates these fields from the response — you don’t set them manually unless you’re calling the provider via raw HTTP.| Field | What it is | Auto-captured? |
|---|---|---|
model | The requested model ID (e.g. gpt-4o-mini) | Yes |
provider | The vendor (e.g. openai, anthropic) | Yes |
input_tokens | Prompt tokens consumed | Yes (from response usage) |
output_tokens | Completion tokens produced | Yes |
cost | USD cost, computed from tokens + model | Yes (server-side pricing table) |
temperature | Sampling temperature used | Yes (from request body) |
input | Prompt messages | Yes |
output | Completion content | Yes |
error_type / error_message | Exception details if the call threw | Yes (on throw) |
Tool span fields
| Field | What it is | Auto-captured? |
|---|---|---|
toolName | The tool/function name (e.g. search_kb) | Yes (LangChain, LlamaIndex, OpenAI Assistants tool calls) |
input | Arguments the tool was called with | Yes for framework-managed tools; manual otherwise |
output | Return value / tool result | Yes for framework-managed tools; manual otherwise |
Retrieval span fields
Retrieval spans have no required fields beyondname. Convention is to record the query as input and a summary of results (count, top IDs) as output so the dashboard waterfall shows what was searched and what came back.
Universal span fields
Every span, regardless of kind, carries:| Field | What it is |
|---|---|
name | Free-text label (e.g. chat.completions, search_kb) |
kind | One of the six kinds above |
status | ok on success, error on thrown exception |
duration_ms | Wall-clock time from start to end |
attributes | Free-form key/value metadata (e.g. customer_tier, region) |
parent_run_id
Links two runs together. Use when a sub-agent has its own lifecycle (own name, own rollups) but is triggered by another agent. Lives on the child run. The parent shows in the dashboard as “run X triggered sub-run Y”, and you can navigate between them. See Recipes → Sub-agents for the pattern.Propagation
How the SDK knows which run a span belongs to — without you passingrunId everywhere.
- In-process. Node uses
AsyncLocalStorage, Python usescontextvars. EverywithSpan,tool(),llm(),retrieval(),trace(), or auto-instrumented provider call insidewrapAgent(or inside any function it awaits) attaches automatically. Context survivesawait, imports, helper functions — the whole async chain inside the process. - Cross-service. Trodo’s SDK attaches an
X-Trodo-Run-Idheader to outbound HTTP when you usepropagationHeaders(). The receiving service’s middleware (expressMiddleware,fastapi_middleware) reads it and callsjoinRunso its spans land on the caller’s run. See Recipes → Cross-service. - Out-of-context. If you emit spans from a file outside an active
wrapAgent— worker threads, process pools, queue consumers — you must capture therunIdin the parent, pass it across the boundary, and calljoinRun(runId, fn)in the child. See Tracing → Spans outside wrapAgent.
Projects and sites
A site is the top-level container in Trodo. It groups:- All runs from your agent(s) for a given environment.
- The users tracked in that environment.
- The dashboards, clusters, and monitors built on top of those runs.
- A site ID — used when calling
trodo.init()and for SDK authentication. - A dashboard for viewing and analysing data.
my-app-prod, my-app-staging. Separating environments by site keeps prod dashboards clean and makes staging experimentation safe.
Tracer provider
The tracer provider is the OpenTelemetry component responsible for creating and managing spans, exporting them to Trodo’s OTLP endpoint, and handling batching and retries. You normally never interact with it directly —trodo.init() sets it up.
Two cases where it matters:
- Dual export. You already run an OTel provider (Honeycomb, Datadog, Jaeger). Trodo attaches its own span processor alongside yours — both backends receive every span. See Recipes → Dual export.
- Advanced custom instrumentation. Use
trodo.getTracer()to get the underlying OTel tracer if you want to emit spans via the raw OTel API. They flow through to Trodo like any other span.
Next steps
- Tracing → wrapAgent — every option on the run.
- Tracing → Spans — full field reference with examples.
- Tracing → Patterns — custom attributes, errors, feedback, tokens & cost.
- Integrations — per-framework setup.