InfoWok
Intermediate

OpenAI Agents SDK Handoffs: Multi-Agent Routing in Python

A focused 2026 guide to OpenAI Agents SDK handoffs: what a handoff really is, the basic triage-to-specialist pattern, customizing with the handoff() function, passing model-generated data via input_type, filtering the history a specialist sees, and when to use a handoff instead of an agent-as-tool.

SK
Sukhveer Kaur
Published July 4, 2026
5 min read
Title card 'OpenAI Agents SDK Handoffs' — a 2026 Python guide to multi-agent routing, showing a triage agent transferring a conversation to a specialist via OpenAI Agents SDK handoffsAI Engineering
MULTI-AGENT · DEEP DIVE
On this page +

One agent rarely does everything well. A billing question, a bug report, and a refund each want a different specialist — and cramming all of that into one prompt makes an agent that is mediocre at all three. OpenAI Agents SDK handoffs solve this by letting a router transfer the conversation to the right specialist mid-turn.

This guide picks up where the OpenAI Agents SDK tutorial left off and goes deep on handoffs. You build a triage router, customize a handoff with a callback, pass structured data at the moment of transfer, control what history the specialist sees, and learn when a handoff is the wrong tool. Every snippet runs on the current SDK.

🟡 Intermediate⏱️ 15 minStack: Python 3.10+, openai-agents
Before you start
🎯 Key takeaways
  • A handoff transfers control: the router hands the turn to a specialist, and the specialist produces the final answer.
  • The basic form is one line — list specialist agents in handoffs=[...] and the model routes to them.
  • handoff() unlocks the good parts: a callback (on_handoff), a renamed tool, model-supplied data via input_type, and history control via input_filter.
  • Handoff ≠ agent-as-tool. A handoff gives up control; as_tool keeps it. Pick by who should own the reply.

What OpenAI Agents SDK handoffs actually do#

A handoff lets one agent delegate to another. Under the hood, the SDK exposes each handoff to the model as a tool named transfer_to_<agent_name>. So a handoff to a “Refund Agent” appears to the model as a transfer_to_refund_agent tool it can call.

That detail matters. The model chooses a handoff the same way it chooses any tool — from the description — so routing quality lives or dies on how you describe each specialist. Get the descriptions right and the router just works. For the wider picture of multi-agent design, see agent orchestration patterns.

The basic handoff#

Start simple. Give a triage agent a list of specialists in handoffs, and set each specialist’s handoff_description so the router knows when to pick it.

python
import asyncio
from agents import Agent, Runner
 
billing_agent = Agent(
    name="Billing Agent",
    handoff_description="Handles billing, invoices, and charges.",
    instructions="Resolve billing questions clearly.",
)
 
refund_agent = Agent(
    name="Refund Agent",
    handoff_description="Processes refund requests and timelines.",
    instructions="Handle refunds and explain the timeline.",
)
 
triage_agent = Agent(
    name="Triage",
    instructions="Route each customer message to the right specialist.",
    handoffs=[billing_agent, refund_agent],
)
 
async def main():
    result = await Runner.run(triage_agent, "I want a refund for a double charge.")
    print(result.final_output)
    print("Handled by:", result.last_agent.name)
 
if __name__ == "__main__":
    asyncio.run(main())

The router reads the message, calls transfer_to_refund_agent, and the refund agent writes the reply. result.last_agent.name tells you which specialist actually answered — log it.

Customizing with handoff()#

Passing a bare Agent works, but the handoff() function gives you control. You can rename the tool, add a callback that fires the instant a transfer happens, and override the description.

python
from agents import Agent, handoff, RunContextWrapper
 
refund_agent = Agent(name="Refund Agent", instructions="Handle refunds.")
 
def on_refund_handoff(ctx: RunContextWrapper[None]):
    print("Routing to refunds — logging the transfer.")
 
triage_agent = Agent(
    name="Triage",
    instructions="Route billing and refund questions.",
    handoffs=[
        handoff(
            refund_agent,
            on_handoff=on_refund_handoff,
            tool_name_override="escalate_to_refunds",
        )
    ],
)

The on_handoff callback is the hook for side effects — log the transfer, warm a cache, or notify a queue. It fires the moment the model decides to hand off, before the specialist runs, which is the right place for setup work.

Passing data at handoff time with input_type#

Sometimes you want the model to attach a little structured data to the transfer — a reason, a priority, a language. Set input_type to a Pydantic model and the SDK validates that payload, then passes it to your callback.

python
from pydantic import BaseModel
from agents import Agent, handoff, RunContextWrapper
 
class EscalationData(BaseModel):
    reason: str
    priority: str
 
async def on_escalate(ctx: RunContextWrapper[None], data: EscalationData):
    print(f"Escalated: {data.reason} (priority={data.priority})")
 
escalation_agent = Agent(name="Escalation Agent", instructions="Handle escalations.")
 
escalation_handoff = handoff(
    escalation_agent,
    on_handoff=on_escalate,
    input_type=EscalationData,
)

Now the model must supply a reason and priority when it escalates, and you can log or persist them before the specialist takes over. Use input_type for small model-decided metadata — not for app state you already have, which belongs in the run context. New to Pydantic models? The BaseModel primer is a quick read.

Controlling what the specialist sees#

By default, the receiving agent inherits the entire conversation history. That is usually what you want — but not always. An input_filter rewrites the history before the specialist sees it. The SDK ships common filters, including one that strips all prior tool calls.

python
from agents import Agent, handoff
from agents.extensions import handoff_filters
 
faq_agent = Agent(name="FAQ Agent", instructions="Answer common questions.")
 
faq_handoff = handoff(
    faq_agent,
    input_filter=handoff_filters.remove_all_tools,  # drop earlier tool calls
)

A cleaner history means fewer tokens and less chance the specialist gets distracted by irrelevant tool chatter. When a specialist only needs the question, not the machinery that got you there, filter the history.

Help the model route reliably#

Handoffs work better when the model is told they exist. The SDK provides a recommended instructions prefix that primes the model to use handoffs correctly. Add it to your agents’ instructions.

python
from agents import Agent
from agents.extensions.handoff_prompt import RECOMMENDED_PROMPT_PREFIX
 
billing_agent = Agent(
    name="Billing Agent",
    instructions=f"{RECOMMENDED_PROMPT_PREFIX}\nYou resolve billing questions.",
)

It is a one-line change that noticeably improves routing. If your triage agent hesitates or answers instead of transferring, this prefix is the first fix to try.

Handoffs vs agents-as-tools#

This is the choice people get wrong most often. Both let agents collaborate, but they hand control very differently.

HandoffAgent as tool (as_tool)
Who owns the replyThe specialist takes overThe orchestrator stays in control
The other agentProduces the final answerReturns a result to the caller
Tool nametransfer_to_<agent>Your chosen tool name
Use whenDistinct domains own the responseYou need a sub-answer, then continue

Reach for a handoff when a specialist should own the outcome; reach for as_tool when you need its answer but the orchestrator should keep driving. For a broader menu of frameworks and patterns, see the best AI agent frameworks in 2026 and OpenAI Agents SDK vs LangGraph.

Common mistakes#

  • Vague handoff_descriptions. The model routes on them. “Handles stuff” earns misroutes; name the domain precisely.
  • Skipping RECOMMENDED_PROMPT_PREFIX. Without it, agents sometimes answer instead of transferring.
  • Assuming the specialist starts fresh. It sees the full history unless you set an input_filter.
  • Overloading input_type. It is for model-generated metadata, not for dependencies you already hold in context.
  • Expecting one handoff to dispatch to many agents. Register one handoff per destination and let the model choose.
  • Forgetting guardrail scope. Input guardrails apply only to the first agent; output guardrails only to the agent that produces the final answer.

Summary#

Handoffs turn a single overloaded agent into a team. The pattern scales from one line — a list of specialists — to a fully customized transfer with callbacks, validated data, and filtered history. Keep the descriptions sharp, add the recommended prefix, and choose handoff versus as_tool by who should own the reply. That is most of what you need to route a real multi-agent app with OpenAI Agents SDK handoffs. Before production, wrap it in evals so a routing regression shows up in a test, not a ticket.

🧭 Where to go from here

Built a triage router with this? Tell me how many specialists you’re routing between, and I’ll flag where handoffs tend to misfire at that size.

Frequently asked questions

What's the difference between a handoff and calling an agent as a tool? +
A handoff transfers control — the specialist takes over the turn and produces the final answer. Agent-as-tool (`as_tool`) keeps the orchestrator in control: it calls the other agent, gets a result back, and continues. Handoff when a specialist should own the reply; as_tool when you need a sub-answer and want to keep going.
Does the specialist agent see the earlier conversation? +
Yes. By default the receiving agent sees the full conversation history. If you want to trim it — for example, strip prior tool calls — pass an input_filter such as handoff_filters.remove_all_tools when you build the handoff.
How does the model know which specialist to pick? +
It reads each handoff's tool description, which includes the target agent's handoff_description. Write those clearly, and add RECOMMENDED_PROMPT_PREFIX to your instructions so the model reliably understands that handoffs exist.
Can one handoff route to several agents? +
No. Each handoff transfers to one specific agent. If there are multiple possible destinations, register one handoff per destination and let the model choose between them.

References

  1. OpenAI Agents SDK — Handoffs (official docs)
  2. OpenAI Agents SDK — Quickstart (official docs)
  3. openai/openai-agents-python (GitHub)
Written by
Sukhveer Kaur
Sukhveer KaurSoftware Developer & AI Engineer

Sukhveer is a software developer specialising in AI systems and backend engineering. She has hands-on experience designing agentic AI applications, working with large language model pipelines, autonomous agent frameworks, and cloud-native services in Java and Python. At InfoWok, she bridges the gap between cutting-edge AI research and practical implementation — helping developers understand and apply emerging technologies through clear, experience-backed writing.

New AI engineering guides, the day they ship

Real Python, production depth. No digest spam.

Comments