setup({ modules }) emits an LLM span on its own. But a real
agent does work the SDK never sees, like running tools, retrieving documents,
routing requests, validating output, or shelling out to a CLI. Manual spans are
how you put that work into the same trace tree, next to the auto-captured LLM
spans.
There are two shapes you author by hand, and everything on this page is one of
them:
- The agent span wraps a whole run. It is the parent row everything else
nests under, and it carries the agent identity and session. You author it with
agentSpan()/agent_span(). This is the same wrapper the integration guides put around a run. - Child spans sit inside that run, one per non-LLM step: a tool call, a
retrieval, a routing step. You author them with
manualSpan()/manual_span(), choosing the span kind that matches the step.
setup(), start with the
Traces Quickstart.
Span Kinds You Author Manually
| Kind | Use it for |
|---|---|
AGENT | The outer span around an agent run. Carries agent.id, agent.name, optional session.id and user.id. |
TOOL | One invocation of a tool function by an agent. Carries tool.name, optional tool_call.id. |
CHAIN | A chain step that is not itself an LLM call (routing logic, prompt assembly, post-processing). |
RETRIEVER | A vector-search or document-lookup call. |
EMBEDDING | An embedding-model call your code makes directly. |
LLM | A model call made outside an instrumented SDK (rare, prefer the SDK patches). |
TOOL span gets a tool name and an
arguments/result panel, a RETRIEVER span gets a query and a document list, an
LLM span gets messages, token counts, and cost. Pick the kind that matches how
you want the step to appear; when none of the specific kinds fit, use CHAIN,
the generic “this is a step” kind. The kind strings come from SpanKindValues,
and the Attributes reference lists which
attributes each kind expects.
Agent Spans
UseagentSpan() (TypeScript) or agent_span() (Python) as the outermost
wrapper around an agent run. Pass a stable agentId so the Catalyst Agents
dashboard groups executions correctly across renames and deploys. Pass
sessionId for one conversation, not as process-wide setup. Pass userId to
record the end user the run acts on behalf of so you can filter traces by user
on the dashboard. The wrapper also accepts free-form metadata and tags.
span argument.
Tool, Chain, And Retriever Spans
When the work inside an agent loop is a tool call, a retrieval step, or a chain step, wrap it in a child span with the rightSpanKind. The child span
automatically parents under the active AGENT span and inherits its agent.id.
manualSpan() / manual_span() is the general-purpose helper. Pass
spanKind / span_kind, optional typed fields (toolName / tool_name,
toolCallId / tool_call_id, model, usage, input, output), and any
extra attributes. It defaults to CHAIN; pass TOOL, RETRIEVER, or
EMBEDDING when authoring the matching shape.
Tool spans
Chain spans
UseCHAIN for routing logic, prompt assembly, post-processing, or any other
non-LLM step that benefits from its own span. metadata and tags are
free-form attributes that show up under the span’s attributes view in the
dashboard.
Retriever spans
UseRETRIEVER for vector-store or document-lookup calls. Record the query as
input and the result list as output.
Escape Hatch
manualSpan is the canonical path for every non-AGENT manual span. The only
reason to reach below it is tracing.tracer.startActiveSpan() directly, when
you need behavior the helper doesn’t expose, like mid-callback span events paired
with custom status transitions, or a span lifetime that doesn’t match a single
callback. With raw startActiveSpan you own status, exception recording, and
span.end() yourself.
For a full example that ties an AGENT span, several TOOL spans, and the
patched provider LLM spans together, see the
Production Agent Example.
Adding Custom Attributes
The typed handle covers the OpenInference vocabulary. For domain-specific attributes (tenant ID, request channel, deploy environment), usesetAttribute / set_attribute on the handle. The helper normalizes values
into OTel-safe types (primitives and primitive arrays pass through; mixed
arrays and plain objects are JSON-stringified).
span.raw (TypeScript) or span.span (Python).
Custom attributes appear under the span’s attributes view in the dashboard and
in inf span get <trace-id> <span-id> --view attributes. They are also
filterable from the CLI with --metadata "app.tenant_id=acme".
How Identity And Context Propagate
The agent span runs its callback inside an active OTel context that carries:- The OTel
Spanitself, so child spans created bytracer.startActiveSpanor by patched provider SDKs auto-parent under it. - A Catalyst-specific agent identity record (
agent.id,agent.name,agent.role), which the per-SDK patchers copy onto LLM child spans so the Agents dashboard groups model calls under the right agent.
agentId to every child span. If you create a tool
span inside an agent span, the agent identity flows through automatically.
agent.id), import the helpers:
TypeScript
CLI And Subprocess Wrappers
When the work is a shell-out to a CLI (Claude Code, Codex, a custom binary), wrap the process call in an agent span and set the input and output yourself.Status Semantics
All Catalyst manual-span helpers follow the same status rules:- Callback returns normally → span ends with
OK. - Callback throws → the exception is recorded as a span event, the span ends
with
ERRORand the error message, and the original exception re-throws so caller error handling is unaffected.
tracer.startActiveSpan() directly (the OTel escape hatch
mentioned above), you own status and span.end() yourself, which is the
price of the lower-level API.
Next Steps
Handle API reference
Every method on the AGENT / TOOL / CHAIN span handle, with value-coercion rules.
Attributes reference
All
Attr.* constants and SpanKindValues with the attributes each kind expects.Production agent example
A production-shaped agent with custom tool execution, end to end.
Agent identity
Pick stable
agent.id and session.id values for the Agents dashboard.