OpenTelemetry GenAI Semantic Conventions: Modeling Agent Runs as Span Trees

The article explains how OpenTelemetry GenAI semantic conventions address the gap in agent observability by defining a standardized span tree structure (invoke_agent, execute_tool, chat, etc.) and three critical fields (gen_ai.agent.name, gen_ai.conversation.id, gen_ai.operation.name) to capture tool calls, retries, and child-agent handoffs that auto-instrumentation misses.

Architecture Development Notes
Architecture Development Notes
Architecture Development Notes
OpenTelemetry GenAI Semantic Conventions: Modeling Agent Runs as Span Trees

When troubleshooting production agents, traces often show only a single model chat span — tool executions, retries, and child-agent handoffs are invisible. The root cause is instrumentation at the wrong layer: most frameworks auto-instrument only the model call, yet an agent run is a composite execution of model, tools, child agents, and downstream systems.

OpenTelemetry GenAI semantic conventions aim to fix this mismatch by decomposing an agent run into a fixed set of named spans: invoke_agent — one agent execution (CLIENT for managed services, INTERNAL for in-process frameworks like LangChain/CrewAI) invoke_workflow — a graph execution orchestrating multiple agents plan — planning and task decomposition execute_tool — tool execution chat — model text generation

The span category is recorded in the gen_ai.operation.name field using an enum. This naming forces modeling a run as a causal tree:

invoke_agent orchestrator
├── chat                     # plan this step
├── execute_tool list_orders
├── execute_tool refund      # fails, span marked error
├── invoke_agent billing     # child-agent handoff
│   ├── chat
│   └── execute_tool create_invoice
└── chat                     # final user reply

The tree root is invoke_agent; model calls, tool executions, retries, and child-agent handoffs nest as subtrees. Parent-child relationships align the timeline: parallel calls, failure-triggered retries, and which child agent's result fed the final reply become traceable. Logs are flat; only the span tree carries causality.

Beyond the tree, vocabulary convergence matters. Previously every framework and visualization tool invented its own field names, requiring per-SDK mapping. The conventions consolidate concepts used across tracing, evaluation, and cost attribution into a single enum: chat, embeddings, retrieval, execute_tool, invoke_agent, invoke_workflow, plan, etc. Though still in Development status and alongside the parallel OpenInference effort, the direction is clear: observability will adopt a shared vocabulary.

Three Fields That Define Tree Ownership and Boundaries

For teams adopting observability, three fields are the minimal starting point:

gen_ai.agent.name — identifies "whose tree this is." Child agents use their own name; they do not inherit the parent's. This field creates each agent's swim lane in the UI.

gen_ai.conversation.id — binds all agents and tools within one conversation. It is not the trace ID: a trace describes a single request's execution context, while an agent turn may spawn many requests across many traces. The conversation ID stitches the full session narrative. The spec warns: only populate when the system already has a session/thread ID; do not fabricate a UUID — otherwise it adds no value over trace ID.

gen_ai.operation.name — defines the span's role on the timeline (enum value).

The first two fields cannot be supplied by auto-instrumentation. Auto-instrumentation recognizes model calls but cannot know which call belongs to which conversation or agent. Conversation boundaries and agent identity must be explicitly set by the caller inside the agent loop. Multi-agent handoff attribution follows the same rule: the initiator starts an invoke_agent span pointing to the child agent; the child then produces its own chat and execute_tool spans under its own name. The handoff becomes an explicit event in the initiator's subtree. If each agent merely logs independently, the handoff point remains broken — neither side covers the middle.

Example of explicit instrumentation in the orchestrator loop:

# orchestrator loop, before calling child agent
span = tracer.start_span("invoke_agent billing")
span.set_attribute("gen_ai.operation.name", "invoke_agent")
span.set_attribute("gen_ai.agent.name", "orchestrator")  # tree belongs to initiator
span.set_attribute("gen_ai.conversation.id", session.id)
with trace.use_span(span):
    result = billing_agent.run(query)

Recording Decisions: Opt-In for Privacy and Cost

The conventions mark full prompt and model output as opt-in via gen_ai.input.messages and gen_ai.output.messages because they may contain user PII. Default is not to store; if enabled, sanitize at the application layer, clean in the Collector, or enable only in non-production. Tool definitions follow the same principle. Sampling should be decided at span creation using low-cardinality attributes (agent name, operation type, provider, model) rather than high-cardinality keys like conversation ID.

The semantic layer dictates what structure to record ; how much to record is a cost and privacy decision. Separating these concerns prevents accidentally enabling full message capture and facing billing and compliance regrets later.

Recommended Rollout Order

Start at your own agent-loop boundary — not by wrapping every model call. Let framework auto-instrumentation handle chat and embeddings; you inject conversation.id and agent.name into context at the loop entry.

On tool failure, record error type on the execute_tool span and mark its status as error so observers can filter to failed paths.

If quality events are needed, attach them to the corresponding span so cost, latency, and quality share one timeline.

The semantic layer is far from stable; there is no need to bet on one vendor today. What matters for a team is internally defining an agent run as these span types and three fields, so tracing, evaluation, and cost attribution speak the same vocabulary. Migration after standardization converges is cheap; the real expense is when every internal module has its own field style, forcing a mapping layer for every future visualization or evaluation tool.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

instrumentationObservabilityOpenTelemetryLLM agentsGenAIagent tracingsemantic conventionsspan tree
Architecture Development Notes
Written by

Architecture Development Notes

Focused on architecture design, technology trend analysis, and practical development experience sharing.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.