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 for Callable[[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 far
  • pre_handoff_items: Items generated before the handoff trigger
  • new_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:

  1. Normalizes raw input_history into TResponseInputItem instances
  2. Flattens any previously nested summaries via _flatten_nested_history_messages
  3. Converts RunItem objects from the current turn into plain input items using _run_item_to_plain_input
  4. Builds a transcript combining pre_items_as_inputs and new_items_as_inputs
  5. 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
  6. Replaces input_history with this summary while preserving original items in new_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_filter parameter in the handoff() factory function to prune or transform context before the target agent receives it.
  • Built-in filters like remove_all_tools in src/agents/extensions/handoff_filters.py provide ready-made solutions for common filtering scenarios.
  • Enable nest_handoff_history to summarize prior conversation turns using markers defined in src/agents/handoffs/history.py, though this feature disables automatically when using server-managed conversation IDs.
  • Custom input filters are functions accepting and returning HandoffInputData objects, 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →