Handoffs
Handoffs allow an agent to delegate tasks to another agent. This is particularly useful in scenarios where different agents specialize in distinct areas. For example, a customer support app might have agents that each specifically handle tasks like order status, refunds, FAQs, etc.
Handoffs are represented as tools to the LLM. So if there's a handoff to an agent named Refund Agent, the tool would be called transfer_to_refund_agent.
Creating a handoff
All agents have a handoffs param, which can either take an Agent directly, or a Handoff object that customizes the Handoff.
If you pass plain Agent instances, their handoff_description (when set) is appended to the default tool description. Use it to hint when the model should pick that handoff without writing a full handoff() object.
You can create a handoff using the handoff() function provided by the Agents SDK. This function allows you to specify the agent to hand off to, along with optional overrides and input filters.
Basic usage
Here's how you can create a simple handoff:
from agents import Agent, handoff
billing_agent = Agent(name="Billing agent")
refund_agent = Agent(name="Refund agent")
# (1)!
triage_agent = Agent(name="Triage agent", handoffs=[billing_agent, handoff(refund_agent)])
- You can use the agent directly (as in
billing_agent), or you can use thehandoff()function.
Customizing handoffs via the handoff() function
The handoff() function lets you customize things.
agent: This is the agent to which things will be handed off.tool_name_override: By default, theHandoff.default_tool_name()function is used, which resolves totransfer_to_<agent_name>. You can override this.tool_description_override: Override the default tool description fromHandoff.default_tool_description()on_handoff: A callback function executed when the handoff is invoked. This is useful for things like kicking off some data fetching as soon as you know a handoff is being invoked. This function receives the agent context, and can optionally also receive LLM generated input. The input data is controlled by theinput_typeparam.input_type: The schema for the handoff tool-call arguments. When set, the parsed payload is passed toon_handoff.input_filter: This lets you filter the input received by the next agent. See below for more.is_enabled: Whether the handoff is enabled. This can be a boolean or a function that returns a boolean, allowing you to dynamically enable or disable the handoff at runtime.nest_handoff_history: Optional per-call override for the RunConfig-levelnest_handoff_historysetting. IfNone, the value defined in the active run configuration is used instead.
The handoff() helper always transfers control to the specific agent you passed in. If you have multiple possible destinations, register one handoff per destination and let the model choose among them. Use a custom Handoff only when your own handoff code must decide which agent to return at invocation time.
from agents import Agent, handoff, RunContextWrapper
def on_handoff(ctx: RunContextWrapper[None]):
print("Handoff called")
agent = Agent(name="My agent")
handoff_obj = handoff(
agent=agent,
on_handoff=on_handoff,
tool_name_override="custom_handoff_tool",
tool_description_override="Custom description",
)
Handoff inputs
In certain situations, you want the LLM to provide some data when it calls a handoff. For example, imagine a handoff to an "Escalation agent". You might want a reason to be provided, so you can log it.
from pydantic import BaseModel
from agents import Agent, handoff, RunContextWrapper
class EscalationData(BaseModel):
reason: str
async def on_handoff(ctx: RunContextWrapper[None], input_data: EscalationData):
print(f"Escalation agent called with reason: {input_data.reason}")
agent = Agent(name="Escalation agent")
handoff_obj = handoff(
agent=agent,
on_handoff=on_handoff,
input_type=EscalationData,
)
input_type describes the arguments for the handoff tool call itself. The SDK exposes that schema to the model as the handoff tool's parameters, validates the returned JSON locally, and passes the parsed value to on_handoff.
It does not replace the next agent's main input, and it does not choose a different destination. The handoff() helper still transfers to the specific agent you wrapped, and the receiving agent still sees the conversation history unless you change it with an input_filter or nested handoff history settings.
input_type is also separate from RunContextWrapper.context. Use input_type for metadata the model decides at handoff time, not for application state or dependencies you already have locally.
When to use input_type
Use input_type when the handoff needs a small piece of model-generated metadata such as reason, language, priority, or summary. For example, a triage agent can hand off to a refund agent with { "reason": "duplicate_charge", "priority": "high" }, and on_handoff can log or persist that metadata before the refund agent takes over.
Choose a different mechanism when the goal is different:
- Put existing application state and dependencies in
RunContextWrapper.context. See the context guide. - Use
input_filter,RunConfig.nest_handoff_history, orRunConfig.handoff_history_mapperif you want to change what history the receiving agent sees. - Register one handoff per destination if there are multiple possible specialists.
input_typecan add metadata to the chosen handoff, but it does not dispatch between destinations. - If you want structured input for a nested specialist without transferring the conversation, prefer
Agent.as_tool(parameters=...). See tools.
Input filters
When a handoff occurs, it's as though the new agent takes over the conversation, and gets to see the entire previous conversation history. If you want to change this, you can set an input_filter. An input filter is a function that receives the existing input via a HandoffInputData, and must return a new HandoffInputData.
HandoffInputData includes:
input_history: the input history beforeRunner.run(...)started.pre_handoff_items: items generated before the agent turn where the handoff was invoked.new_items: items generated during the current turn, including the handoff call and handoff output items.input_items: optional items to forward to the next agent instead ofnew_items, allowing you to filter model input while keepingnew_itemsintact for session history.run_context: the activeRunContextWrapperat the time the handoff was invoked.
Nested handoffs are available as an opt-in beta and are disabled by default while we stabilize them. When you enable RunConfig.nest_handoff_history, the runner collapses the prior transcript into a single assistant summary message and wraps it in a <CONVERSATION HISTORY> block that keeps appending new turns when multiple handoffs happen during the same run. You can provide your own mapping function via RunConfig.handoff_history_mapper to replace the generated message without writing a full input_filter. The opt-in only applies when neither the handoff nor the run supplies an explicit input_filter, so existing code that already customizes the payload (including the examples in this repository) keeps its current behavior without changes. You can override the nesting behaviour for a single handoff by passing nest_handoff_history=True or False to handoff(...), which sets Handoff.nest_handoff_history. If you just need to change the wrapper text for the generated summary, call set_conversation_history_wrappers (and optionally reset_conversation_history_wrappers) before running your agents.
If both the handoff and the active RunConfig.handoff_input_filter define a filter, the per-handoff input_filter takes precedence for that specific handoff.
Note
Handoffs stay within a single run. Input guardrails still apply only to the first agent in the chain, and output guardrails only to the agent that produces the final output. Use tool guardrails when you need checks around each custom function-tool call inside the workflow.
There are some common patterns (for example removing all tool calls from the history), which are implemented for you in agents.extensions.handoff_filters
from agents import Agent, handoff
from agents.extensions import handoff_filters
agent = Agent(name="FAQ agent")
handoff_obj = handoff(
agent=agent,
input_filter=handoff_filters.remove_all_tools, # (1)!
)
- This will automatically remove all tools from the history when
FAQ agentis called.
Recommended prompts
To make sure that LLMs understand handoffs properly, we recommend including information about handoffs in your agents. We have a suggested prefix in agents.extensions.handoff_prompt.RECOMMENDED_PROMPT_PREFIX, or you can call agents.extensions.handoff_prompt.prompt_with_handoff_instructions to automatically add recommended data to your prompts.