Migrating to A2A Protocol 1.0 is not a method-renaming exercise. The stable specification changes discovery, interface negotiation, task visibility, stream event discrimination, errors, timestamps, and several core objects. A safe production migration freezes the current v0.3 behavior, introduces explicit dual-version interfaces, upgrades clients behind a compatibility boundary, and proves authorization, replay, cancellation, stream recovery, and downgrade behavior before retiring the old protocol.
What problem does A2A 1.0 solve?
The A2A Protocol 1.0 announcement describes the release as the first stable version of the open agent-to-agent communication standard. It is designed for independent, potentially opaque agent systems. A client discovers a remote agent's declared capabilities, sends a message, and receives either a direct Message for a simple interaction or a Task for longer work. Task progress can be consumed through polling, streaming, or push notifications.
A2A does not replace MCP. MCP commonly connects one agent to tools and context; A2A coordinates communication between agents. A remote A2A server may use MCP internally without exposing its tools or private state to the caller. The tool boundary still needs the authorization, input validation, approval, and audit controls in this MCP server security checklist.
The benefit of 1.0 is therefore more than a stable label. It converts several ambiguous integration choices into testable contracts: versions belong to specific protocol interfaces, operations have standardized names, task listing and caller visibility are explicit, stream event types use a new discriminator pattern, and errors use more structured models. Those improvements also mean that a v0.3 client cannot safely migrate by changing a version string.
Which v0.3 to v1.0 changes are breaking?
The official What's New in A2A v1.0 guide identifies changes across behavior and data structures. At minimum, a migration inventory should cover the following areas.
| Area | Common v0.3 shape | Required v1.0 work |
|---|---|---|
| Operations | Path-like names such as message/send and tasks/get |
Adopt standardized operations such as SendMessage, GetTask, ListTasks, and CancelTask |
| Agent Card | Top-level protocolVersion and separate transport fields |
Use supportedInterfaces[]; each interface declares url, protocolBinding, and protocolVersion |
| Stream events | kind and final fields drive parsing |
Discriminate by members such as taskStatusUpdate and taskArtifactUpdate; use binding-level stream closure |
| Parts | Multiple separate part types | Use the unified Part structure and its explicit members |
| Errors | SDK- or transport-specific conventions | Map failures to the standardized error model and google.rpc.Status / ErrorInfo |
| Task discovery | No standard ListTasks operation |
Add filtered, paginated task listing scoped to the authenticated caller |
| Time | Inconsistent field and serialization assumptions | Parse and emit UTC ISO 8601 values for fields such as createdAt and lastModified |
This table is not a substitute for a schema diff. Pin the protocol binding, SDK version, and extensions used by your system, then scan every request, response, stored event, fixture, and generated client type.
Step 1: Freeze current behavior before upgrading dependencies
Create a black-box baseline for the existing v0.3 deployment:
- the public Agent Card location, cache behavior, and authenticated-card behavior;
- supported message, task, cancellation, streaming, and push-notification operations;
- the conditions under which a request returns a
Messageor aTask; - what happens when the same
messageIdis replayed; - whether one principal can read another tenant's task;
- how streams resume and how clients recognize terminal state;
- how domain failures map to HTTP, JSON-RPC, or gRPC errors.
Capture protocol types and state transitions, not credentials, prompts, user files, or complete task histories. Without a baseline, a missing final artifact after the upgrade could be caused by a protocol change, an SDK regression, proxy buffering, or an old behavior that was never guaranteed.
Step 2: Make version negotiation explicit in the Agent Card
In 1.0, an agent can advertise multiple interfaces and versions. The following is an illustrative structure; generate the final shape from the specification and SDK version you pin:
{
"name": "report-agent",
"description": "Creates a report from approved inputs",
"supportedInterfaces": [
{
"url": "https://agents.example.com/a2a",
"protocolBinding": "JSONRPC",
"protocolVersion": "1.0"
},
{
"url": "https://agents.example.com/a2a-v03",
"protocolBinding": "JSONRPC",
"protocolVersion": "0.3"
}
],
"capabilities": {
"streaming": true
}
}
The client must choose an interface it actually supports. It should not silently fall back after a server rejection if the fallback removes required behavior. A controlled downgrade records the selected version, checks that mandatory capabilities still exist, and fails closed when they do not.
An Agent Card is a declaration, not proof of trust. Bind its URL, content, and optional signature to a trusted discovery channel. A public card must not contain private network addresses, confidential scopes, debug endpoints, or temporary credentials. If card signatures are enabled, follow the specification's JSON Canonicalization Scheme and JWS requirements, including key rotation, expiry, and revocation.
Step 3: Isolate operation and type migration behind an adapter
Centralize version differences in a protocol adapter rather than spreading version branches through business code:
async def send_work(client, request):
if client.protocol_version == "1.0":
return await client.send_message(request)
return await client.send_message_v03(request)
This is architectural pseudocode, not a claim about an exact SDK method name. A production adapter also normalizes:
- request and response types;
- the
Message | Taskresult; - identifiers, timestamps, and pagination tokens;
- states in which cancellation is allowed;
- transport errors into domain errors;
- operation names in metrics and traces.
Avoid a global string replacement of operation names. ListTasks is new, while GetTask has clearer history and caller-visibility rules. A proprietary v0.3 list endpoint is not automatically equivalent to the 1.0 operation.
Step 4: Rewrite the stream event parser
Streaming is where a migration can return HTTP 200 while corrupting client state. Version 1.0 no longer uses the old kind discriminator. A typed client should inspect the explicit event member:
def apply_stream_event(event, state):
if event.task_status_update is not None:
return state.apply_status(event.task_status_update)
if event.task_artifact_update is not None:
return state.apply_artifact(event.task_artifact_update)
raise UnsupportedEvent("unknown A2A stream event")
The example intentionally uses generated-type properties instead of hand-parsing raw JSON keys. Production tests still need to establish:
- whether events for one task retain the required order;
- whether a broken stream resumes by subscription,
GetTaskpolling, or a new message; - whether repeated artifact chunks merge idempotently;
- whether terminal state comes from the task model rather than an EOF assumption;
- whether concurrent subscribers receive consistent ordered events;
- whether closing one client connection accidentally cancels the remote task.
If a browser displays long-running progress, the cursor, recovery, and proxy controls in this FastAPI SSE production guide remain useful. Do not equate a browser's Last-Event-ID with authoritative A2A task state.
Step 5: Bind identity, tasks, and tenant scope
The A2A 1.0 specification requires a server to return only tasks visible to the caller. Filtering ListTasks is insufficient. GetTask, cancellation, subscriptions, artifact downloads, and push-notification configuration must reuse the same authorization predicate.
A server-side task record should preserve at least:
tenant_id, identifying ownership;principal_id, identifying the creator or authorized caller;task_idandcontext_id, preserving protocol relationships;message_id, supporting idempotency and tracing;policy_version, identifying the authorization policy used at creation;protocol_versionandbinding, allowing stored events to be interpreted correctly.
Authentication credentials belong in HTTP headers or the secure channel defined by the binding, not in an A2A message, artifact metadata, or Agent Card. An agent saying "I am the finance agent" is not authorization evidence. The server must decide from a verified identity and server-controlled policy.
Step 6: Define idempotency, timeout, and cancellation semantics
The specification defines naturally idempotent operations and allows SendMessage implementations to use messageId for duplicate detection. It does not provide application-level exactly-once execution. A client timeout may occur after the server creates a task; blindly sending a new message can create duplicate work.
A recoverable send flow should:
- generate a stable
messageIdand reuse it for retries of the same business intent; - atomically store
principal + messageId -> taskId; - return the original task on replay instead of charging or executing again;
- reconcile a known task or context after timeout before resending;
- allow cancellation only from valid states and make repeated cancellation safe;
- deduplicate stream and webhook updates using an event or task revision.
The revision-safe state model in this FastAPI, MongoDB, and Redis publishing guide illustrates the same boundary: a model may plan, but identity, state transitions, and side effects need deterministic enforcement.
Step 7: Prefer a dual-stack rollout to an in-place switch
The official A2A Python SDK implements 1.0 and documents compatibility support for 0.3. Compatibility mode does not guarantee compatibility with every custom extension. Use three deliberate phases.
Phase A: add v1.0 server support
Keep v0.3 behavior stable, introduce a 1.0 endpoint or binding, and advertise both in the Agent Card. Route 1.0 types through an explicit compatibility layer so that old consumers do not receive data they cannot decode.
Phase B: canary clients
Migrate internal test clients first, followed by low-risk callers. Compare success rate, terminal-state distribution, stream recovery, unknown-event counts, and authorization rejection reasons by protocol version. Keep sensitive content out of those metrics.
Phase C: retire v0.3
Remove the old version only after active callers reach zero, the rollback window closes, and durable tasks created under v0.3 remain readable by the new stack. Old tasks may outlive old traffic, so recent request versions are not enough.
What should the migration test matrix cover?
| Test | Passing behavior |
|---|---|
| Discovery and negotiation | A supported version is selected; missing mandatory capabilities fail explicitly |
| Direct response | A simple request returns a stable, correctly typed Message |
| Long task | Create, get, list, cancel, and terminal transitions follow the specification |
| Streaming | Status and artifact events parse correctly; reconnecting does not duplicate side effects |
| Idempotency | Replaying one messageId does not create a second task |
| Tenant isolation | Read, cancel, subscribe, and artifact routes cannot cross caller scope |
| Error mapping | JSON-RPC, REST, and gRPC produce semantically equivalent failures |
| Push notification | Target validation, signature, retry, deduplication, and disable flows work |
| Downgrade | Unsupported versions never silently drop required behavior |
| Observability | Traces correlate message and task IDs without credentials or user content |
These cross-service checks can run on a recurring workflow such as Cronova, but a successful schedule only proves that the tests ran. Explicit assertions must decide whether the compatibility gate passed.
Common migration failures
Upgrading the SDK without migrating stored events
Historical events still contain a v0.3 kind, enum, or field name. The new consumer fails when an old task is opened. Version the stored event envelope and transform it according to the version that wrote it.
Advertising two versions that point to one incompatible endpoint
The declaration and implementation diverge, so negotiation succeeds but behavior fails. Every supportedInterfaces entry needs its own contract test.
Treating stream closure as task success
Networks fail, proxies expire idle connections, and servers restart. Success must be derived from protocol state, not EOF.
Filtering only the task list
An attacker can still enumerate a taskId through detail, cancellation, subscription, or artifact routes. Reuse the same authorization function everywhere and intentionally decide whether unauthorized and absent resources have indistinguishable external behavior.
FAQ
Does A2A 1.0 replace MCP?
No. The official guidance treats them as complementary: MCP connects an agent to tools and context, while A2A connects independent agents. A system can use either or both.
Can a v0.3 client call a v1.0 server directly?
Do not assume so. Version 1.0 changes operations, structures, stream events, and errors. Use an explicit compatibility layer or documented SDK compatibility mode, then verify it with real contract tests.
Must a server implement JSON-RPC, REST, and gRPC?
No. Implement the bindings your users need and your team can operate correctly. If multiple bindings are advertised, functionality, authorization, and error semantics must remain equivalent.
What is the smallest safe migration?
Freeze the v0.3 baseline, add a dual-stack v1.0 endpoint, migrate the Agent Card and generated types, fix stream parsing and task authorization, canary clients with replay and disconnect tests, and remove v0.3 only after durable old tasks remain accessible.
Production migration checklist
- [ ] Pin the A2A specification and SDK versions.
- [ ] Preserve a black-box v0.3 behavior baseline.
- [ ] Contract-test every advertised interface.
- [ ] Migrate operations, Parts, enums, timestamps, and error mappings.
- [ ] Remove dependence on the old stream
kindandfinalfields. - [ ] Reuse one tenant-authorization rule across every task route.
- [ ] Ensure
messageIdreplay cannot duplicate side effects. - [ ] Test disconnects, timeouts, cancellation, and push retries.
- [ ] Prevent downgrade from silently removing required capabilities.
- [ ] Keep credentials, prompts, and artifacts out of logs and traces.
- [ ] Maintain rollback and historical-task compatibility during dual-stack rollout.
The migration is complete only when the same business intent has stable, recoverable, and auditable semantics across discovery, messaging, tasks, streams, errors, and authorization—not when a new client merely receives HTTP 200.