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.
The AdCP A2A Profile Extension v3 also pins an AdCP status: "submitted" response inside an A2A TASK_STATE_COMPLETED Task. Native A2A TASK_STATE_SUBMITTED is an interim transport state before the AdCP handler returns; it is not the mapping for queued AdCP work.
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)
SendMessageResponse pattern:
- 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)
AdCP Submitted Responses (A2A completed)
When the AdCP handler returnsstatus: "submitted", the handler invocation is finished even though the durable AdCP operation is queued. The profile therefore uses an A2A completed Task and carries the Submitted response in the artifact:
task_id are independent. The AdCP handle MUST remain only in the direct DataPart; do not copy it to artifact.metadata.adcp_task_id. Poll by sending a new profile invocation with skill: "get_task_status" and input.task_id: "adcp-task-9a21", not by polling the completed A2A Task.
Interim A2A Responses (working, native 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 such as native A2A Get Task 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
A schema-valid business outcome, including a partial result with anerrors[]
member, is still a completed A2A invocation. A non-streaming SendMessage
response selects the task branch:
Webhook Payloads
A2A push notifications use aStreamResponse branch. Terminal delivery wraps
the same Task structure used by SendMessageResponse:
statusUpdate, and its server-authored Message carries
its own messageId plus the matching taskId and contextId:
File Parts in Responses
File references may accompany the authoritative DataPart inside the Task artifact. They never replace the typed AdCP response:Retry and Idempotency
A continuation of an input-required A2A Task sends a new Message containing a newmessageId, the existing taskId and contextId, and a complete typed
profile invocation. Ordinary AdCP idempotency rules still apply to the task
input. Native GetTask observes an A2A transport Task; it does not replace
get_task_status for a durable AdCP operation.
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) - Map an AdCP
status: "submitted"response to A2A completed, keeptask_idonly in the DataPart, and direct clients to the AdCPget_task_statustask
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