Description
What happened
The group-chat orchestrator wraps each participant in an AgentExecutor that maintains a per-participant message cache. The interaction that leads to the crash:
- On each round, the orchestrator broadcasts the latest message to participants with
should_respond=False, which fills each participant's AgentExecutor._cache. (agent_framework_orchestrations/_base_group_chat_orchestrator.py:436)
- When a participant is selected to speak,
AgentExecutor runs it against its cache and then clears the cache (agent_framework/_workflows/_agent_executor.py:402).
- On the next round,
_handle_response broadcasts the new message to all participants except the one that just spoke (agent_framework_orchestrations/_group_chat.py:407, p != participant), so the just-spoken participant's cache stays empty.
_send_request_to_participant sends the next speaker an AgentExecutorRequest that contains no conversation messages — only an optional additional_instruction, which AgentBasedGroupChatOrchestrator never provides (_base_group_chat_orchestrator.py:467-473; call sites _group_chat.py:367-370 and 409-412).
- If the orchestrator selects the same participant that just spoke (or otherwise selects a participant whose cache is currently empty),
AgentExecutor calls agent.run([]).
For a normal chat-client agent, run([]) is tolerated — AgentExecutor only logs a warning (_agent_executor.py:417-422). But A2AAgent.run hard-raises when the normalized message list is empty and there is no continuation token (agent_framework_a2a/_agent.py:315-316), which aborts the entire workflow.
What I expected to happen
An A2AAgent used as a group-chat participant should behave like other participants when invoked with an empty cache — i.e., not crash the workflow. Either:
A2AAgent.run([]) should degrade gracefully (e.g., return an empty response) instead of raising, consistent with how AgentExecutor treats empty caches for other agents; and/or
- the group-chat orchestrator should carry conversation context in the request to the selected participant (rather than relying solely on prior broadcasts that skip the just-spoken participant), so a participant is never invoked with an empty cache.
Why it's intermittent / environment-dependent
The crash only occurs when the orchestrator model selects a participant whose cache is empty — most commonly re-selecting the participant that just spoke. Whether this happens depends on the orchestrator LLM's selection behavior (MAF builds its own selection prompt internally at _group_chat.py:508-520; the participant/agent's own instructions don't control selection). We observed the same code path succeed with one orchestrator model and crash with another. Because only A2AAgent turns an empty cache into a hard error, group chats whose participants are all A2AAgents are especially exposed.
Steps to reproduce
- Build a group chat with
GroupChatBuilder(participants=[a2a_agent_1, a2a_agent_2], orchestrator_agent=orchestrator).build() where the participants are A2AAgent instances.
- Run a multi-turn conversation where the orchestrator selects the same A2A participant on two consecutive rounds (or selects an A2A participant immediately after it spoke, before any other participant speaks).
- Observe the
ValueError raised from A2AAgent.run / _map_a2a_stream.
Code Sample
from agent_framework.a2a import A2AAgent
from agent_framework.orchestrations import GroupChatBuilder
reasoner = A2AAgent(name="reasoner", agent_card=...) # remote A2A agent
vector_db = A2AAgent(name="vector_db", agent_card=...) # remote A2A agent
workflow = GroupChatBuilder(
participants=[reasoner, vector_db],
orchestrator_agent=orchestrator, # an LLM chat agent selecting next_speaker
intermediate_output_from="all",
).build()
# Multi-turn run in which the orchestrator re-selects an A2A participant
# whose AgentExecutor cache was just cleared -> A2AAgent.run([]) -> ValueError
async for _ in workflow.run_stream("..."):
...
Error Messages / Stack Traces
ValueError: At least one message is required when starting a new task (no continuation_token).
Raised at:
agent_framework_a2a/_agent.py, in run (line 315-316):
if not normalized_messages:
raise ValueError(
"At least one message is required when starting a new task (no continuation_token)."
)
Reached via the group-chat participant executor calling `agent.run(self._cache)` with an empty `self._cache`:
agent_framework/_workflows/_agent_executor.py:424-431 -> A2AAgent.run([])
Package Versions
agent-framework-core: 1.6.0 agent-framework-a2a: 1.0.0b260521 agent-framework-orchestrations: 1.0.0rc2
(Also reproducible against the current main — the relevant logic in _agent.py, _agent_executor.py, _group_chat.py, and _base_group_chat_orchestrator.py is unchanged.)
Python Version
Python 3.12
Additional Context
Suggested fixes (either or both):
A2AAgent.run empty-input handling: when invoked with empty messages and no continuation token in a context where the framework tolerates it (as AgentExecutor does for chat agents), return an empty AgentResponse instead of raising — so A2A agents are first-class group-chat participants.
- Group-chat request context: have
AgentBasedGroupChatOrchestrator include the current conversation (or last message) in the AgentExecutorRequest sent to the selected participant (_send_request_to_participant), rather than relying on broadcasts that exclude the just-spoken participant. This guarantees a non-empty cache at run time regardless of selection order.
Local workaround (no framework changes): wrap each A2AAgent participant in a thin SupportsAgentRun adapter that short-circuits run() to an empty AgentResponse when the normalized message list is empty, delegating unchanged otherwise. This stops the crash and lets the group-chat loop continue.
Description
What happened
The group-chat orchestrator wraps each participant in an
AgentExecutorthat maintains a per-participant message cache. The interaction that leads to the crash:should_respond=False, which fills each participant'sAgentExecutor._cache. (agent_framework_orchestrations/_base_group_chat_orchestrator.py:436)AgentExecutorruns it against its cache and then clears the cache (agent_framework/_workflows/_agent_executor.py:402)._handle_responsebroadcasts the new message to all participants except the one that just spoke (agent_framework_orchestrations/_group_chat.py:407,p != participant), so the just-spoken participant's cache stays empty._send_request_to_participantsends the next speaker anAgentExecutorRequestthat contains no conversation messages — only an optionaladditional_instruction, whichAgentBasedGroupChatOrchestratornever provides (_base_group_chat_orchestrator.py:467-473; call sites_group_chat.py:367-370and409-412).AgentExecutorcallsagent.run([]).For a normal chat-client agent,
run([])is tolerated —AgentExecutoronly logs a warning (_agent_executor.py:417-422). ButA2AAgent.runhard-raises when the normalized message list is empty and there is no continuation token (agent_framework_a2a/_agent.py:315-316), which aborts the entire workflow.What I expected to happen
An
A2AAgentused as a group-chat participant should behave like other participants when invoked with an empty cache — i.e., not crash the workflow. Either:A2AAgent.run([])should degrade gracefully (e.g., return an empty response) instead of raising, consistent with howAgentExecutortreats empty caches for other agents; and/orWhy it's intermittent / environment-dependent
The crash only occurs when the orchestrator model selects a participant whose cache is empty — most commonly re-selecting the participant that just spoke. Whether this happens depends on the orchestrator LLM's selection behavior (MAF builds its own selection prompt internally at
_group_chat.py:508-520; the participant/agent's own instructions don't control selection). We observed the same code path succeed with one orchestrator model and crash with another. Because onlyA2AAgentturns an empty cache into a hard error, group chats whose participants are allA2AAgents are especially exposed.Steps to reproduce
GroupChatBuilder(participants=[a2a_agent_1, a2a_agent_2], orchestrator_agent=orchestrator).build()where the participants areA2AAgentinstances.ValueErrorraised fromA2AAgent.run/_map_a2a_stream.Code Sample
from agent_framework.a2a import A2AAgent from agent_framework.orchestrations import GroupChatBuilder reasoner = A2AAgent(name="reasoner", agent_card=...) # remote A2A agent vector_db = A2AAgent(name="vector_db", agent_card=...) # remote A2A agent workflow = GroupChatBuilder( participants=[reasoner, vector_db], orchestrator_agent=orchestrator, # an LLM chat agent selecting next_speaker intermediate_output_from="all", ).build() # Multi-turn run in which the orchestrator re-selects an A2A participant # whose AgentExecutor cache was just cleared -> A2AAgent.run([]) -> ValueError async for _ in workflow.run_stream("..."): ...Error Messages / Stack Traces
Package Versions
agent-framework-core: 1.6.0 agent-framework-a2a: 1.0.0b260521 agent-framework-orchestrations: 1.0.0rc2
(Also reproducible against the current
main— the relevant logic in_agent.py,_agent_executor.py,_group_chat.py, and_base_group_chat_orchestrator.pyis unchanged.)Python Version
Python 3.12
Additional Context
Suggested fixes (either or both):
A2AAgent.runempty-input handling: when invoked with empty messages and no continuation token in a context where the framework tolerates it (asAgentExecutordoes for chat agents), return an emptyAgentResponseinstead of raising — so A2A agents are first-class group-chat participants.AgentBasedGroupChatOrchestratorinclude the current conversation (or last message) in theAgentExecutorRequestsent to the selected participant (_send_request_to_participant), rather than relying on broadcasts that exclude the just-spoken participant. This guarantees a non-empty cache at run time regardless of selection order.Local workaround (no framework changes): wrap each
A2AAgentparticipant in a thinSupportsAgentRunadapter that short-circuitsrun()to an emptyAgentResponsewhen the normalized message list is empty, delegating unchanged otherwise. This stops the crash and lets the group-chat loop continue.