How do I trace a multi-agent workflow built with the OpenAI Agents SDK?
Rius traces a multi-agent run built with the OpenAI Agents SDK as one trace, with your wrapped entrypoint as the root span and every model call and tool call nested under it. Install the openai extra, call rius.init(), and wrap the run and each tool body with rius.observe. Name your agents with underscores so the names stay readable. A handoff has no span of its own in the trace, so you read it as consecutive model calls that belong to different agents.
How it works
The OpenAI Agents SDK drives the OpenAI client underneath, so the openai integration captures every model call without framework-specific code. Each call arrives as a generation with model, tokens, cost and time to first token. The agent loop, tool executions and handoffs run inside the framework, so the structure of the run comes from a thin layer of observe wrappers you add.
The result is one trace per run:
- Root span. Your function around
Runner.run(), wrapped withobserveand kindSpanKind.AGENT, is the root. Its duration is the run’s latency, and a failure here marks the run as failed. See Concepts for how traces, spans and kinds fit together. - Model calls. Each call is an
LLMgeneration nested under the root. - Tools. Each function tool is a
TOOLspan, created by theobservewrapper on the tool body. It parents under the run through ambient context.
A run that crosses a handoff looks like this:
handle-ticket AGENT 9.8s
├─ Response LLM 2.3s triage decides to hand off
├─ Response LLM 2.8s billing agent decides to call the tool
├─ execute_tool account_status TOOL 0ms
└─ Response LLM 4.6s billing agent answersThe framework also ships its own tracing, which uploads to the OpenAI platform and is independent of OpenTelemetry. Disable it with set_tracing_disabled(True) if Rius should be your single telemetry path. Leaving it on reports to two backends but does not interfere with Rius.
Set it up
-
Install the Rius SDK with the
openaiextra, plus the framework:pip install "glassflow-rius[openai]" openai-agents -
Initialize Rius, turn off the framework’s own tracing, and wrap the entrypoint and tool bodies. Give each agent an underscore name such as
triage_agent:import rius from agents import Agent, Runner, function_tool, set_tracing_disabled from rius import SpanKind rius.init(api_key="ri_...", service_name="my-agent") set_tracing_disabled(True) @function_tool @rius.observe(kind=SpanKind.TOOL) def account_status(email: str) -> str: """Fetch a customer's account standing by email.""" return lookup(email) billing_agent = Agent( name="billing_agent", instructions="Answer billing questions; check accounts with the tool.", tools=[account_status], model="gpt-5-mini", ) triage_agent = Agent( name="triage_agent", instructions="Route billing questions to billing_agent.", handoffs=[billing_agent], model="gpt-5-mini", ) @rius.observe(name="handle-ticket", kind=SpanKind.AGENT) def handle_ticket(ticket: str) -> str: result = Runner.run_sync(triage_agent, ticket) return str(result.final_output) -
Keep the decorator order on tools:
@function_tooloutermost,observeon the function body. The framework reads the function signature for its tool schema. -
Use underscores in agent names, not hyphens. Hyphens get rewritten in the generated handoff tool name, and the framework warns on every run.
-
To see which agent owns each stretch of the run, add an
observewrapper per agent entry, or use the framework’sRunHooksto open spans at agent boundaries. Each wrapper gives that agent its own named span in the same trace. -
Scope each run with
rius.session(), reusing your conversation or ticket id, so every turn of one conversation groups into one session. -
Run one request that calls a tool and open the trace in the console. You should see the root
AGENTspan, theLLMgenerations and your namedTOOLspans.
Streamed runs carry token counts only when usage is enabled on the underlying OpenAI client. The OpenAI Agents SDK integration page has the full recipe and the include_usage note.
What you get
- One trace per run, so the trace list shows one row for each multi-agent job.
- Tokens, cost and time to first token on every model call, rolled up across the run. Cost is a list-price estimate in US dollars that Rius computes, so you never send it.
- Named tool spans, so you can see which tool ran and how long it took.
- Named agent spans wherever you add a wrapper, which turn the trace into a readable map of which agent did what.
- Handoffs you can follow. The agent that decides to hand off appears as a generation, and the next generations come from the receiving agent. In the example above, the triage agent’s call ends with the decision to hand off, and the billing agent’s calls follow.
- Sessions that group the turns of one conversation under the id you chose.
One caution when reading the trace: all Responses API calls are named Response, so the span name alone does not say which agent spoke. Read the order of the calls, or add per-agent spans, to tell agents apart.
When something else fits
- If you need an explicit span at every handoff boundary, add the per-agent
observewrappers orRunHooksdescribed above. - The OpenAI Agents SDK integration is Python only. In TypeScript, trace the calls with manual generations; the integrations overview lists what each framework and language gets.