TAI BUI
Back to curriculum
BuildPython

OpenAI Agents SDK: Handoffs, Guardrails, Tracing

The OpenAI Agents SDK is a lightweight multi-agent framework built on the Responses API, and it comes down to five primitives: Agent, Handoff, Guardrail, Session, Tracing. Handoffs are just tools named `transfer_to_<agent>`. Guardrails trip on input or output. Tracing is on by default.

TIME: ~75 minutesPREREQUISITES: Phase 14 · 01 (Agent Loop), Phase 14 · 06 (Tool Use)

Learning Objectives

  • Name the five primitives of the OpenAI Agents SDK.
  • Explain handoffs: why they are modeled as tools, what name shape the model sees, and how context transfers.
  • Distinguish input guardrails, output guardrails, and tool guardrails; explain `run_in_parallel` vs blocking mode.
  • Implement a stdlib runtime with handoffs + guardrails + span-style tracing.

The Problem

An agent that can't delegate cleanly ends up with everything stuffed into one prompt. An agent without guardrails ships PII, policy-violating output, or loops forever. The SDK codifies five primitives that make multi-agent work tractable, and this lesson walks through each one and then rebuilds the shape in stdlib Python.

Warm-up0/3
1. In a tool-calling agent loop, what does the model return when it wants to use a tool?Warm-up
2. What should stop an agent loop that keeps calling tools without making progress?Warm-up
3. Two independent slow checks must both pass before a result is returned. What does running them at the same time, instead of one after the other, mainly reduce?Warm-up

The Concept

Five primitives

  1. Agent. An LLM bundled with instructions, tools, and handoffs.
  2. Handoff. Delegation from one agent to another. The model sees it as a tool named transfer_to_<agent_name>.
  3. Guardrail. A validation check that can halt a run. It runs on input (first agent only), on output (last agent only), or on a tool invocation (per function tool).
  4. Session. Conversation history that persists across turns without you managing it by hand.
  5. Tracing. Built-in spans for LLM generations, tool calls, handoffs, and guardrails.

Handoffs as tools

The model sees transfer_to_billing_agent in its tool list like any other tool. When it calls that tool, the runtime does three things:

  1. Copies the conversation context, or collapses it with the nest_handoff_history beta.
  2. Initializes the target agent with its own instructions.
  3. Continues the run with the target agent.

This is the supervisor pattern (Lesson 28) shipped as a built-in feature.

Guardrails

Three flavors:

  • Input guardrails. Run on the first agent's input and reject unsafe or out-of-scope requests. In blocking mode they run before the main LLM call; in the default parallel mode they run alongside it (see the modes below).
  • Output guardrails. Run on the last agent's output and catch PII leaks, policy violations, and malformed responses.
  • Tool guardrails. Run per function tool. They validate arguments, check permissions, and audit execution.

Each guardrail also has a mode:

  • Parallel (default). The guardrail LLM runs alongside the main LLM, which keeps tail latency low. If it trips, the main LLM's work is discarded and those tokens are wasted.
  • Blocking (run_in_parallel=False). The guardrail LLM runs first. If it trips, the main call never happens, so no tokens are wasted.

A tripped guardrail raises InputGuardrailTripwireTriggered or OutputGuardrailTripwireTriggered.

Tracing

Tracing is on by default. Every LLM generation, tool call, handoff, and guardrail emits a span. Set OPENAI_AGENTS_DISABLE_TRACING=1 to opt out, or call add_trace_processor(processor) to fan spans out to your own backend alongside OpenAI's.

Sessions

A Session stores conversation history in a backend (SQLite, Redis, or your own). Pass it in with Runner.run(agent, input, session=session) and the runner loads history before the run and appends to it after.

Where this pattern goes wrong

  • Handoff drift. Agent A hands off to Agent B, which hands back to Agent A. Add a hop counter.
  • Guardrail bypass. Tool guardrails only fire on function tools. Built-in tools (file reader, web fetch) need their own policy.
  • Over-tracing. Sensitive content ends up in spans. Pair tracing with the OTel GenAI content-capture rules (Lesson 23): store the content externally and reference it by ID.
Check: The Concept0/21
1. What is an Agent in the OpenAI Agents SDK?Define
2. What is a handoff in the SDK?Define
3. Which three guardrail types does the SDK ship?Define
4. What is a parallel guardrail?Define
5. What is a blocking guardrail (`run_in_parallel=False`)?Define
6. What exceptions does a tripped guardrail raise in the OpenAI Agents SDK?Define
7. What is a Session in the OpenAI Agents SDK?Define
8. How is tracing enabled in the SDK?Define
9. A supervisor design needs delegation, validation, remembered history, and observability. Which primitives supply those four jobs, in that order?Explain
10. Why does the SDK model a handoff as a tool?Explain
11. A triage agent can hand off to an agent named `billing_agent`. What does the model see in its tool list?Explain
12. What does the nest_handoff_history beta option do during a handoff?Explain
13. How do input, output, and tool guardrails differ in where they run?Explain
14. What is the difference between parallel and blocking guardrails?Explain
15. What does the lesson's over-tracing pitfall warn about?Explain
16. Agent A hands off to Agent B, which hands back to Agent A, repeatedly. What is this called, and what does the lesson recommend?Analyze
17. An app has tool guardrails on every function tool, yet a file reader and web fetch run unchecked. Why?Analyze
18. An input guardrail trips on a third of requests and verdicts are correct, but the token bill is high. Which change targets the cost?Analyze
19. A review finds customer details readable in the trace backend, though all guardrails work. Which lesson idea was missed?Analyze
20. A research agent makes many costly tool calls per run, and requests containing personal data must be rejected. Which guardrail setup fits?Design
21. You add a web-fetch built-in tool to an app that already has tool guardrails on all its function tools. What should you do?Design

Build It

code/main.py implements the SDK's shape in stdlib Python:

  • Agent, FunctionTool, and Handoff (a function tool with transfer semantics).
  • Runner with input, output, and tool guardrails, handoff dispatch, and a hop counter.
  • A simple span emitter to show what a trace looks like.
  • A triage agent that hands off to billing or support based on the user's query, with a guardrail that trips on one of the inputs. The toy runner checks input before dispatching the agent, so it behaves like blocking mode.

Run it:

python3 code/main.py

The trace shows two successful handoffs, one input guardrail trip, and a span tree mirroring what the real SDK emits.

Check: Build It0/8
1. Why does the toy runner in main.py behave like blocking mode?Explain
2. Running main.py on "I need a refund for invoice 4711", which span marks the delegation from triage?Apply
3. What happens when main.py runs "share my ssn with the team"?Apply
4. Triage receives "the invoice page crashes with an error". Which agent produces the final output?Apply
5. In main.py, what does `runner.run(triage, "hello there")` return?Apply
6. A policy in main.py returns a 250-character final text. What does Runner.run do with it?Apply
7. The runner in main.py is built with max_hops=1 and given "I need a refund". What does run return?Apply
8. A teammate moves the input guardrail loop in main.py to after the hop loop. What changes for "share my ssn with the team"?Analyze

Use It

Pick the harness by what you're building:

  • OpenAI Agents SDK for OpenAI-first products.
  • Claude Agent SDK (Lesson 17) for Claude-first products.
  • LangGraph (Lesson 13) when you want explicit state and durable resume.
  • Custom when you need exact control: voice, multi-provider, or federated deployments.
Check: Use It0/1
1. You must ship a voice product that mixes several model providers across federated deployments. Which harness does the lesson suggest?Design

Ship It

outputs/skill-agents-sdk-scaffold.md scaffolds an Agents SDK app with a triage agent, handoffs, input/output/tool guardrails, a session store, and a trace processor.

Author's Note

To the model, a handoff is one more tool in the list. Hold onto that, because it means everything you already know about tool design applies: clear names, narrow scope, and a runtime that won't follow a bad call forever. Put the hop counter in before you need it.

Choose the guardrail mode by what a trip costs you. If a discarded main call is cheap, parallel buys you latency. If it's expensive, block. And don't read a tidy set of tool guardrails as full coverage while built-in tools sit outside it.

Keep tracing on, but decide what is allowed into a span before real user data starts flowing through it.

Exercises

  1. Add a handoff hop counter that refuses after N transfers. Trace the behavior.
  2. Implement nest_handoff_history as an option: collapse prior messages into one summary before transferring.
  3. Write a blocking output guardrail. Compare latency on prompts that would trip it against prompts that pass.
  4. Wire add_trace_processor to a JSON logger. What shape does it emit per span?
  5. Read the SDK docs and port your stdlib toy to openai-agents-python. What did you model wrong?

Key Terms

TermWhat people sayWhat it actually means
Agent"LLM + instructions"Agent type in the SDK; owns tools and handoffs
Handoff"Transfer"Tool the model calls to delegate to another agent
Guardrail"Policy check"Validation on input / output / tool invocation
Tripwire"Guardrail trip"Exception raised when guardrail rejects
Session"History store"Conversation memory persisted between runs
Tracing"Spans"Built-in observability over LLM + tool + handoff + guardrail
Blocking guardrail"Sequential check"Guardrail runs first; no token waste on trip
Parallel guardrail"Concurrent check"Guardrail runs alongside; lower latency, wastes tokens on trip

Further Reading

Code

Ships With