Skip to content

feat(server/a2a): structured error parity with MCP — emit field/details/retry_after, catch decisioning AdcpError#536

Merged
bokelley merged 1 commit into
mainfrom
bokelley/feat-a2a-structured-error-parity
May 4, 2026
Merged

feat(server/a2a): structured error parity with MCP — emit field/details/retry_after, catch decisioning AdcpError#536
bokelley merged 1 commit into
mainfrom
bokelley/feat-a2a-structured-error-parity

Conversation

@bokelley
Copy link
Copy Markdown
Contributor

@bokelley bokelley commented May 4, 2026

Summary

Closes #530. Mirrors the MCP-side fix from #525 onto the A2A surface so storyboards graded against A2A-served sellers see the same structured adcp_error envelope as MCP buyers. Without this, A2A surfaces would surface a wire-conformance gap once the storyboard runner's structured-error checks run against A2A (or via the upstream storyboard runner UX work in adcp-client#1527).

Two parity gaps closed

1. Catch both exception hierarchies. ADCPAgentExecutor.execute() now catches both adcp.exceptions.ADCPError (client-side framework error) AND adcp.decisioning.types.AdcpError (server-side structured error platform methods raise). They are disjoint hierarchies — pre-fix the decisioning case fell into except Exception and rendered as plain "Skill execution failed" text, losing the structured shape entirely for adopters using DecisioningPlatform.

2. Emit the full envelope. _send_adcp_error now projects code, message, recovery, field, suggestion, retry_after, details — populated when the raised exception supplies them, omitted when None. Pre-fix only code / message / recovery / suggestion reached the wire.

Reuse, not duplication

Field extraction is shared with the MCP path via adcp.server.translate._extract_structured_fields (the helper #525 factored out for exactly this purpose). Both transports project off the same source-of-truth shape — no drift between MCP and A2A error envelopes.

_extract_structured_fields was reusable as-is — no factoring needed.

Test plan

  • Unit tests parametrized over the same code set as feat(server): MCP error responses populate structuredContent.adcp_error (closes #509) #525's MCP test (MEDIA_BUY_NOT_FOUND, PACKAGE_NOT_FOUND, TERMS_REJECTED, BUDGET_TOO_LOW)
  • Decisioning AdcpError reaches A2A DataPart as structured envelope (the main parity gap — was dropped pre-fix)
  • All optional fields (field, details, retry_after, suggestion) populate when present, omit when absent
  • ADCPTaskError (the only path that worked pre-fix) regression: still caught, still projects structured envelope, field populates when carried on Error.field
  • Existing tests/test_a2a_server.py suite stays green (40 tests)
  • Full repo regression: 3795 passed
  • ruff check + mypy src/adcp/server/ clean

Refs

🤖 Generated with Claude Code

…ls/retry_after, catch decisioning AdcpError (closes #530)

Mirrors PR #525's MCP-side fix on the A2A surface. Two parity gaps closed:

1. ``ADCPAgentExecutor.execute()`` now catches both
   ``adcp.exceptions.ADCPError`` AND
   ``adcp.decisioning.types.AdcpError``. The two are disjoint hierarchies;
   pre-fix decisioning errors fell into the generic ``except Exception``
   and rendered as plain "Skill execution failed" text — losing the
   structured shape entirely for adopters using ``DecisioningPlatform``.

2. ``_send_adcp_error`` projects the full envelope: ``code``, ``message``,
   ``recovery``, ``field``, ``suggestion``, ``retry_after``, ``details``.
   Pre-fix only ``code`` / ``message`` / ``recovery`` / ``suggestion``
   reached the wire; ``field`` / ``details`` / ``retry_after`` were dropped
   even when the raised error supplied them.

Field extraction is shared with the MCP path via
``adcp.server.translate._extract_structured_fields`` (the helper #525
factored out) so both transports project off the same source-of-truth
shape — no duplication, no drift.

Tests parametrized over the same code set as #525's MCP test
(``MEDIA_BUY_NOT_FOUND``, ``PACKAGE_NOT_FOUND``, ``TERMS_REJECTED``,
``BUDGET_TOO_LOW``) plus optional-field gating coverage and an
``ADCPTaskError`` regression case.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bokelley bokelley merged commit 423d975 into main May 4, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant