0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
27 small wins to finish your pathNext lesson →

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

python
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.

python
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.

python
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.

Example
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

ApproachGroupsYou control the name and id
Default (per run)One trace per runNo, the SDK names it
with trace(...)Every run in the block under one traceYes, 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_span to time it.
  • Tagging a trace with an id you keep, so you can find that exact run later.
Watch out. Without an API key the SDK skips the export, so the ids exist but nothing reaches the dashboard. Set OPENAI_API_KEY when you want the trace sent.
Try it yourself
  • Add a second custom_span("format step") inside the same trace.
  • Print trace_id in 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.