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

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 (lines 65–78) Defines nest_handoff_history and handoff_history_mapper attributes.
Default mapper 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 (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 (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 (lines 24–30) _normalize_input_history converts string histories to TResponseInputItem objects.
History flattening 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 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.

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.

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.

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 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 (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.
  • 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 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 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 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 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.

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 →