Skip to content

Python: [Bug]: A2AAgent as a group chat participant crashes with "At least one message is required" on empty-cache turns #7456

Description

@greghorne20

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:

  1. 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)
  2. 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).
  3. 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.
  4. _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).
  5. 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

  1. Build a group chat with GroupChatBuilder(participants=[a2a_agent_1, a2a_agent_2], orchestrator_agent=orchestrator).build() where the participants are A2AAgent instances.
  2. 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).
  3. 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):

  1. 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.
  2. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    likely-fixedpythonUsage: [Issues, PRs], Target: PythontriageUsage: [Issues], Target: All issues that still need to be triaged

    Type

    Projects

    Status
    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions