A refund specialist by handoff
A handoff is a tool call that passes the whole conversation to another agent, so a refund is handed to a specialist agent and the run continues there.
Last updated: 28 Sep, 2026 · openai-agents 0.22.3
The desk from The desk agent and its tools answers order questions. A refund is a different job, so it belongs to a different agent. Handoffs, from the lesson on Handoffs: passing control to another agent, move the run there.
The pieces, and where each came from
| Piece | From lesson |
|---|---|
| The desk with its lookup tool | The desk agent and its tools |
| A second Agent, the refund specialist | Agent: name, instructions and tools |
| handoffs=[refund] on the desk | Handoffs: passing control to another agent |
| result.last_agent to read who answered | Runner and the RunResult object |
The refund specialist
The specialist is a second Agent. It only handles refunds, so its instructions say so.
refund = Agent(
name="Refund specialist",
instructions="Handle refund requests.",
model=ShopModel(),
)The desk that can hand off
The desk lists the specialist in handoffs. That turns the specialist into a transfer target the model can pick.
desk = Agent(
name="Shop desk",
instructions="Help shoppers; send refunds to the specialist.",
tools=[lookup_order],
handoffs=[refund], # the desk may transfer to this agent
model=ShopModel(),
)Reading who answered
After the run, result.last_agent is whichever agent produced the final answer.
result = Runner.run_sync(desk, "I want a refund")
print("Answered by:", result.last_agent.name)
print("Reply:", result.final_output)The desk handing a refund to the specialist
The desk starts the run, the word refund triggers the transfer, and the specialist finishes it.
from agents import Agent, Runner, function_tool, set_tracing_disabled
from shop_model import ShopModel
set_tracing_disabled(True)
@function_tool
def lookup_order(order_id: str) -> str:
"Look up an order by its id."
return f"Order {order_id}: shipped on 3 March, arriving 7 March."
refund = Agent(
name="Refund specialist",
instructions="Handle refund requests.",
model=ShopModel(),
)
desk = Agent(
name="Shop desk",
instructions="Help shoppers; send refunds to the specialist.",
tools=[lookup_order],
handoffs=[refund],
model=ShopModel(),
)
result = Runner.run_sync(desk, "I want a refund")
print("Answered by:", result.last_agent.name)
print("Reply:", result.final_output)
What the handoff produced
- The stand-in saw the word refund and returned a call to the specialist's transfer tool rather than a reply.
- result.last_agent.name is Refund specialist, so control moved off the desk for the rest of the run.
- The reply was composed by the specialist after the transfer, from its own refund instructions, so it reads as a refund answer and not as the desk's; Swapping in a real model puts a real model behind the same specialist.
Handoff vs one agent for everything
| Approach | What happens |
|---|---|
| One desk handles refunds too | Refund rules crowd the desk's instructions and every tool sits on one agent |
| Handoff to a specialist | Each agent stays focused; result.last_agent tells you who took over |
When to hand off
- A task with its own rules or tools, a refund, a cancellation, a fraud check, is cleaner as a separate agent.
- You want the transcript to show which agent handled a request.
Related
- Previous: The desk agent and its tools
- Next: Guarding the desk's input and output
- Reference: Handoffs
- Send an order question instead and confirm
last_agent.namestays Shop desk. - Rename the specialist and watch the printed agent name change.
- Add a second specialist to
handoffsand see the stand-in still picks the first on the word refund.
Little by little, you're building something great.