Describe the bug
When an A2AAgent participant returns a task in TASK_STATE_INPUT_REQUIRED, the remote agent is asking the caller a question. Agent Framework does not treat this as a pause: A2AAgent surfaces it like a terminal response (_agent.py:631, :820), so the orchestration continues and the next message routed to that participant is silently bound to the waiting task as its answer — without the caller ever being consulted.
The binding is message-agnostic (_agent.py:965-968):
if previous_task_id:
if task_state == TaskState.TASK_STATE_INPUT_REQUIRED:
a2a_message.task_id = previous_task_id # continues the same task
Verified against A2AAgent with a session in INPUT_REQUIRED:
peer message -> task_id = 'task-42' reference_task_ids = []
peer, completed -> task_id = '' reference_task_ids = ['task-42']
So in a group chat, another participant's reply becomes the answer to a confirmation prompt. AgentExecutor holds a persistent AgentSession per executor (_agent_executor.py:174, passed at :435/:491), so task state survives across turns and this is reachable in normal multi-turn use.
Expected behavior
A task awaiting caller input should suspend the workflow and surface a request to the caller. Only caller-supplied input should be bound to the waiting task.
Proposed fix
AgentExecutor already pauses when a response carries user input requests (_agent_executor.py:441-448):
if response.user_input_requests:
for user_input_request in response.user_input_requests:
await ctx.request_info(user_input_request, Content, request_id=user_input_request.id)
return None # no send_message, no _cache.clear()
user_input_request is a generic flag on Content (_types.py:587) — only the function-approval factories set it today (:1287, :1340), but nothing ties it to approvals.
So A2AAgent should surface INPUT_REQUIRED as a content flagged user_input_request=True. The rest follows: the executor returns early without clearing its cache, the orchestration suspends, the caller gets a standard request_info event, and on reply handle_user_input_response re-runs with the caller's message — which is then correctly bound to the waiting task.
Scope
packages/a2a: branch on INPUT_REQUIRED in _updates_from_task / _updates_from_task_update_event (~:757-840), emitting a request-shaped content keyed by task_id.
packages/core/_types.py: likely a Content.from_user_input_request(...) factory, since existing producers are approval-shaped. New public API — needs feature-lifecycle review.
- Round-trip constraint:
_prepare_message_for_a2a raises Unknown content type for contents it cannot map to an A2A part, so the caller's reply must serialise to a text part. (Confirmed: the resumed cache is [Message(role="user", contents=[<approval Content>])], whose .text is empty.)
- Tests for the new shape plus suspend/resume; docs in
packages/a2a/AGENTS.md and a HITL sample.
No orchestration changes required
Simulated the post-fix behaviour with a participant that raises a user input request in a two-participant group chat:
request_info events: 1
alpha calls: 1 | beta calls: 0 <- chat halted; beta never invoked
after resume -> alpha calls: 3 | beta calls: 2
The orchestrator only advances in _handle_response and exactly one participant holds should_respond=True per round, so a suspended executor halts the chat implicitly. Handoff and Magentic inherit the same property.
Minor note: _increment_round() fires when the request is sent, so a suspended turn consumes a round.
Risk
This is a behaviour change and arguably breaking. Today a group chat answers a remote agent's question and continues; afterwards it stops and waits for a human. For some users that is the bug being fixed, for others a working flow that begins blocking. Whether it should be unconditional or opt-in is a maintainer decision.
Related
Found while reviewing #7549 (fixes #7456). The auto-answering predates that PR and is independent of it.
Describe the bug
When an
A2AAgentparticipant returns a task inTASK_STATE_INPUT_REQUIRED, the remote agent is asking the caller a question. Agent Framework does not treat this as a pause:A2AAgentsurfaces it like a terminal response (_agent.py:631,:820), so the orchestration continues and the next message routed to that participant is silently bound to the waiting task as its answer — without the caller ever being consulted.The binding is message-agnostic (
_agent.py:965-968):Verified against
A2AAgentwith a session inINPUT_REQUIRED:So in a group chat, another participant's reply becomes the answer to a confirmation prompt.
AgentExecutorholds a persistentAgentSessionper executor (_agent_executor.py:174, passed at:435/:491), so task state survives across turns and this is reachable in normal multi-turn use.Expected behavior
A task awaiting caller input should suspend the workflow and surface a request to the caller. Only caller-supplied input should be bound to the waiting task.
Proposed fix
AgentExecutoralready pauses when a response carries user input requests (_agent_executor.py:441-448):user_input_requestis a generic flag onContent(_types.py:587) — only the function-approval factories set it today (:1287,:1340), but nothing ties it to approvals.So
A2AAgentshould surfaceINPUT_REQUIREDas a content flaggeduser_input_request=True. The rest follows: the executor returns early without clearing its cache, the orchestration suspends, the caller gets a standardrequest_infoevent, and on replyhandle_user_input_responsere-runs with the caller's message — which is then correctly bound to the waiting task.Scope
packages/a2a: branch onINPUT_REQUIREDin_updates_from_task/_updates_from_task_update_event(~:757-840), emitting a request-shaped content keyed bytask_id.packages/core/_types.py: likely aContent.from_user_input_request(...)factory, since existing producers are approval-shaped. New public API — needs feature-lifecycle review._prepare_message_for_a2araisesUnknown content typefor contents it cannot map to an A2A part, so the caller's reply must serialise to a text part. (Confirmed: the resumed cache is[Message(role="user", contents=[<approval Content>])], whose.textis empty.)packages/a2a/AGENTS.mdand a HITL sample.No orchestration changes required
Simulated the post-fix behaviour with a participant that raises a user input request in a two-participant group chat:
The orchestrator only advances in
_handle_responseand exactly one participant holdsshould_respond=Trueper round, so a suspended executor halts the chat implicitly. Handoff and Magentic inherit the same property.Minor note:
_increment_round()fires when the request is sent, so a suspended turn consumes a round.Risk
This is a behaviour change and arguably breaking. Today a group chat answers a remote agent's question and continues; afterwards it stops and waits for a human. For some users that is the bug being fixed, for others a working flow that begins blocking. Whether it should be unconditional or opt-in is a maintainer decision.
Related
Found while reviewing #7549 (fixes #7456). The auto-answering predates that PR and is independent of it.