Description
When running a HandoffBuilder workflow, the orchestrator forces store=False on cloned agents (in _clone_chat_agent(), line 375 of _handoff.py). This is intentional and well-documented in the code comments:
"Handoff workflows already manage full conversation context explicitly across executors. Keep provider-side conversation storage disabled to avoid stale tool-call state (Responses API previous_response chains)."
However, when the handoff replays conversation history to the next agent, the serialized input items (specifically reasoning and function_call) retain their server-assigned IDs (rs_* and fc_* prefixes). Since store=False means the API did not persist those items, the IDs reference non-existent objects. The Responses API then rejects the request with:
Item with id 'rs_...' not found. Items are not persisted when store is set to false.
Root cause
In _responses_client.py, the method _prepare_content_for_openai() unconditionally includes server-assigned IDs:
- Reasoning items (line ~975):
if content.id: ret["id"] = content.id — always includes the rs_* ID
- Function call items (line ~1051):
"id": fc_id — always includes the fc_* ID
When these items are replayed as part of a handoff conversation under store=False, the API cannot resolve those IDs because nothing was persisted.
Steps to reproduce
- Run the official sample
handoff_autonomous.py using AzureOpenAIResponsesClient (or AzureAIClient)
- Wait for the first handoff (e.g., coordinator → research_agent)
- When the second agent responds and the conversation is replayed back with
reasoning or function_call items from the previous turn, the API rejects it
This is reproducible with any model and with both AzureOpenAIResponsesClient and AzureAIClient.
What did you expect to happen?
The handoff should successfully replay conversation history to the next agent without API errors, even under store=False.
What happened?
The API rejects the input because it references item IDs (rs_*, fc_*) that were never persisted (due to store=False).
Code Sample
The exact official sample that reproduces the bug:
# https://github.com/microsoft/agent-framework/blob/main/python/samples/03-workflows/orchestrations/handoff_autonomous.py
import asyncio
import os
from typing import cast
from agent_framework import Agent, AgentResponseUpdate, Message, resolve_agent_id
from agent_framework.azure import AzureOpenAIResponsesClient
from agent_framework.orchestrations import HandoffBuilder
from azure.identity import AzureCliCredential
from dotenv import load_dotenv
load_dotenv()
def create_agents(client):
coordinator = client.as_agent(
instructions="You are a coordinator. You break down a user query into a research task and a summary task. Assign the two tasks to the appropriate specialists, one after the other.",
name="coordinator",
)
research_agent = client.as_agent(
instructions="You are a research specialist that explores topics thoroughly. When given a research task, break it down into multiple aspects and explore each one. Continue your research across multiple responses. When you have covered the topic comprehensively, return control to the coordinator.",
name="research_agent",
)
summary_agent = client.as_agent(
instructions="You summarize research findings. Provide a concise, well-organized summary. When done, return control to the coordinator.",
name="summary_agent",
)
return coordinator, research_agent, summary_agent
async def main():
client = AzureOpenAIResponsesClient(
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
credential=AzureCliCredential(),
)
coordinator, research_agent, summary_agent = create_agents(client)
workflow = (
HandoffBuilder(
name="autonomous_iteration_handoff",
participants=[coordinator, research_agent, summary_agent],
termination_condition=lambda conv: (
sum(1 for msg in conv if msg.author_name == "coordinator" and msg.role == "assistant") >= 5
),
)
.with_start_agent(coordinator)
.add_handoff(coordinator, [research_agent, summary_agent])
.add_handoff(research_agent, [coordinator])
.add_handoff(summary_agent, [coordinator])
.with_autonomous_mode(
turn_limits={
resolve_agent_id(coordinator): 5,
resolve_agent_id(research_agent): 10,
resolve_agent_id(summary_agent): 5,
}
)
.build()
)
request = "Perform a comprehensive research on Microsoft Agent Framework."
async for event in workflow.run(request, stream=True):
if event.type == "handoff_sent":
print(f"\nHandoff: {event.data.source} -> {event.data.target}\n")
elif event.type == "output":
data = event.data
if isinstance(data, AgentResponseUpdate) and data.text:
print(data.text, end="", flush=True)
if __name__ == "__main__":
asyncio.run(main())
Error Messages / Stack Traces
openai.BadRequestError: Error code: 400 - {
'error': {
'message': "Item with id 'rs_...' not found. Items are not persisted when store is set to false.",
'type': 'invalid_request_error',
'param': 'input',
'code': None
}
}
The error is raised during the second agent invocation after a handoff, when the conversation replay includes reasoning or function_call items with server-assigned IDs from the prior agent's response.
Package Versions
agent-framework: 1.0.0rc2
agent-framework-orchestrations: 1.0.0b260225
agent-framework-azure-ai: 1.0.0rc2
Python Version
Python 3.13
Additional Context
Relationship to #4053
This bug is distinct from the issues tracked in #4053 (stale previous_response_id and context loss). The fixes merged via PR #3911 (clearing service_session_id and using _full_conversation for cache) are already present in our installed version (1.0.0b260225) and correctly address those problems. However, those fixes do not address the server-assigned ID leakage described here.
Proposed fix
In _prepare_content_for_openai() (in _responses_client.py), omit the id field from reasoning and function_call items when store=False. The items should be replayed by value rather than by reference, since there is nothing persisted server-side to reference.
Possible implementation (in _prepare_options or _prepare_messages_for_openai):
# After building the input items, strip server-assigned IDs when store=False
if run_options.get("store") is False and "input" in run_options:
for item in run_options["input"]:
if isinstance(item, dict) and item.get("type") in ("reasoning", "function_call"):
item.pop("id", None)
Alternatively, the check could be done at the serialization level in _prepare_content_for_openai() if store is available in scope:
- For
text_reasoning: only include ret["id"] = content.id when store is not False
- For
function_call: generate a fresh fc_ prefixed ID (e.g., from call_id) instead of reusing the server-assigned one, or omit it
Note: We have validated this approach as a monkeypatch in our production code and it resolves the issue without observable side effects. However, the team should evaluate whether omitting these IDs could affect other scenarios (e.g., when store=True and IDs are expected).
Working monkeypatch (temporary workaround)
For anyone encountering this issue, the following monkeypatch can be applied before running the workflow:
import agent_framework.openai._responses_client as _rc
_orig_prepare = _rc.RawOpenAIResponsesClient._prepare_options
async def _patched_prepare(self, messages, options, **kwargs):
run_options = await _orig_prepare(self, messages, options, **kwargs)
if run_options.get("store") is False and "input" in run_options:
for item in run_options["input"]:
if isinstance(item, dict) and item.get("type") in ("reasoning", "function_call"):
item.pop("id", None)
return run_options
_rc.RawOpenAIResponsesClient._prepare_options = _patched_prepare
Description
When running a
HandoffBuilderworkflow, the orchestrator forcesstore=Falseon cloned agents (in_clone_chat_agent(), line 375 of_handoff.py). This is intentional and well-documented in the code comments:However, when the handoff replays conversation history to the next agent, the serialized input items (specifically
reasoningandfunction_call) retain their server-assigned IDs (rs_*andfc_*prefixes). Sincestore=Falsemeans the API did not persist those items, the IDs reference non-existent objects. The Responses API then rejects the request with:Root cause
In
_responses_client.py, the method_prepare_content_for_openai()unconditionally includes server-assigned IDs:if content.id: ret["id"] = content.id— always includes thers_*ID"id": fc_id— always includes thefc_*IDWhen these items are replayed as part of a handoff conversation under
store=False, the API cannot resolve those IDs because nothing was persisted.Steps to reproduce
handoff_autonomous.pyusingAzureOpenAIResponsesClient(orAzureAIClient)reasoningorfunction_callitems from the previous turn, the API rejects itThis is reproducible with any model and with both
AzureOpenAIResponsesClientandAzureAIClient.What did you expect to happen?
The handoff should successfully replay conversation history to the next agent without API errors, even under
store=False.What happened?
The API rejects the input because it references item IDs (
rs_*,fc_*) that were never persisted (due tostore=False).Code Sample
The exact official sample that reproduces the bug:
Error Messages / Stack Traces
The error is raised during the second agent invocation after a handoff, when the conversation replay includes
reasoningorfunction_callitems with server-assigned IDs from the prior agent's response.Package Versions
agent-framework: 1.0.0rc2agent-framework-orchestrations: 1.0.0b260225agent-framework-azure-ai: 1.0.0rc2Python Version
Python 3.13
Additional Context
Relationship to #4053
This bug is distinct from the issues tracked in #4053 (stale
previous_response_idand context loss). The fixes merged via PR #3911 (clearingservice_session_idand using_full_conversationfor cache) are already present in our installed version (1.0.0b260225) and correctly address those problems. However, those fixes do not address the server-assigned ID leakage described here.Proposed fix
In
_prepare_content_for_openai()(in_responses_client.py), omit theidfield fromreasoningandfunction_callitems whenstore=False. The items should be replayed by value rather than by reference, since there is nothing persisted server-side to reference.Possible implementation (in
_prepare_optionsor_prepare_messages_for_openai):Alternatively, the check could be done at the serialization level in
_prepare_content_for_openai()ifstoreis available in scope:text_reasoning: only includeret["id"] = content.idwhenstoreis notFalsefunction_call: generate a freshfc_prefixed ID (e.g., fromcall_id) instead of reusing the server-assigned one, or omit itNote: We have validated this approach as a monkeypatch in our production code and it resolves the issue without observable side effects. However, the team should evaluate whether omitting these IDs could affect other scenarios (e.g., when
store=Trueand IDs are expected).Working monkeypatch (temporary workaround)
For anyone encountering this issue, the following monkeypatch can be applied before running the workflow: