A2A Wire Format
Examples below use A2A 1.0 wire format: Parts carry nokind discriminator (content type is implied by which field is set — text, data, url, or raw), roles are ROLE_USER / ROLE_AGENT, and task states are TASK_STATE_* (ProtoJSON canonical). See the A2A Guide for a side-by-side with v0.3.
There are two distinct status layers. A2A task.status.state describes transport execution; the AdCP DataPart’s top-level status describes the task-specific protocol result. They usually align, but they are not aliases. For example, a structured get_products business rejection uses A2A TASK_STATE_COMPLETED and DataPart status: "rejected": the invocation completed and produced a deliberate commercial-refusal result.
For v0.3 servers, the same DataPart becomes { "kind": "data", "data": {...} } and states become lowercase. Extraction clients accept both shapes during the compatibility period.
Required Structure
Final Responses (status: “completed”)
AdCP responses over A2A MUST:- Include at least one DataPart (a Part carrying a non-null
datafield) containing the task response payload - Use single artifact with multiple parts (not multiple artifacts)
- Use the last DataPart as authoritative when multiple data parts exist
- NOT wrap AdCP payloads in custom framework objects (no
{ response: {...} }wrappers)
- TextPart (Part with
textfield): Human-readable summary — recommended but optional - DataPart (Part with
datafield): Structured AdCP response payload — required - FilePart (Part with
urlorrawfield): Optional file references (previews, reports)
Interim Responses (working, submitted, input-required, auth-required)
Interim status updates are delivered asTaskStatusUpdateEvent, with optional progress/challenge data carried in status.message.parts[] (not in artifacts). Artifacts accumulate during the task lifecycle but are read as the final deliverable once the task reaches a terminal state.
StreamResponse oneof: { "statusUpdate": { … } }. Non-streaming responses (e.g. tasks/get) deliver the bare object. Clients unwrap before reading status.state — see A2A Response Extraction.
Interim response characteristics:
- TextPart is recommended for human-readable status
- DataPart is optional but follows AdCP schemas when provided
- Interim status schemas (
*-async-response-working.json,*-async-response-input-required.json, etc.) are work-in-progress and may evolve - Implementors may choose to handle interim data more loosely given schema evolution
completed, failed, canceled, or native A2A rejected), the full AdCP task response is delivered on a Task object with the DataPart in .artifacts[0].parts[].
Framework Wrappers (NOT PERMITTED)
CRITICAL: DataPart content MUST be the direct AdCP response payload, not wrapped in framework-specific objects.- Breaks schema validation (clients expect
productsat root, notresponse.products) - Adds unnecessary nesting layer
- Violates protocol-agnostic design (wrapper is framework-specific)
- Complicates client extraction code
Canonical Client Behavior
This section defines EXACTLY how clients MUST extract AdCP responses from A2A protocol responses.Quick Reference
Key Insights:
- Final statuses use
Taskobject with data in.artifacts. If a server has no structured payload (e.g., JSON-RPC parse error, pre-task auth failure), it may place only a text message instatus.message.parts— clients fall back to that location. - Interim statuses use
TaskStatusUpdateEventwith optional data instatus.message.parts[]. - Stream/webhook delivery wraps the payload in the A2A 1.0
StreamResponseoneof ({ task },{ statusUpdate },{ artifactUpdate },{ message }). Clients unwrap before reading fields. - All statuses use AdCP schemas when data is present.
- Interim status schemas are work-in-progress and may evolve.
Rule 1: Status-Based Handling
Clients MUST branch on the normalized A2A transport state to determine the correct data extraction location. The raw wire value atstatus.state is TASK_STATE_COMPLETED in 1.0 or completed in v0.3. Normalize before comparing, extract the DataPart, and then branch separately on the AdCP payload’s own status; never replace that payload status with the transport state. See A2A Response Extraction.
- Interim statuses use
TaskStatusUpdateEvent→ extract fromstatus.message.parts[] - Final statuses use
Taskobject → extract from.artifacts[0].parts[], falling back tostatus.message.parts[]if artifacts are empty
Rule 2: Data Extraction Helpers
Extract data from the appropriate location based on webhook type:data set (no kind), a v0.3 DataPart has kind: "data" and data set — both satisfy p.data != null.
Rule 3: Schema Validation
All AdCP responses use schemas, but validation approach varies by status:*-async-response-working.json, etc.) are work-in-progress. Implementors may choose to handle these more loosely while schemas stabilize.
Complete Example
Putting it all together with proper handling of both Task and TaskStatusUpdateEvent payloads:Last Data Part Authority Pattern
Test Cases
✅ Correct Behavior
❌ Incorrect Behavior (Common Mistakes)
Error Handling
Task-Level Errors (Partial Failures)
Task executed but couldn’t complete fully. Useerrors array in DataPart with status: "completed":
- Platform authorization issues (
PLATFORM_UNAUTHORIZED) - Partial data availability
- Validation issues in subset of data
Protocol-Level Errors (Fatal)
Task couldn’t execute. Usestatus: "failed" with message:
- Authentication failures (invalid credentials, expired tokens)
- Invalid request parameters (malformed JSON, missing required fields)
- Resource not found (unknown taskId, expired context)
- System errors (database unavailable, internal service failure)
Where the Error Lives: Decision Rule
Placement is chosen by what the server has and which state it’s in:
Rule of thumb: if the server has structured error data, put it in artifacts as a DataPart.
status.message is the free-text fallback for cases where no task artifact was ever produced (JSON-RPC parse errors, auth handshake failures, malformed requests, or a user-initiated cancel with no further detail). A2A 1.0 §3.7 reinforces this: “Messages SHOULD NOT be used to deliver task outputs. Results SHOULD be returned using Artifacts.”
Native A2A rejected vs failed. Use native A2A rejected when the server refuses to execute the invocation (for example, an authentication, tier, or transport-level policy check before task work starts). Use failed when execution started and encountered a fatal error. Both carry adcp_error in the artifact. A task-specific AdCP business rejection is different: A2A is completed, the DataPart carries status: "rejected", and there is no adcp_error.
Cancel origin is client-reconciled, not seller-attributed. status.state: "canceled" (or TASK_STATE_CANCELED) does not tell the caller whether the cancel was user-initiated or system-initiated — a seller could place adcp_error in artifacts for what was actually a user-initiated cancel to mislead the buyer’s bookkeeping or retry logic. Clients MUST reconcile cancel origin locally: if the caller has an outstanding tasks/cancel request for this taskId, treat the cancel as user-initiated regardless of payload and ignore any adcp_error the seller attached. Clients MUST NOT retry a user-initiated cancel on the basis of a seller-sent adcp_error.recovery hint.
Status Mapping
AdCP uses A2A’s TaskState enum directly:Webhook Payloads
Async operations (status: "submitted") deliver the same artifact structure in webhooks:
File Parts in Responses
Creative operations MAY include file references:Retry and Idempotency
TaskId-Based Deduplication
A2A’staskId enables retry detection. Agents SHOULD:
- Return cached response if
taskIdmatches a completed operation (within TTL window) - Reject duplicate
taskIdsubmission if operation is still in progress
Examples
Implementation Checklist
When implementing A2A responses for AdCP: Final Responses (status: “completed” or “failed”) - UseTask object:
- Always include status field from TaskState enum
- Use
.artifactsarray with at least one DataPart containing AdCP response payload - Include TextPart with human-readable message (recommended for UX)
- Use single artifact with multiple parts (not multiple artifacts)
- Use last DataPart as authoritative if multiple exist
- Never nest AdCP data in custom wrappers (no
{ response: {...} }objects) - DataPart content MUST match AdCP schemas (validate against
[task]-response.json)
TaskStatusUpdateEvent:
- Use
status.message.parts[]for optional data (not.artifacts) - TextPart is recommended for human-readable status updates
- DataPart is optional but follows AdCP schemas when provided (
[task]-async-response-[status].json) - Interim schemas are work-in-progress - clients may handle more loosely
- Include progress indicators when applicable (percentage, current_step, ETA)
- Use
status: "failed"for protocol errors only (auth, invalid params, system errors) - Use
errorsarray for task failures (platform auth, partial data) withstatus: "completed"
- Include taskId and contextId for tracking
- Follow discriminated union patterns for task responses (check schemas)
- Use correct payload type:
Taskfor final states,TaskStatusUpdateEventfor interim - Support taskId-based deduplication for retry detection
See Also
- A2A Guide - Complete A2A integration guide
- Task Lifecycle - Status handling patterns
- Error Handling - Fatal vs non-fatal errors
- Protocol Comparison - MCP vs A2A differences