How Agent Handoffs Work in openai-agents-python: Input Filters and Configuration
Agent handoffs delegate conversations between agents via tool calls that preprocess context through optional input filters and history summarization before the target agent receives the data.
Agent handoffs allow one Agent to transfer control to another Agent mid-conversation in the openai-agents-python SDK. This mechanism treats the transfer as a tool invocation that the LLM can trigger, with configurable preprocessing to control exactly what context the receiving agent sees. Understanding how to configure input filters between agents ensures clean context windows and predictable multi-agent workflows.
The Agent Handoff Architecture
When the LLM invokes a handoff tool, the runtime executes a five-step pipeline defined in src/agents/handoffs/__init__.py. First, it collects the current turn data including the full transcript (input_history), items produced before the handoff (pre_handoff_items), and items generated by the handoff trigger (new_items). Second, it optionally executes an input filter that can prune or rewrite this data. Third, it optionally nests the previous conversation into a summary. Finally, it invokes on_invoke_handoff to retrieve the target agent and runs that agent with the filtered input.
Core Data Structures
The handoff system relies on three primary types exported from src/agents/handoffs/__init__.py:
HandoffInputData– Container for all data passed between agents, including history and items.HandoffInputFilter– Type alias forCallable[[HandoffInputData], Awaitable[HandoffInputData]]that transforms the data.Handoff– Dataclass storing configuration including the tool name, JSON schema, filter function, and nesting flags.
The Handoff Factory Function
The handoff() function is the primary entry point for creating handoff configurations. According to the source in src/agents/handoffs/__init__.py, its signature exposes the key customization points:
def handoff(
agent: Agent[TContext],
*,
tool_name_override: str | None = None,
tool_description_override: str | None = None,
on_handoff: OnHandoffWithInput[THandoffInput] | OnHandoffWithoutInput | None = None,
input_type: type[THandoffInput] | None = None,
input_filter: Callable[[HandoffInputData], HandoffInputData] | None = None,
nest_handoff_history: bool | None = None,
is_enabled: bool | Callable[[RunContextWrapper[Any], Agent[Any]], MaybeAwaitable[bool]] = True,
) -> Handoff[TContext, Agent[TContext]]
The input_filter parameter accepts a callable that receives a complete HandoffInputData snapshot and returns a modified copy. This filter executes after the handoff tool is called but before the next agent processes the input. The nest_handoff_history boolean controls whether the previous transcript collapses into a concise summary using the utilities in src/agents/handoffs/history.py.
Configuring Input Filters Between Agents
Input filters provide precise control over what context flows between agents. When configuring input filters between agents, you pass a function that inspects and mutates the HandoffInputData object. The filter can remove sensitive information, truncate long histories, or transform message formats before the target agent receives them.
The HandoffInputData structure contains:
input_history: The conversation transcript so farpre_handoff_items: Items generated before the handoff triggernew_items: Items generated by the handoff trigger itself
Your filter function must return a modified HandoffInputData instance, typically using the .clone() method to create a copy with specific fields updated.
Built-in Input Filters and Utilities
The package ships with reusable filters in src/agents/extensions/handoff_filters.py. The most commonly used is remove_all_tools, which strips every tool-related artifact from both history and new items:
def remove_all_tools(handoff_input_data: HandoffInputData) -> HandoffInputData:
"""Filters out all tool items: file search, web search and function calls+output."""
...
This utility delegates to internal helper functions _remove_tools_from_items and _remove_tool_types_from_input to drop items whose "type" matches tool-type strings. Use this filter when the receiving agent does not need to see function call outputs or retrieval results from previous agents.
Managing Conversation History with nest_handoff_history
When nest_handoff_history is enabled, the runtime summarizes the prior conversation before passing it to the target agent. This feature uses conversation-history markers (<CONVERSATION HISTORY> / </CONVERSATION HISTORY>) defined in src/agents/handoffs/history.py to create a condensed view.
The summarization pipeline executes these steps:
- Normalizes raw
input_historyintoTResponseInputIteminstances - Flattens any previously nested summaries via
_flatten_nested_history_messages - Converts
RunItemobjects from the current turn into plain input items using_run_item_to_plain_input - Builds a transcript combining
pre_items_as_inputsandnew_items_as_inputs - Applies the mapper function (default:
default_handoff_history_mapper) to create a single assistant message containing a numbered list of prior turns surrounded by markers - Replaces
input_historywith this summary while preserving original items innew_items
Important limitation: When using server-managed conversation IDs (conversation_id, previous_response_id, or auto_previous_response_id), nested handoff history is automatically disabled according to the Handoff dataclass docstring, though input filters remain active.
Practical Implementation Examples
Simple Handoff with Tool Removal
This example demonstrates a triage agent delegating to a billing specialist while removing all tool artifacts:
from agents import Agent, handoff
from agents.extensions.handoff_filters import remove_all_tools
triage_agent = Agent(name="triage", instructions="Route the user to the appropriate specialist.")
billing_agent = Agent(name="billing", instructions="Handle billing inquiries.")
billing_handoff = handoff(
billing_agent,
input_filter=remove_all_tools,
nest_handoff_history=True,
)
triage_agent.add_tool(billing_handoff)
The remove_all_tools filter is imported from src/agents/extensions/handoff_filters.py and ensures the billing agent receives only the conversational context, not any internal tool calls.
Custom Input Filter for Last Message Only
Create a filter that passes only the most recent user message to the target agent:
from agents import HandoffInputData
def keep_last_user_message(data: HandoffInputData) -> HandoffInputData:
if isinstance(data.input_history, tuple):
for item in reversed(data.input_history):
if isinstance(item, dict) and item.get("role") == "user":
return data.clone(input_history=(item,))
return data.clone(input_history=())
return data
specialist_handoff = handoff(
specialist_agent,
input_filter=keep_last_user_message,
nest_handoff_history=False,
)
Custom History Mapper
Override the default summarization format to use bullet points instead of numbered lists:
from agents.handoffs.history import nest_handoff_history
def bullet_point_mapper(transcript):
summary_content = "\n".join(f"- {item.get('content', str(item))}" for item in transcript)
return [{"role": "assistant", "content": f"<CONVERSATION HISTORY>\n{summary_content}\n</CONVERSATION HISTORY>"}]
def custom_filter(data: HandoffInputData) -> HandoffInputData:
return nest_handoff_history(data, history_mapper=bullet_point_mapper)
advanced_handoff = handoff(
target_agent,
input_filter=custom_filter,
)
The nest_handoff_history helper is defined in src/agents/handoffs/history.py and accepts a custom history_mapper to control summary formatting.
Summary
- Agent handoffs in openai-agents-python delegate control via tool calls that the LLM can invoke during a run.
- Configure input filters between agents using the
input_filterparameter in thehandoff()factory function to prune or transform context before the target agent receives it. - Built-in filters like
remove_all_toolsinsrc/agents/extensions/handoff_filters.pyprovide ready-made solutions for common filtering scenarios. - Enable
nest_handoff_historyto summarize prior conversation turns using markers defined insrc/agents/handoffs/history.py, though this feature disables automatically when using server-managed conversation IDs. - Custom input filters are functions accepting and returning
HandoffInputDataobjects, allowing complete programmatic control over inter-agent context flow.
Frequently Asked Questions
What is the difference between input_filter and nest_handoff_history in openai-agents-python?
The input_filter is a user-provided function that directly manipulates the HandoffInputData object to remove or transform specific items, while nest_handoff_history is a boolean flag that triggers automatic summarization of the conversation history using the logic in src/agents/handoffs/history.py. Input filters run regardless of the nesting setting, but nesting adds a structural summary layer before the filter executes.
Can input filters be asynchronous in openai-agents-python?
Yes, the HandoffInputFilter type alias defines the filter as Callable[[HandoffInputData], Awaitable[HandoffInputData]], meaning your filter function can be either synchronous or asynchronous. The runtime awaits the filter automatically, allowing you to perform asynchronous operations like database lookups or external API calls when determining what context to pass to the target agent.
Why should I remove tool items during agent handoffs?
Removing tool items via filters like remove_all_tools prevents the target agent from being distracted by internal function calls, file search results, or web search artifacts that were relevant only to the previous agent's task. This keeps the context window focused on the user-facing conversation and reduces token usage, particularly when the target agent does not have access to the same tools or when those tool results contain irrelevant implementation details.
How do server-managed conversation IDs affect handoff history?
When using server-managed conversation IDs (conversation_id, previous_response_id, or auto_previous_response_id), the nest_handoff_history feature is automatically disabled according to the Handoff dataclass docstring in src/agents/handoffs/__init__.py. Input filters remain active and will still process the data, but the runtime skips the automatic summarization step because the server manages the conversation state directly.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →