# How to Manage Conversation History with Handoff History Mappers in openai-agents-python

> Manage conversation history in openai-agents-python using handoff history mappers. Collapse transcripts into one message or customize context for the next agent.

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

---

**Use `RunConfig.nest_handoff_history=True` to collapse conversation transcripts into a single assistant message via a history mapper, or provide a custom `handoff_history_mapper` callable to control exactly what context the next agent receives.**

When building multi-agent workflows with the `openai-agents-python` SDK, handoffs between agents can create unwieldy conversation histories that consume tokens and confuse downstream models. The SDK provides **handoff history mappers** to compress, filter, or restructure this context before passing it to the next agent. This article explains the implementation details found in the source code and demonstrates how to configure custom history mapping logic.

## What Are Handoff History Mappers?

A **history mapper** is a callable that transforms the conversation transcript before a handoff completes. When an agent hands off to another agent, the SDK normally forwards the raw input history (all previous model turns) to the new agent. By enabling **nested handoff history** via `RunConfig.nest_handoff_history=True`, the SDK instead passes the transcript through a mapper that returns a condensed list of input items—typically a single assistant message containing a summary of the prior conversation.

The default mapper strips tool-approval items, flattens any previously nested summaries, and wraps the remaining transcript in `<CONVERSATION HISTORY>` markers. You can replace this behavior with any callable matching the signature `Callable[[list[TResponseInputItem]], list[TResponseInputItem]]`.

## Core Components and Source Locations

| Component | File Path | Purpose |
|-----------|-----------|---------|
| **Configuration flags** | [`src/agents/run_config.py`](https://github.com/openai/openai-agents-python/blob/main/src/agents/run_config.py) (lines 65–78) | Defines `nest_handoff_history` and `handoff_history_mapper` attributes. |
| **Default mapper** | [`src/agents/handoffs/history.py`](https://github.com/openai/openai-agents-python/blob/main/src/agents/handoffs/history.py) (lines 15–22) | Implements `default_handoff_history_mapper`, which builds the summary message. |
| **Nesting orchestration** | [`src/agents/handoffs/history.py`](https://github.com/openai/openai-agents-python/blob/main/src/agents/handoffs/history.py) (lines 71–112) | Contains `nest_handoff_history`, which normalizes input, flattens nested history, and invokes the mapper. |
| **Turn resolution** | [`src/agents/run_internal/turn_resolution.py`](https://github.com/openai/openai-agents-python/blob/main/src/agents/run_internal/turn_resolution.py) (lines 39–45, 81–99) | Decides whether to apply the mapper based on flags and server-managed conversation settings. |
| **Item normalization** | [`src/agents/handoffs/history.py`](https://github.com/openai/openai-agents-python/blob/main/src/agents/handoffs/history.py) (lines 24–30) | `_normalize_input_history` converts string histories to `TResponseInputItem` objects. |
| **History flattening** | [`src/agents/handoffs/history.py`](https://github.com/openai/openai-agents-python/blob/main/src/agents/handoffs/history.py) (lines 91–105) | `_flatten_nested_history_messages` extracts prior `<CONVERSATION HISTORY>` blocks to prevent duplication. |

## How Nested Handoff History Works Internally

The `nest_handoff_history` function in [`src/agents/handoffs/history.py`](https://github.com/openai/openai-agents-python/blob/main/src/agents/handoffs/history.py) performs the following steps when processing a handoff:

1. **Normalize input history** – Converts raw history strings into structured `TResponseInputItem` objects via `_normalize_input_history`.

2. **Flatten nested summaries** – Scans for previously generated `<CONVERSATION HISTORY>` blocks using `_flatten_nested_history_messages`. It extracts the content from these markers (via `_extract_nested_history_transcript` and `_parse_summary_line`) and appends them to the flat list, preventing recursive summarization of summaries.

3. **Filter non-forwardable items** – Removes tool-approval items and converts `RunItem` instances to plain inputs using `_run_item_to_plain_input`. Items marked as summary-only (assistant messages, function calls, reasoning) are omitted from the forwarded transcript based on `_should_forward_*` helpers.

4. **Build the transcript** – Concatenates `flattened_history + pre_items_as_inputs + new_items_as_inputs`.

5. **Execute the mapper** – Calls the user-provided `handoff_history_mapper` if set; otherwise uses `default_handoff_history_mapper`, which constructs a single assistant message containing the transcript wrapped in `<CONVERSATION HISTORY>` / `</CONVERSATION HISTORY>` markers.

6. **Return `HandoffInputData`** – The result contains the collapsed `input_history` (the mapper output), filtered pre-handoff items, and untouched `new_items`. The runner uses `input_history` as the model input for the next agent while preserving original items for session persistence.

## Code Examples

### Enable Default History Nesting

Enable the built-in summarization to collapse conversation history into a single assistant message at every handoff.

```python
from agents import RunConfig, Runner, Agent

agent_a = Agent(name="Greeter")
agent_b = Agent(name="TaskProcessor")

run_cfg = RunConfig(
    nest_handoff_history=True,  # Opt-in to nested history

)

runner = Runner(agent_a, run_config=run_cfg)
result = runner.run("Hello, I need help with a complex task.")

```

When `agent_a` hands off to `agent_b`, the latter receives a single assistant message containing the conversation summary wrapped in `<CONVERSATION HISTORY>` markers, rather than the full raw transcript.

### Create a Custom History Mapper

Implement a custom mapper to retain only the last two user-assistant exchanges, reducing token usage for agents that only need recent context.

```python
from agents import RunConfig, Runner, Agent
from agents.types import TResponseInputItem

def last_two_turns_mapper(transcript: list[TResponseInputItem]) -> list[TResponseInputItem]:
    """Retain only the last two user/assistant turns."""
    user_assistant_only = [
        item for item in transcript
        if item.get("role") in ("user", "assistant")
    ]
    return user_assistant_only[-2:]  # Return at most two items

run_cfg = RunConfig(
    nest_handoff_history=True,
    handoff_history_mapper=last_two_turns_mapper,  # Inject custom logic

)

agent_a = Agent(name="Router")
agent_b = Agent(name="Specialist")
runner = Runner(agent_a, run_config=run_cfg)
result = runner.run("Initial query requiring escalation.")

```

The custom mapper receives the full flattened transcript and returns a filtered list. The next agent receives only those specific items as its `input_history`.

### Customize Conversation History Markers

Override the default XML-style markers used to wrap summarized history. This is rarely needed but useful if your model is sensitive to specific delimiter formats.

```python
from agents.handoffs.history import (
    set_conversation_history_wrappers,
    reset_conversation_history_wrappers
)

# Apply custom markers before running

set_conversation_history_wrappers(start="<<HISTORY>>", end="<<END HISTORY>>")

run_cfg = RunConfig(nest_handoff_history=True)

# ... execute runner ...

# Restore defaults to avoid affecting other runs

reset_conversation_history_wrappers()

```

The markers control how `_extract_nested_history_transcript` in [`src/agents/handoffs/history.py`](https://github.com/openai/openai-agents-python/blob/main/src/agents/handoffs/history.py) identifies previously summarized blocks when flattening nested history.

## Server-Managed Conversations and Limitations

When using server-managed conversation features—specifically when `conversation_id`, `previous_response_id`, or `auto_previous_response_id` is set—the SDK automatically disables nested handoff history. The `_resolve_server_managed_handoff_behavior` function in [`src/agents/run_internal/turn_resolution.py`](https://github.com/openai/openai-agents-python/blob/main/src/agents/run_internal/turn_resolution.py) (lines 14–20) detects these settings and logs a warning, because the server requires delta-only updates rather than collapsed history summaries.

If you need custom history management in server-managed mode, you must implement the logic outside the `handoff_history_mapper` pipeline, as the SDK overrides the flag internally.

## Summary

- **Enable nesting** by setting `RunConfig.nest_handoff_history=True` to collapse conversation transcripts into a single assistant message at each handoff.
- **Default behavior** wraps the transcript in `<CONVERSATION HISTORY>` markers via `default_handoff_history_mapper` in [`src/agents/handoffs/history.py`](https://github.com/openai/openai-agents-python/blob/main/src/agents/handoffs/history.py).
- **Customize mapping** by providing a `handoff_history_mapper` callable with signature `list[TResponseInputItem] -> list[TResponseInputItem]` to filter, truncate, or reformat the history.
- **Internal pipeline** normalizes input, flattens previous nested summaries, filters tool approvals, and constructs a new `HandoffInputData` with the mapped history.
- **Server-managed restrictions** disable nesting automatically when using `conversation_id` or `previous_response_id` settings.

## Frequently Asked Questions

### What is the default format of a nested handoff history message?

The default mapper in [`src/agents/handoffs/history.py`](https://github.com/openai/openai-agents-python/blob/main/src/agents/handoffs/history.py) creates a single assistant message containing the entire flattened transcript wrapped in `<CONVERSATION HISTORY>` and `</CONVERSATION HISTORY>` markers. This message becomes the sole `input_history` item for the receiving agent.

### Can I use a custom history mapper with server-managed conversations?

No. When you configure `conversation_id`, `previous_response_id`, or `auto_previous_response_id`, the SDK disables `nest_handoff_history` automatically in [`src/agents/run_internal/turn_resolution.py`](https://github.com/openai/openai-agents-python/blob/main/src/agents/run_internal/turn_resolution.py) and logs a warning. Server-managed conversations require delta-only updates, making collapsed history summaries incompatible.

### How does the SDK prevent duplicate summarization across multiple handoffs?

The `nest_handoff_history` function in [`src/agents/handoffs/history.py`](https://github.com/openai/openai-agents-python/blob/main/src/agents/handoffs/history.py) calls `_flatten_nested_history_messages` (lines 91–105) to scan for existing `<CONVERSATION HISTORY>` blocks. It extracts the content from these markers using `_extract_nested_history_transcript` and `_parse_summary_line`, then appends the original items to the flat list before generating a new summary. This prevents recursive summarization of summaries.

### What items are excluded from the forwarded transcript?

The internal filtering logic in [`src/agents/handoffs/history.py`](https://github.com/openai/openai-agents-python/blob/main/src/agents/handoffs/history.py) removes tool-approval items and omits summary-only items such as assistant messages, function-call items, and reasoning traces based on the `_should_forward_*` helper functions. Only substantive user inputs and tool outputs that represent actual conversation turns are retained in the transcript passed to the mapper.