-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Python: InMemoryHistoryProvider auto-injection causes message duplication in HandoffBuilder workflows #5147
Description
Description
When agents participate in a HandoffBuilder workflow with CheckpointStorage, conversation history is managed by the HandoffAgentExecutor via its internal _full_conversation list. However, the Agent base class auto-injects an InMemoryHistoryProvider when context_providers is empty. This creates two independent history sources that compound on every turn, causing quadratic message growth and eventual API failures.
Reproduction
- Create a
HandoffBuilderworkflow withCheckpointStorageand 2+ agents - Leave at least one agent with an empty
context_providerslist (the default) - Run a multi-turn conversation (3+ user messages)
Observed behavior
- Turn 1: Agent receives N messages (correct)
- Turn 2: Agent receives ~2N messages (executor history +
InMemoryHistoryProviderreplay) - Turn 3: Agent receives ~3N messages
- Each turn inflates the message list further
This results in:
- The LLM re-requesting tool calls it already completed (duplicate task progress in the UI)
- OpenAI 400 errors:
"An assistant message with 'tool_calls' must be followed by tool messages"— the duplicated history placestool_callsmessages where the matchingtoolresult is displaced - Token usage growing quadratically instead of linearly
Expected behavior
Agents inside a HandoffBuilder workflow should not accumulate a second copy of conversation history. The executor is the single source of truth.
Current workaround
Create a no-op ContextProvider and assign it to every workflow participant that doesn't already have one:
class NoHistoryProvider(ContextProvider):
def __init__(self):
super().__init__("no_history_provider")
async def before_run(self, *, agent, session, context, state):
passAgent(
client=client,
instructions="...",
name="my_agent",
context_providers=[NoHistoryProvider()] # prevents auto-injection
)This is fragile — every new agent added to a workflow must remember to include it, and there's no framework-level indication that auto-injection is inappropriate.
For a real-world example of this workaround applied to a multi-agent banking assistant, see:
https://github.com/Azure-Samples/agent-openai-python-banking-assistant/blob/main/app/backend/app/agents/azure_chat/handoff_orchestrator.py#L57
Suggested resolution (pick one or combine)
| Option | Description |
|---|---|
| A. Suppress auto-injection inside workflows | HandoffBuilder or HandoffAgentExecutor should disable InMemoryHistoryProvider auto-injection for all participants, since it owns the conversation state. |
| B. Add an explicit opt-out | Provide Agent(..., history_provider=None) or Agent(..., auto_history=False) so developers can explicitly disable it without a dummy provider. |
| C. Document the interaction | At minimum, add a warning in the HandoffBuilder / orchestration docs explaining that agents with empty context_providers will get InMemoryHistoryProvider auto-injected, which conflicts with the executor's history management. Include the no-op provider pattern as a recommended practice. |
Environment
agent-framework-core==1.0.0agent-framework-orchestrations==1.0.0b260402agent-framework-openai==1.0.0/agent-framework-foundry==1.0.0- Reproducible with both
OpenAIChatCompletionClientandFoundryChatClient