Skip to content

Python: [Bug]: A2A INPUT_REQUIRED tasks are auto-answered instead of pausing for the caller #7620

Description

@giles17

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.

Metadata

Metadata

Labels

pythonUsage: [Issues, PRs], Target: Python

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions