Skip to content

fix(a2a-client): recognize poll-mode 'queued' envelope (#2967) - #2972

Merged
HongmingWang-Rabbit merged 1 commit into
stagingfrom
fix/a2a-poll-queued-envelope-2967
May 6, 2026
Merged

fix(a2a-client): recognize poll-mode 'queued' envelope (#2967)#2972
HongmingWang-Rabbit merged 1 commit into
stagingfrom
fix/a2a-poll-queued-envelope-2967

Conversation

@HongmingWang-Rabbit

Copy link
Copy Markdown
Contributor

Summary

Fixes the immediate production symptoms from #2967: external molecule-mcp standalone runtimes (which are inherently poll-mode) hit "unexpected response shape" on every A2A send to a poll-mode peer, causing false-failure logs + delegate_task retries that produced duplicate delegations.

The workspace-server's poll-mode short-circuit returns `{status:"queued", delivery_mode:"poll", method:...}` (`a2a_proxy.go:402`). The Python client only knew JSON-RPC `{result, error}` keys → fell through to the catch-all error path.

Adds a third parser branch BETWEEN the existing `{result, error}` cases and the catch-all "unexpected" fallback. Returns a clean queued-success string (no `_A2A_ERROR_PREFIX`) so callers route it through the normal-outcome path.

Scope

This is the hotfix for bare `send_a2a_message`. The broader SSOT typed-A2AResponse refactor (#158-#163 in #2967's plan) is out of scope — it ends the symptom now without preempting that longer arc.

Test plan

  • `test_poll_queued_envelope_returns_success_string` — canonical envelope returns non-error string. Verified discriminating: FAILS on old code (returns `[A2A_ERROR] unexpected response shape...`), PASSES on new.
  • `test_poll_queued_envelope_with_other_method` — parser doesn't hardcode `message/send`. Also FAILS on old code.
  • `test_status_queued_without_poll_mode_still_falls_through` — defensive: both keys required, partial envelope still tagged as error.
  • All 12 existing `TestSendA2AMessage` tests still pass (no regression on JSON-RPC paths).

🤖 Generated with Claude Code

workspace-server's a2a_proxy poll-mode short-circuit returns

    {status: "queued", delivery_mode: "poll", method: <a2a_method>}

when the peer has no URL to dispatch to (poll-mode peers, including
every external molecule-mcp standalone runtime). The bare
send_a2a_message parser only knew about JSON-RPC {result, error}
keys, so this envelope fell through to the "unexpected response shape"
error path. Two production symptoms on the reno-stars tenant traced
to it:

1. File transfer logged as failed when it actually succeeded —
   operator-facing logs showed an A2A_ERROR but the receiving
   workspace did get the chunked file via the agent's fallback path.
2. delegate_task retried after the false failure → peer received
   duplicate delegations → conversation got confused, the second
   peer self-diagnosed in a notify ("⚠️ Peer 二次请求 — 我先不执行").

Add a third branch to the parser, BETWEEN the existing JSON-RPC
{result, error} cases and the catch-all "unexpected" fallback. The
queued envelope is delivery-acknowledged-but-pending-consumption —
not an error — so it returns a clean success string the agent can
render as a normal outcome. The success string includes "queued"
and "poll" so an operator scanning logs sees the routing path
without parsing JSON.

Defensive: the new branch only fires when BOTH status="queued" AND
delivery_mode="poll" are present. A partial envelope (one key
missing) still falls through to the catch-all, so a future server
bug that emits a malformed shape gets surfaced instead of silently
swallowed.

Tests:
- test_poll_queued_envelope_returns_success_string — pins the canonical
  envelope returns a non-error string. Discriminating: verified to FAIL
  on old code (returned [A2A_ERROR] string), PASS on new.
- test_poll_queued_envelope_with_other_method — pins the parser doesn't
  hardcode message/send. Discriminating: also FAILS on old code.
- test_status_queued_without_poll_mode_still_falls_through — pins both
  keys are required (defensive against future server bugs).

12 existing tests in TestSendA2AMessage still pass — no regression.

Scope: hotfix for the bare send_a2a_message path. The full SSOT
typed-A2AResponse refactor (#158-#163, parents under #2967) covers the
broader vocabulary alignment between Go server and Python client. This
PR ends the production symptoms now without preempting that work.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant