Handoffs: passing control to another agent
A handoff is a transfer that lets one agent give the rest of the run to another: Agent(handoffs=[refunds]) lets the model move control to refunds, which then produces the answer.
Last updated: 28 Sep, 2026 · openai-agents 0.22.3
One agent should not carry every skill. A triage agent can route, and a refund specialist can handle refunds. A handoff moves the run from the first to the second, and the second finishes it.
The handoffs list
List other agents in handoffs. The SDK gives the model a transfer tool for each one. When the model calls it, that agent takes over the rest of the run.
triage = Agent(name="Triage", instructions="Route the customer.",
handoffs=[refunds]) # refunds is another Agent
# when the model emits the transfer, refunds finishes the runThe specialist agent
The agent that will take over is an ordinary Agent. It uses the canonical shop stand-in, imported from shop_model.py.
from shop_model import ShopModel
refunds = Agent(name="Refunds", instructions="Handle refund requests.",
model=ShopModel())The triage agent that can hand off
The triage agent lists refunds in its handoffs. The shop stand-in emits a transfer to the first handoff when it sees the word refund in the message.
triage = Agent(name="Triage", instructions="Route the customer to the right team.",
handoffs=[refunds], model=ShopModel())
# ShopModel transfers to handoffs[0] on a message containing "refund"Seeing who finished the run
result.last_agent is the agent that produced the final answer. result.new_items lists what happened, so you can see the transfer itself.
result = Runner.run_sync(triage, "I need a refund for order A17")
print(result.last_agent.name) # Refunds, not Triage
for item in result.new_items:
print(type(item).__name__) # the handoff call, its output, the messageRouting a refund to the specialist
The whole run. It starts on Triage and ends on Refunds, and the items show the transfer between them.
from agents import Agent, Runner, set_tracing_disabled
from shop_model import ShopModel
set_tracing_disabled(True)
refunds = Agent(name="Refunds", instructions="Handle refund requests.",
model=ShopModel())
triage = Agent(name="Triage", instructions="Route the customer to the right team.",
handoffs=[refunds], model=ShopModel())
result = Runner.run_sync(triage, "I need a refund for order A17")
print("Started with:", triage.name)
print("Ended with: ", result.last_agent.name)
for item in result.new_items:
print(type(item).__name__)What the items tell you
- last_agent is Refunds: the run ended on the specialist, so the transfer took effect.
- HandoffCallItem is the model asking to transfer, the same way it would call a tool.
- HandoffOutputItem is the SDK carrying out the transfer; after it, Refunds is running.
- MessageOutputItem is the final message, produced by Refunds, not Triage.
Handoff vs a plain tool call
| A tool call | A handoff | |
|---|---|---|
| What is added | a function | another Agent |
| After it runs | control returns to the same agent | the other agent takes over |
| last_agent | unchanged | becomes the specialist |
| The answer comes from | the calling agent | the agent handed to |
When to hand off
- Specialisation: a refund, a booking, or an escalation each handled by its own agent.
- Routing: a front agent that only decides which specialist should answer.
last_agent is no longer the one you started with. Read result.last_agent if you need to know who answered.Related
- Previous: Instructions from a function
- Next: An agent as a tool with as_tool
- Reference: Handoffs
- Send
"where is my order?"instead and checklast_agent.namestays Triage. - Add a second specialist and put it first in
handoffs, then read where "refund" now goes. - Print
len(result.new_items)and match it to the three item lines.
Every expert started right here.