You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Follow-up to #4309: fix for failed A2A tasks needs both _handle_a2a_response/_v2, _compat.TS_FAILED, and error_code — still reproducible in 2.6.2 #6708
Follow-up to #4309 ("Failed A2A task errors leak into conversation history as regular content"), closed for lack of follow-up rather than a fix. Filing separately since we hit this independently on a newer version and found a few things the original report and its proposed workaround don't cover — happy to have this folded into #4309 instead if that's preferred.
Still present in google-adk==2.6.2 (Python 3.12). convert_a2a_task_to_event() (google/adk/a2a/converters/event_converter.py) never reads a2a_task.status.state; it only extracts message content and returns a plain Event. Same root cause as reported for 1.1.0 → 1.24.1.
This is a round-trip asymmetry, not just a missing check.convert_event_to_a2a_events() (used when ADK is the A2A server) already does the reverse correctly: if event.error_code is set, _create_error_status_event() builds a TaskStatus(state=TASK_STATE_FAILED, ...). So ADK already models "failed task" as a first-class A2A state on the way out — it just never reads that same state back in on the way in. Two ADK instances talking A2A to each other already lose this, not only ADK-vs-third-party.
Three things worth folding into whatever fix lands:
Which hook actually fires.RemoteA2aAgent dispatches each response to either _handle_a2a_response or _handle_a2a_response_v2 depending on whether the task carries ADK's own integration-extension marker. A third-party (non-ADK) A2A server never sets that marker, so every response goes through _handle_a2a_response — _handle_a2a_response_v2 never fires against it at all. A workaround/fix that only patches _v2 silently does nothing in that setup.
Streaming vs non-streaming shape. With streaming=True the terminal state arrives as an A2ATaskStatusUpdateEvent (update.status.state), not on the Task itself (task.status.state) — already flagged in Failed A2A task errors leak into conversation history as regular content #4309, restating because both hooks from point 1 need to handle both shapes.
TaskState.failed isn't version-safe. The proposed fix in Failed A2A task errors leak into conversation history as regular content #4309 compares against TaskState.failed, which only exists on the pydantic-enum shape of a2a-sdk (0.3.x). On the protobuf shape (1.x, e.g. a2a-sdk==1.1.2, bundled with google-adk==2.6.2), TaskState has no .failed attribute at all — the value is TaskState.Value("TASK_STATE_FAILED"). ADK already has a shim for exactly this split: google.adk.a2a._compat.TS_FAILED. Any fix should compare against that instead of a hardcoded enum member.
Also: the proposed fix sets error_message but not error_code. That stops the leak into history, but callers that want to tell "remote task failed" apart from other RemoteA2aAgent error paths generally key off error_code (which exists on Event for exactly that). Worth setting a stable code (e.g. "A2A_TASK_FAILED") alongside the message.
Sketch (untested against internals of every version, but shows the shape):
Follow-up to #4309 ("Failed A2A task errors leak into conversation history as regular content"), closed for lack of follow-up rather than a fix. Filing separately since we hit this independently on a newer version and found a few things the original report and its proposed workaround don't cover — happy to have this folded into #4309 instead if that's preferred.
Still present in
google-adk==2.6.2(Python 3.12).convert_a2a_task_to_event()(google/adk/a2a/converters/event_converter.py) never readsa2a_task.status.state; it only extracts message content and returns a plainEvent. Same root cause as reported for 1.1.0 → 1.24.1.This is a round-trip asymmetry, not just a missing check.
convert_event_to_a2a_events()(used when ADK is the A2A server) already does the reverse correctly: ifevent.error_codeis set,_create_error_status_event()builds aTaskStatus(state=TASK_STATE_FAILED, ...). So ADK already models "failed task" as a first-class A2A state on the way out — it just never reads that same state back in on the way in. Two ADK instances talking A2A to each other already lose this, not only ADK-vs-third-party.Three things worth folding into whatever fix lands:
RemoteA2aAgentdispatches each response to either_handle_a2a_responseor_handle_a2a_response_v2depending on whether the task carries ADK's own integration-extension marker. A third-party (non-ADK) A2A server never sets that marker, so every response goes through_handle_a2a_response—_handle_a2a_response_v2never fires against it at all. A workaround/fix that only patches_v2silently does nothing in that setup.streaming=Truethe terminal state arrives as anA2ATaskStatusUpdateEvent(update.status.state), not on theTaskitself (task.status.state) — already flagged in Failed A2A task errors leak into conversation history as regular content #4309, restating because both hooks from point 1 need to handle both shapes.TaskState.failedisn't version-safe. The proposed fix in Failed A2A task errors leak into conversation history as regular content #4309 compares againstTaskState.failed, which only exists on the pydantic-enum shape ofa2a-sdk(0.3.x). On the protobuf shape (1.x, e.g.a2a-sdk==1.1.2, bundled withgoogle-adk==2.6.2),TaskStatehas no.failedattribute at all — the value isTaskState.Value("TASK_STATE_FAILED"). ADK already has a shim for exactly this split:google.adk.a2a._compat.TS_FAILED. Any fix should compare against that instead of a hardcoded enum member.Also: the proposed fix sets
error_messagebut noterror_code. That stops the leak into history, but callers that want to tell "remote task failed" apart from otherRemoteA2aAgenterror paths generally key offerror_code(which exists onEventfor exactly that). Worth setting a stable code (e.g."A2A_TASK_FAILED") alongside the message.Sketch (untested against internals of every version, but shows the shape):
applied in both
_handle_a2a_responseand_handle_a2a_response_v2, checking both theTaskandTaskStatusUpdateEventshapes.Environment:
google-adk2.6.2,a2a-sdk1.1.2, Python 3.12.Happy to close as a duplicate of #4309 if you'd rather track everything there.