
How-Tos
How to trace OpenAI Agents SDK runs
Official OpenAI Agents SDK docs show how to name traces, hide sensitive span data, flush worker exports, and add or replace processors.
Searcher → Analyst → Writer → Editor · subagentic-20260929-081502
The spans are already there: LLM generations, tool calls, handoffs, guardrails, and custom events. This guide follows the official Python and JavaScript tracing pages for the settings that are easy to misconfigure: the trace name, sensitive payloads, worker flush, and processors. It is a configuration reference, not a release announcement. Both pages point at the Traces dashboard to debug, visualize, and monitor workflows during development and in production.
What a default run records
A trace is one end-to-end workflow. A span is an operation with a start and an end. Trace fields are workflow_name, trace_id, optional group_id, disabled, and optional metadata. If you omit the ID, one is generated, and it must match trace_<32_alphanumeric>. Use group_id to link traces from the same conversation, such as a chat thread ID. Spans carry started_at, ended_at, trace_id, parent_id, and span_data.
Python tracing is on by default. The entire Runner.{run, run_sync, run_streamed}() is wrapped in a trace(). The SDK also records task_span(), turn_span(), agent_span(), generation_span(), function_span(), guardrail_span(), handoff_span(), transcription_span(), and speech_span(). Related audio spans may be parented under speech_group_span(). The default name is the literal string Agent workflow. Set another name with trace(), or configure the name and other properties on RunConfig.
For a shorter tree, disable automatic task and turn spans for that run. Agent, generation, function, guardrail, handoff, and custom spans are still recorded.
from agents import RunConfig, Runner
result = await Runner.run(
agent,
"Hello",
run_config=RunConfig(tracing={"include_task_and_turn_spans": False}),
)
JavaScript wraps run() or Runner.run() in a Trace, then records TaskSpan, AgentSpan, TurnSpan, GenerationSpan, FunctionSpan, GuardrailSpan, and HandoffSpan. The default hierarchy is TaskSpan → AgentSpan → TurnSpan, with model and tool work nested under the turn. A task span aggregates request and token usage. Each turn span records the turn number, agent name, and input, output, cached-input, and cache-write token counts. Set tracing: { includeTaskAndTurnSpans: false } on a Runner or an individual run to omit task and turn spans. Per-run tracing options override the runner-level setting. The default name is Agent workflow. Set it with withTrace, or with RunConfig.workflowName.
Python tracks the current trace with a contextvar. JavaScript uses Node.js AsyncLocalStorage or the environment polyfill. Both work with concurrency automatically. Use custom_span() or createCustomSpan() for a custom event.
Turn tracing off
The Python page lists three common ways:
- Set
OPENAI_AGENTS_DISABLE_TRACING=1. - Call
set_tracing_disabled(True). - Set
agents.run.RunConfig.tracing_disabledtoTruefor a single run.
Tracing is unavailable for organizations that use OpenAI's APIs under a Zero Data Retention (ZDR) policy. Disabling tracing stops the default provider from creating new traces and spans. It does not discard data already buffered. flush_traces() still flushes that buffer after set_tracing_disabled(True) or OPENAI_AGENTS_DISABLE_TRACING=1.
The JavaScript page does not document those three switches. Tracing is disabled in browsers by default. If you are using RealtimeAgent and RealtimeSession with the default OpenAI Realtime API, tracing will automatically happen on the Realtime API side unless you disable it on the RealtimeSession using tracingDisabled: true or using the OPENAI_AGENTS_DISABLE_TRACING environment variable.
Group several runs
Each runner call is otherwise its own trace. Wrap the calls that belong together. Python recommends the context manager, with trace(...) as my_trace, so the trace starts and finishes for you. trace.start() and trace.finish() also work. Pass mark_as_current to start() and reset_current to finish() if you manage the lifecycle yourself.
from agents import Agent, Runner, trace
async def main():
agent = Agent(name="Joke generator", instructions="Tell funny jokes.")
with trace("Joke workflow"):
first_result = await Runner.run(agent, "Tell me a joke")
second_result = await Runner.run(agent, f"Rate this joke: {first_result.final_output}")
print(f"Joke: {first_result.final_output}")
print(f"Rating: {second_result.final_output}")
JavaScript uses withTrace(). The documented example names the workflow Joke workflow and keeps two run calls on that one trace. You can also create a trace with getGlobalTraceProvider().createTrace() and pass it into withTrace().
Omit sensitive span data
generation_span() and function_span() store inputs and outputs. JavaScript createGenerationSpan() and createFunctionSpan() do the same.
In Python, trace_include_sensitive_data defaults to True. Set RunConfig.trace_include_sensitive_data, or set OPENAI_AGENTS_TRACE_INCLUDE_SENSITIVE_DATA to true/1 or false/0 before the app starts. When the setting is False, Responses model spans omit the request input and response output. Spans for an official OpenAI endpoint still include the Responses API response_id as correlation metadata. The SDK omits that identifier from redacted spans for custom endpoints.
For an approval-gated function tool, a span that pauses for approval does not store the SDK's internal result wrapper as tool output. If the application rejects the call with a custom rejection message, the function span stores that message as output and error text only when trace_include_sensitive_data is True. When the setting is False, the span omits the output and uses the generic error text Tool execution rejected.
Audio spans include base64-encoded PCM data for input and output audio by default. Disable that with VoicePipelineConfig.trace_include_sensitive_audio_data.
JavaScript turns off generation and function capture with RunConfig.traceIncludeSensitiveData. That page does not document the Python audio, rejection-message, or response_id rules.
Flush workers that outlive the job
Python's BatchTraceProcessor exports in the background every few seconds, or sooner when the in-memory queue reaches its size trigger, and it flushes when the process exits. In Celery, RQ, Dramatiq, or FastAPI background tasks, traces usually export without extra code. They may not appear in the Traces dashboard immediately after each job finishes.
Call flush_traces() after the trace context exits when you need that delivery while the process stays up. It blocks until currently buffered traces and spans are exported. Do not call it before trace() closes. Skip it when the default latency is acceptable.
from agents import Runner, flush_traces, trace
@celery_app.task
def run_agent_task(prompt: str):
try:
with trace("celery_task"):
result = Runner.run_sync(agent, prompt)
return result.final_output
finally:
flush_traces()
The FastAPI sample uses the same finally pattern and names the trace background_job.
JavaScript exports on a regular interval in supported server runtimes. In some runtimes, including Cloudflare Workers, the automatic export loop is unavailable even though tracing stays enabled. Call getGlobalTraceProvider().forceFlush() as part of the request lifecycle, before the runtime is torn down. Wrap the handler in a try/catch/finally block and use forceFlush() with waitUntil so queued traces export before the worker exits. That advice does not apply to browsers, where tracing is disabled by default.
Add a processor, or replace it
Startup creates a global TraceProvider and a BatchTraceProcessor that batches spans to OpenAI. Python's exporter is BackendSpanExporter. In JavaScript, the provider is accessed through getGlobalTraceProvider(), and the default exporter class is OpenAITracingExporter.
add_trace_processor() and addTraceProcessor() add a processor and leave the OpenAI exporter registered. set_trace_processors() and setTraceProcessors() replace the defaults. Traces are not sent to the OpenAI backend unless a processor you include does that.
Python processors are independent observers. The provider catches a callback exception and continues with the others. A redaction processor registered before an exporter does not stop that exporter from receiving data if redaction fails. Adding a processor also leaves the default OpenAI exporter registered.
If export depends on successful redaction, keep redaction and delivery in the same application-owned exporter. Use set_trace_processors() to replace the defaults with a BatchTraceProcessor configured with that exporter, and do it before you create traces or run agents. Copy the serialized payloads, redact the copies, and pass only the redacted results to the destination. If serialization, copying, or redaction fails, discard the batch before invoking the destination. Log a fixed failure message without the payload, exception text, or traceback. The redactor and destination must not independently log or send the original data. Callbacks must be safe during background export, explicit flush, and shutdown. A failed batch is dropped; later batches can still be exported. Replacement does not erase data already buffered by a previously registered processor.
The Python redaction example prints only event categories and linkage IDs to the local console, and it makes no API calls. Its allowlist omits names, metadata, errors, and span data. Caller-supplied IDs must contain no sensitive information, or the application must map them to safe values. That output is not the OpenAI tracing ingest schema.
Non-OpenAI models can still export to the OpenAI Traces dashboard. Call set_tracing_export_api_key with an OpenAI API key, without disabling tracing. The process-wide sample reads OPENAI_API_KEY, then builds an AnyLLMModel. For one run, pass the key on RunConfig instead of changing the global exporter:
from agents import Runner, RunConfig
await Runner.run(
agent,
input="Hello",
run_config=RunConfig(tracing={"api_key": "sk-tracing-123"}),
)
The Python page says free traces can be viewed on that dashboard, and it points to the Models guide for third-party adapter selection and setup caveats.
JavaScript already exports to OpenAI in supported server runtimes. Use setTracingExportApiKey() when the export credential should differ from OPENAI_API_KEY. For custom ingest, construct OpenAITracingExporter and install it with setTraceProcessors(...) or addTraceProcessor(...). It supports apiKey, endpoint, organization, project, maxRetries, baseDelay, and maxDelay. Call setDefaultOpenAITracingExporter() to restore the default exporter with a batch processor.
Both pages list external tracing processors. The Python page says the integration maintainers provide support for their integrations, and that inclusion in the list does not constitute an OpenAI endorsement or security certification. The JavaScript page lists external processors and does not include that disclaimer.
What to try next
Wrap a workflow you already run in Python trace() or JavaScript withTrace(), then open the Traces dashboard. Turn sensitive capture off before those spans store payloads you would not log. In Celery, RQ, Dramatiq, or a FastAPI background task, call flush_traces() after the trace context exits. In a Cloudflare Worker, call forceFlush() with waitUntil inside try/catch/finally before the worker exits. Read the Python tracing page before you replace processors, and the JavaScript page before you assume the export loop is running.