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.
AdCP’s top-level unified status field (returned by @adcp/sdk) continues to use the lowercase shorthand ("completed", "failed", "working", "input-required", "submitted"). That is an AdCP abstraction over status.state — not an A2A wire value.
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 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
| Status | Webhook Type | Data Location | Schema Required? | Returns |
|---|---|---|---|---|
working | TaskStatusUpdateEvent | status.message.parts[] | ✅ Yes (if present) | { status, taskId, message, data? } |
submitted | TaskStatusUpdateEvent | status.message.parts[] | ✅ Yes (if present) | { status, taskId, message, data? } |
input-required | TaskStatusUpdateEvent | status.message.parts[] | ✅ Yes (if present) | { status, taskId, message, data? } |
auth-required (1.0) | TaskStatusUpdateEvent | status.message.parts[] | ✅ Yes (auth challenge) | { status, taskId, message, data } |
completed | Task | .artifacts[] (fallback: status.message.parts[]) | ✅ Required | { status, taskId, message, data } |
failed | Task | .artifacts[] (fallback: status.message.parts[]) | ✅ Required | { status, taskId, message, data } |
rejected (1.0) | Task | .artifacts[] | ✅ Required (adcp_error) | { status, taskId, message, data } |
- 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 status to determine the correct data extraction location. Thestatus referenced here is AdCP’s unified lowercase value (e.g. "completed"); the raw A2A wire value at status.state is TASK_STATE_COMPLETED in 1.0 or completed in v0.3. Normalize before comparing — 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:| Situation | State | Location | Payload |
|---|---|---|---|
| Task executed, subset failed | completed | artifacts[0].parts[] DataPart | { <success_fields>, errors: [...] } |
| Task failed with structured error | failed | artifacts[0].parts[] DataPart | { adcp_error: {...} } |
| Task rejected by policy/validation (1.0) | rejected | artifacts[0].parts[] DataPart | { adcp_error: {...} } |
| System-initiated cancel (timeout, upstream failure) | canceled | artifacts[0].parts[] DataPart | { adcp_error: {...} } |
User-initiated cancel (tasks/cancel) | canceled | status.message.parts[] TextPart | Human-readable text only |
| Protocol/transport failure, no artifact produced | failed | status.message.parts[] TextPart | Human-readable text only |
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.”
rejected vs failed. Use rejected when the server refuses to attempt the task (policy/tier/validation check, before any work is started). Use failed when work started and encountered a fatal error. Both carry adcp_error in the artifact — the state distinguishes when the failure occurred, which drives different retry and UX behavior on the caller side.
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:| A2A Status | Payload Type | Data Location | AdCP Usage |
|---|---|---|---|
completed | Task | .artifacts | Task finished successfully, data in DataPart, optional errors array |
failed | Task | .artifacts (or status.message for text-only) | Fatal error preventing completion, adcp_error when structured |
rejected (1.0) | Task | .artifacts | Policy/validation rejection, adcp_error with rejection reason |
canceled | Task | .artifacts (typically none) | Task canceled by user or system |
input-required | TaskStatusUpdateEvent | status.message.parts | Need user input/approval, data + text explaining what’s needed |
auth-required (1.0) | TaskStatusUpdateEvent | status.message.parts | Authentication challenge during task execution (scheme, URL, scopes) |
working | TaskStatusUpdateEvent | status.message.parts | Processing (< 120s), optional progress data |
submitted | TaskStatusUpdateEvent | status.message.parts | Long-running (hours/days), minimal data, use webhooks/polling |
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