# How Agent Handoffs Work in openai-agents-python: Input Filters and Configuration

> Learn how agent handoffs work in openai-agents-python. Discover how to configure input filters and preprocess context for seamless agent-to-agent communication.

- Repository: [OpenAI/openai-agents-python](https://github.com/openai/openai-agents-python)
- Tags: how-to-guide
- Published: 2026-04-17

---

**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`](https://github.com/openai/openai-agents-python/blob/main/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`](https://github.com/openai/openai-agents-python/blob/main/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`](https://github.com/openai/openai-agents-python/blob/main/src/agents/handoffs/__init__.py), its signature exposes the key customization points:

```python
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`](https://github.com/openai/openai-agents-python/blob/main/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`](https://github.com/openai/openai-agents-python/blob/main/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:

```python
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`](https://github.com/openai/openai-agents-python/blob/main/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:

```python
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`](https://github.com/openai/openai-agents-python/blob/main/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:

```python
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:

```python
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`](https://github.com/openai/openai-agents-python/blob/main/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`](https://github.com/openai/openai-agents-python/blob/main/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`](https://github.com/openai/openai-agents-python/blob/main/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`](https://github.com/openai/openai-agents-python/blob/main/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`](https://github.com/openai/openai-agents-python/blob/main/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.