Skip to content

Bug: Next agent receives duplicate history (summary + raw tool calls) when nest_handoff_history is enabled #2171

@pokliu

Description

@pokliu

Please read this first

  • Have you read the docs?Agents SDK docs [Yes]
  • Have you searched for related issues? Others may have faced similar issues. [Yes]

Describe the bug

When nest_handoff_history is enabled (which is the default behavior), the SDK summarizes the conversation history into a single assistant message containing <CONVERSATION HISTORY>. This summary correctly includes the handoff tool calls (function_call) and their outputs.

However, the logic that filters the summarized items from the input history (in agents.handoffs.history.nest_handoff_history) only removes items where role == "assistant". Since Tool Calls (type="function_call") and Tool Outputs (type="function_call_output") do not have a role attribute (or it is None), they are NOT filtered out.

As a result, the next agent receives inputs containing duplicate information:

  1. The "Summary Message" (which mentions the handoff tool call).
  2. The original "Handoff Tool Call" and "Handoff Tool Output" items in the raw message list.

This duplication causes the model to see the same action twice, confusing the context and consuming unnecessary tokens.

Debug information

  • Agents SDK version: ( v0.6.2)
  • Python version: 3.12

Repro steps

Run the following script. It sets up Agent A to hand off to Agent B, and uses call_model_input_filter to inspect the actual input sent to Agent B. It requires a valid OPENAI_API_KEY.

import asyncio
import os
from typing import Any

from agents import Agent, Runner, RunConfig
from agents.run import CallModelData, ModelInputData

# Ensure OPENAI_API_KEY is set in your environment
os.environ["OPENAI_API_KEY"] = "sk-proj-***"

def check_duplication_filter(data: CallModelData[Any]) -> ModelInputData:
    """
    Inspects the input for Agent B to check for duplicates.
    """
    if data.agent.name == "AgentB":
        print(f"\n=== Inspecting Input for {data.agent.name} ===")
        summary_found = False
        tool_call_found = False
        
        inputs = data.model_data.input
        for i, item in enumerate(inputs):
            role = item.get('role')
            msg_type = item.get('type')
            content = str(item.get('content', '')).replace('\n', ' ')
            name = item.get('name', '')
            print(f"[{i}] role={role}, type={msg_type}, name={name}, content={content}...")

            # 1. Check for Summary Message
            if role == "assistant" and "<CONVERSATION HISTORY>" in content:
                summary_found = True
            
            # 2. Check for Raw Handoff Tool Call
            if msg_type == "function_call" and "transfer_to" in name:
                tool_call_found = True
        
        if summary_found and tool_call_found:
            print("\n[FAIL] Duplication detected: Both Summary and Raw Tool Call exist in input!")
        else:
            print("\n[PASS] No duplication detected.")
            
    return data.model_data

async def main():
    agent_b = Agent(
        name="AgentB", 
        instructions="You are Agent B. Just say hello."
    )
    
    agent_a = Agent(
        name="AgentA", 
        instructions="You are Agent A. Just transfer to AgentB",
        handoffs=[agent_b]
    )

    print("Running reproduction flow...")
    
    # Run Agent A and let it hand off to Agent B
    await Runner.run(
        agent_a, 
        "Start conversation", 
        run_config=RunConfig(
            call_model_input_filter=check_duplication_filter
        )
    )

if __name__ == "__main__":
    asyncio.run(main())

Output:

Running reproduction flow...

=== Inspecting Input for AgentB ===
[0] role=assistant, type=None, name=, content=For context, here is the conversation so far between the user and the previous agent: <CONVERSATION HISTORY> 1. user: Start conversation 2. function_call: {"arguments": "{}", "call_id": "call_DjTNgeg17pk5Y2c8peaG2i6Z", "name": "transfer_to_agentb", "id": "fc_025c33410193e02200693a4ad2e8e08190b56f9e509fc3dee9", "status": "completed"} 3. function_call_output: {"call_id": "call_DjTNgeg17pk5Y2c8peaG2i6Z", "output": "{\"assistant\": \"AgentB\"}"} </CONVERSATION HISTORY>...
[1] role=None, type=function_call, name=transfer_to_agentb, content=...
[2] role=None, type=function_call_output, name=, content=...

[FAIL] Duplication detected: Both Summary and Raw Tool Call exist in input!

Expected behavior

The input list for the next agent (Agent B) should ONLY contain the summarized history message. The raw handoff tool calls and outputs should be removed from the input list because they are already represented in the <CONVERSATION HISTORY> summary.

Suggested Fix

In agents/handoffs/history.py, the filtering logic in nest_handoff_history should be updated to also filter out items based on their type, not just their role.

    filtered_pre_items = tuple(
        item
        for item in handoff_input_data.pre_handoff_items
        if _get_run_item_role(item) != "assistant" 
        and item.type not in ("handoff_call_item", "handoff_output_item", "tool_call_item", "tool_call_output_item")
    )

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions