Agent Handoffs is a pattern that lets one agent give control of a conversation to another, more specialized agent. A triage agent decides the customer's problem is a billing issue and calls a transfer_to_billing tool. The tool call carries a short payload: a summary of what has happened, the structured facts gathered so far, and the user's own words. A router validates the target, switches the active agent, and from then on the billing agent talks to the user directly until it finishes or hands off again.
At a Glance
- Problem: One agent that owns every topic needs every tool and every instruction in its prompt, so it picks the wrong tool more often and costs more per turn. Splitting it into specialists raises a new question: how the conversation moves between them.
- Forces: Carrying the full transcript versus a clean context the specialist can follow; letting the model decide when to hand off versus deterministic routing you can test; agents free to pass control onward versus loops in which no one ever answers.
- Solution: Expose each possible handoff as a tool whose arguments are the payload. When the model calls it, a router checks the target exists, counts the hop against a cap, builds the next agent's context from the payload, and makes that agent active.
- Consequences: Gains: short specialist prompts, small tool sets, per-agent models and tests, and a visible record of who handled what. Costs: an extra model call at each switch, facts lost when a summary is thin, and new failure modes such as ping-pong loops.
What problem does Agent Handoffs solve?
Customer support is the standard example. A user opens a chat about a login error, works through it with the assistant, and then asks why they were charged twice. A single agent that can handle both needs the instructions and tools for authentication, sync errors, invoices, refunds, and account changes in one prompt. Every turn pays for all of them, and the more tools the model sees, the more often it calls the wrong one.
The obvious fix is several specialists. That creates the problem this pattern addresses: the user is in one conversation, but the right agent changes partway through. Something has to decide when to switch, which agent to switch to, and what the new agent should know when it starts.
There are two naive answers, and both fail. Passing nothing makes the user repeat themselves, which is the chatbot version of being transferred to a call center queue. Passing the entire transcript gives the billing agent six thousand tokens of troubleshooting logs, tool results, and another agent's instructions. That transcript costs money on every later turn, and it can steer the new agent: a specialist reading a long history of password resets may keep offering password resets.
Handoffs differ from the supervisor style in Multi-Agent Collaboration. A supervisor stays in charge, delegates subtasks, and assembles the results. In a handoff, control moves. The receiving agent owns the conversation and answers the user itself, and the agent that handed off is no longer involved.
How does Agent Handoffs work?
The pattern has four parts.
Handoff tools. Each agent sees one tool per agent it may hand off to, named transfer_to_<agent>. Making the handoff a tool call puts the decision where the model already makes decisions, and it forces the model to fill in arguments. Those arguments are the payload. The tool list also defines the graph: if the billing agent has no transfer_to_technical tool, that edge does not exist. OpenAI's Agents SDK takes the same approach, presenting each handoff to the model as a tool.
The payload. This is the context the next agent receives, and choosing what goes in it is most of the design work. A good payload has three pieces:
- A summary in a sentence or two, written by the agent handing off, of what the user wants and what has already been tried.
- Structured state: the facts the next agent will act on, such as order ID, account ID, amount, and verification status. These belong in fields, not prose, because summaries drop exact numbers.
- The user's request verbatim, so the specialist can check the summary against what the user actually said.
What to leave out matters as much: the previous agent's system prompt, raw tool outputs, and its reasoning. If the specialist might need a detail, make it a state field.
The router. This is plain code that sits between the tool call and the next agent. It checks that the tool name maps to a known agent, that the agent isn't handing off to itself, and that the arguments parse as an object. It normalizes the payload, appends the new agent to a path, and makes it active. It also counts hops. Two agents that each believe the problem belongs to the other will pass the user back and forth forever unless something stops them, so after a fixed number of handoffs the router escalates to a person.
Persistent active agent. After a handoff, the specialist stays active for the next user turn. The caller stores which agent is active, along with that agent's own message history since the handoff, and routes the next message straight to it.
Handoffs between agents built on different stacks, possibly by different companies, need a shared wire format. Google announced the Agent2Agent (A2A) protocol in April 2025 as an open protocol for agents built by different vendors and frameworks to communicate and hand tasks to each other. Within one codebase a tool call and a JSON payload are enough; across organizations, a protocol such as A2A takes the place of the in-process router.
When should you use Agent Handoffs?
Use handoffs when a conversation can move between domains that need different tools or instructions:
- Customer support where triage, billing, technical support, and account changes each have their own tools and policies
- Flows where the right specialist only becomes clear partway through the conversation, after the model has asked a question or two
- Systems where the specialists are owned by different teams, so each agent's prompt, tools, and tests need to change independently
- Cases where some specialists warrant a larger model and others don't, so routing to the right agent also routes to the right price
When not to use it: if one classifier on the first message can pick the handler and the domain never changes mid-conversation, use a Semantic Router, which is cheaper and fully deterministic. If the task needs several specialists' outputs combined into one answer, use the supervisor form of Multi-Agent Collaboration, where control stays with one coordinator. And if a single agent with a modest tool set handles every domain well, Tool Calling alone is simpler than any handoff graph.
Implementation
# Using OpenAI SDK for illustration — swap client for any provider
import json
from openai import OpenAI
client = OpenAI(timeout=30, max_retries=2)
MAX_HANDOFFS = 3 # stops agents passing the user back and forth forever
PREFIX = "transfer_to_"
AGENTS = {
"triage": "Route the customer. Hand off to the specialist who owns the issue.",
"billing": "You handle invoices, refunds and payment methods.",
"technical": "You fix login, sync and app errors.",
}
def handoff_tool(target: str) -> dict:
return {"type": "function", "function": {
"name": PREFIX + target,
"description": f"Give the conversation to the {target} agent.",
"parameters": {"type": "object", "properties": {
"summary": {"type": "string"}, "state": {"type": "object"}},
"required": ["summary"]}}}
def run(user_message: str, agent: str = "triage") -> tuple[str, str]:
if agent not in AGENTS:
raise ValueError(f"Unknown agent: {agent}")
payload = {"request": user_message, "path": [agent]}
for _ in range(MAX_HANDOFFS + 1):
msg = client.chat.completions.create(
model="gpt-4o-mini", parallel_tool_calls=False,
tools=[handoff_tool(a) for a in AGENTS if a != agent],
messages=[{"role": "system", "content": AGENTS[agent]},
{"role": "user", "content": json.dumps(payload)}],
).choices[0].message
if not msg.tool_calls:
return agent, msg.content or ""
name = msg.tool_calls[0].function.name
target = name.removeprefix(PREFIX)
try:
args = json.loads(msg.tool_calls[0].function.arguments)
except (json.JSONDecodeError, TypeError):
args = None
if (not name.startswith(PREFIX) or target not in AGENTS
or target == agent or not isinstance(args, dict)):
return agent, "Sorry, I couldn't route that. A person will follow up."
state = args.get("state")
payload = {"request": user_message, # user's own words
"summary": str(args.get("summary", "")),
"state": state if isinstance(state, dict) else {},
"path": payload["path"] + [target]}
agent = target
return agent, "This needs a person. I've passed your case to our team."
# Usage
agent, reply = run("I was charged twice for order 1182.")
print(agent, reply) # next turn: run(next_message, agent)
The loop runs at most MAX_HANDOFFS + 1 agent turns, so a pair of agents bouncing the user between them ends in an escalation message instead of an endless bill. The new agent never sees the previous agent's prompt or messages, only the payload: the user's words, the summary, the structured state, and the path of agents so far, which also tells it who already handled the case. Every malformed output fails closed. An unknown tool name, a handoff to itself, or arguments that are not a JSON object all leave the current agent in place and send the user to a person, rather than guessing a target, and a missing summary is normalized to an empty string. parallel_tool_calls=False keeps the model from requesting two handoffs at once. The function returns the active agent so the caller can route the next turn straight to it. A production version would also keep each agent's message history since the handoff and add the specialists' real tools next to the handoff tools.
Worked Example: Cost and Latency
A support conversation has gone on with the technical agent long enough to build a 6,000-token transcript of messages and tool results. The user then asks about a double charge, and the conversation continues for three more turns about billing. All calls use the large model.
| Setup | Calls over the three billing turns | Tokens (in / out) | Cost per 1,000 conversations | Latency on the switch turn |
|---|---|---|---|---|
| No handoff: one agent with both tool sets | 3 | 28,650 / 750 | $97.20 | 4.7 s |
| Handoff carrying the full transcript | 4 | 31,800 / 950 | $109.65 | 8.5 s |
| Handoff carrying a payload | 4 | 14,400 / 950 | $57.45 | 8.5 s |
Assumptions: each specialist's system prompt and tools take 1,500 tokens, and the combined agent's take 3,000. Each user message is 150 tokens and each reply 250, so the context grows by 400 tokens per turn. The handoff call is the technical agent reading its full context (1,500 + 6,000 + 150 = 7,650 tokens) and writing a 200-token handoff; the payload the billing agent reads is the same 200 tokens. Later turns cost the same 4.7 s in every setup.
The handoff call costs 7,650 × $3.00 per million + 200 × $15.00 per million = $0.02595 and takes 0.5 + 200 / 60 ≈ 3.8 s. With a payload, the billing agent's three turns read 1,850, 2,250, and 2,650 tokens, which is 6,750 × $3.00 per million = $0.02025, and write 750 × $15.00 per million = $0.01125, for $0.05745 per conversation. Carrying the transcript makes those turns read 7,650, 8,050, and 8,450 tokens instead, for $0.10965 in total. The combined agent reads 9,150, 9,550, and 9,950 tokens for $0.0972. Each answer takes 0.5 + 250 / 60 ≈ 4.7 s, so the switch turn costs 3.8 + 4.7 = 8.5 s with a handoff.
The payload handoff is about 41% cheaper than the combined agent and 48% cheaper than carrying the transcript, at the price of 3.8 s more on the turn where control changes. Carrying the full transcript costs more than not splitting at all. The savings come from what the new agent doesn't read, not from having more agents.
What are the common pitfalls?
Ping-pong loops. Two agents with overlapping scopes each decide the request belongs to the other. Without a cap, the user waits while you pay for every bounce. Cap the hops per user turn, pass the path in the payload so each agent can see who already declined, and tighten the agent descriptions whenever the cap is hit, since every hit means the scopes overlap.
Summaries that drop the facts. A summary written by a model loses exact values: the amount becomes "a charge", and the order ID disappears. If the next agent will act on a value, make it a required field in the handoff tool's schema rather than hoping the summary includes it.
Trusting the target name. The model can call a tool that doesn't exist, hand off to itself, or produce arguments that don't parse. Validate the target against a fixed registry and fail closed. Don't build the target name from free text or look it up with a fuzzy match.
Handing off on every turn. If the active agent is reset to triage on each new message, the user is re-triaged constantly and the specialist loses its thread. Persist the active agent and send follow-ups straight to it, and give specialists a way back (a transfer_to_triage tool) for when the topic really changes.
Losing the audit trail. After three hops, nobody can tell why the user ended up with technical support. Log each handoff with its source, target, payload, and turn, so you can replay a conversation and see where routing went wrong.
What are the trade-offs?
Each handoff costs one extra model call, which the user feels as a few seconds of added latency on the turn where control changes. When handoffs are rare, that cost is small next to the savings from shorter specialist prompts. When conversations switch domains often, the calls add up, and a single agent with a combined tool set may be cheaper.
The payload is lossy by design. That is what keeps specialist contexts short and focused, and it is also how details go missing. Cognition's argument against multi-agent systems is relevant here: decisions an agent made implicitly are hard to put into a summary, and an agent working from a summary can act against them. Handoffs suit domains where a short state object really captures what matters, such as an order, an account, or a ticket. They work less well for long creative or engineering tasks, where the history is the context.
Letting the model choose the handoff is more flexible than a classifier, because it can switch mid-conversation and can ask a question before deciding. It is also harder to test. You test each agent's routing decisions with fixed conversations, plus the router's validation and cap, rather than one classifier on one input.
In return, each specialist is a small agent you can prompt, test, and assign a model to on its own, and the handoff log gives you a clear record of which agent handled each part of the conversation.
Goes Well With
Semantic Router can make the first assignment cheaply. Route the opening message with embeddings, and keep model-driven handoffs for the switches that happen later in the conversation.
Human-in-the-Loop is the natural exit when the hop cap is reached or a handoff is malformed. Treat "transfer to a person" as one more handoff target with its own payload, so the reviewer starts with the summary and state instead of a raw transcript.
Conversation Memory stores the active agent's history after a handoff. Compress that history on long conversations, and keep the handoff payload as the first message so the specialist never loses the reason it was called.
Tool Calling is the mechanism underneath. Handoff tools sit next to each specialist's real tools, so the same validation and logging you already apply to tool calls also cover handoffs.
References
- Anthropic. (2024). Building Effective Agents. Describes the routing workflow, which classifies an input and sends it to a specialized follow-up task.
- Anthropic. (2025). How we built our multi-agent research system. Explains why delegated agents need a clear objective, output format, and task boundaries rather than a vague instruction.
- Cognition. (2025). Don't Build Multi-Agents. Argues that splitting work across agents loses context and implicit decisions, and favors sharing full context and traces.
- OpenAI. OpenAI Agents SDK documentation: Handoffs. Handoffs presented to the model as tools, with options for controlling the history the receiving agent sees.
- Google. (2025). Announcing the Agent2Agent Protocol (A2A). An open protocol, announced in April 2025, for agents from different vendors and frameworks to communicate.
Further Reading
- Wu et al., "AutoGen: Enabling Next-Gen LLM Applications via Multi-Agent Conversation" (2023) — A framework in which agents converse to solve tasks, with configurable patterns for which agent speaks next. arXiv:2308.08155
- Cemri et al., "Why Do Multi-Agent LLM Systems Fail?" (2025) — Analyzes failed multi-agent traces and groups the failures into a taxonomy, including breakdowns in how agents share information with each other. arXiv:2503.13657