Tracing a run with spans
A trace is a record of one workflow, and a span is a timed step inside it, so you can see what an agent did and how long each part took.
Last updated: 28 Sep, 2026 · openai-agents 0.22.3
The SDK traces every run for you. You can group several runs under one named trace with trace(...), and mark your own steps inside it with custom_span(...). Sending a trace to the OpenAI dashboard needs an API key; without one the SDK builds the ids but skips the export.
The trace and custom_span context managers
from agents import trace, custom_span
with trace("shop support"): # one named trace around the work
with custom_span("lookup step"): # a step you name yourself
result = Runner.run_sync(agent, "hello")Generating a trace id
gen_trace_id returns an id in the shape trace_ followed by 32 hex characters. You can pass it to trace(...) so you know the id ahead of the run.
from agents import gen_trace_id
trace_id = gen_trace_id()
print("id starts with:", trace_id.split("_")[0])
print("id is 'trace_' + 32 hex:", trace_id.startswith("trace_") and len(trace_id) == 38)Wrapping runs in a trace and a span
Pass the id into trace(...) and put the run inside a custom_span. The run works the same; the trace and span record it.
agent = Agent(name="Shop", instructions="Help with orders.", model=ShopModel())
with trace("shop support", trace_id=trace_id):
with custom_span("lookup step"):
result = Runner.run_sync(agent, "hello")
print("ran inside the trace:", result.final_output)Trace ids from one wrapped run
The whole program in one file. It prints the id it made and the answer from the wrapped run. The export step needs a key, so nothing is sent to the dashboard here.
from agents import Agent, Runner, trace, custom_span, gen_trace_id, set_tracing_disabled
from shop_model import ShopModel
# tracing left ON here so trace()/custom_span() build real ids; export still needs a key.
trace_id = gen_trace_id()
print("id starts with:", trace_id.split("_")[0])
print("id is 'trace_' + 32 hex:", trace_id.startswith("trace_") and len(trace_id) == 38)
agent = Agent(name="Shop", instructions="Help with orders.", model=ShopModel())
with trace("shop support", trace_id=trace_id):
with custom_span("lookup step"):
result = Runner.run_sync(agent, "hello")
print("ran inside the trace:", result.final_output)What the ids and the wrapped run show
- The id has the fixed
trace_prefix and a 38-character length (the trace_ prefix plus 32 hex characters), which is how the SDK tags one workflow. - The run answers exactly as it would without a trace, so wrapping it changes nothing about the result.
- The export is the part that needs
OPENAI_API_KEY; the trace and span exist in the process either way. - The span timing shows in the OpenAI dashboard after the trace is exported, which needs a key; this run proves the id shape, not the span durations.
Automatic tracing vs a named trace
| Approach | Groups | You control the name and id |
|---|---|---|
| Default (per run) | One trace per run | No, the SDK names it |
with trace(...) | Every run in the block under one trace | Yes, through the arguments |
When to add your own spans
- Grouping a multi-run workflow so it reads as one trace in the dashboard.
- Marking a slow step, such as a lookup, with a
custom_spanto time it. - Tagging a trace with an id you keep, so you can find that exact run later.
OPENAI_API_KEY when you want the trace sent.Related
- Previous: Streaming a run with run_streamed
- Next: Shaping the model with ModelSettings
- Reference: Tracing
- Add a second
custom_span("format step")inside the same trace. - Print
trace_idin full and read its 32 hex characters. - Wrap two runs in one
trace(...)block and note they share the id.
You understood something today that you didn't yesterday.