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

*Source: https://www.infowok.com/openai-agents-sdk-handoffs-python/ · Sukhveer Kaur · Published July 4, 2026*

---

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](/openai-agents-sdk-tutorial-python/) 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.

<Prerequisites>

- Done the basics? This builds on the [OpenAI Agents SDK tutorial](/openai-agents-sdk-tutorial-python/)
- Comfortable with `async`/`await` — see the [async primer](/async-await-python-primer/)
- An OpenAI API key set in your environment — see the [API key primer](/llm-api-key-setup-primer/)

</Prerequisites>

<KeyTakeaways>

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

</KeyTakeaways>

## What OpenAI Agents SDK handoffs actually do

A handoff lets one agent delegate to another. Under the hood, [the SDK](https://openai.github.io/openai-agents-python/handoffs/) 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](/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.**

![Anatomy of a handoff: a triage agent calls transfer_to_refund, an on_handoff callback fires with input_type data, and the refund agent takes over the turn](./openai-agents-sdk-handoffs-python-concept.svg)

## 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](/pydantic-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.

| | Handoff | Agent as tool (`as_tool`) |
|---|---|---|
| Who owns the reply | The specialist takes over | The orchestrator stays in control |
| The other agent | Produces the final answer | Returns a result to the caller |
| Tool name | `transfer_to_<agent>` | Your chosen tool name |
| Use when | Distinct domains own the response | You 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](/best-ai-agent-frameworks-2026/) and [OpenAI Agents SDK vs LangGraph](/openai-agents-sdk-vs-langgraph-2026/).

## Common mistakes

- **Vague `handoff_description`s.** 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](/ai-agent-evaluation-metrics-frameworks-2026/) so a routing regression shows up in a test, not a ticket.

<NextSteps>

- **Need the fundamentals?** Start with the [OpenAI Agents SDK tutorial](/openai-agents-sdk-tutorial-python/).
- **Choosing a framework?** Read [OpenAI Agents SDK vs LangGraph](/openai-agents-sdk-vs-langgraph-2026/).
- **New to agents?** See [What Are AI Agents?](/what-are-ai-agents-complete-guide-2026/).

</NextSteps>

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