Skip to content

RemoteA2aAgent ends the invocation silently when the A2A stream stops before a terminal task state #6585

Description

@ferponse

Environment

  • google-adk 2.6.0
  • a2a-sdk 1.1.2
  • Python 3.12

Summary

If the A2A response stream stops before the task reaches a terminal state — the peer's process
dies, its side times out, a proxy closes the connection cleanly — RemoteA2aAgent._run_async_impl
just returns. The async for over the client's responses ends, the generator finishes, and:

  • no Event with an error_message is emitted (the except blocks only fire on an exception, and
    a cleanly-closed stream is not one),
  • the final task state is never checked,
  • neither a state query nor a resubscription is attempted, although the a2a-sdk offers both
    (a2a/client/base_client.py: async def get_task(...), and async def subscribe(...), whose
    docstring describes its request as "parameters to identify the task to resubscribe to").

The consumer therefore receives a truncated stream that is byte-for-byte indistinguishable from
a complete one
: the last events are ordinary non-terminal updates, and the generator ends
normally.

Reproduction

  1. Serve any ADK agent over A2A with to_a2a, with a card that advertises streaming
    (AgentCardBuilder(..., capabilities=AgentCapabilities(streaming=True))).
  2. From a RemoteA2aAgent, start a turn that makes the peer emit at least one working status
    update carrying text (any agent that narrates before/while calling a tool does this).
  3. Kill the peer process while the turn is in flight, after that update has been delivered.

Observed

The client yields the events built from the updates already received and then the invocation ends
silently. Measured on our side, the sequence was:

13:12:52  delegating to the remote agent
13:12:57  function_call / function_response delivered
13:12:57  text delivered on a `working` update
          <peer killed>
          run_async ends — no error event, no terminal task state, nothing

The peer's task was left in TASK_STATE_WORKING forever, and nothing said so.

Expected

When the stream is exhausted without the task having reached a terminal state, RemoteA2aAgent
should do one of:

  • call client.get_task(...) to resolve the actual final state and emit the corresponding event, or
  • resubscribe once through client.subscribe(...) and continue, or
  • failing both, emit an Event(error_message=...) describing the truncation — the same shape the
    except branches already produce.

Any of the three lets a consumer tell "the peer is done" from "the peer vanished".

Worth noting: ADK already models the notion of a terminal A2A event elsewhere — in the workflow
subclass, response promotion is documented as "gated to the first terminal A2A event of the run" —
so the concept exists; it is the normal invocation path that never checks whether one arrived.

Impact / workaround

Any UI that unblocks on a closing event hangs, and there is nothing in the logs to point at: no
exception, no error event. We had to add a guard at the transport boundary — "if the stream ended
and the client was never released, release it and report it" — which is a net that cannot name
the cause, only its absence.

Related: #6274 (a2a-sdk 1.x). Filed alongside a separate issue about is_final_response() not
recognising a terminal task event that carries tool activity, which is the other way a turn can
end without a detectable close.

Metadata

Metadata

Assignees

No one assigned

    Labels

    a2a[Component] This issue is related a2a support inside ADK.

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions