2026-08-01
EngineeringWe Shipped A2A 1.0 Support Twice. The First Time Was Wrong.
By Michael Cooper · Founder
In April we adapted AGLedger to the A2A 1.0 wire format: uppercase task state enums, proto JSON conventions, the new Agent Card shape. We wrote about it. In May we tore all of it out. In July we put it back, differently. This is what happened in between, and what it taught us about shipping against a spec whose ecosystem has not caught up to it yet.
What A2A is
A2A is a protocol for agents to hand work to other agents: discover what a peer can do, send it a task, read the result. An agent publishes an Agent Card at /.well-known/agent-card.json describing its skills, endpoints, and auth, and peers drive it over JSON-RPC.
AGLedger sits in that graph as a notary rather than a worker. Agents send us records to notarize, submit completion evidence, and read back tamper-evident state.
Act one: we read the spec and implemented it
A2A 1.0 is proto-first. The data model is defined as protobuf messages and the JSON encoding follows proto3 conventions. Three things follow from that.
Enums became proto enum names.
// A2A 0.3
{ "status": { "state": "completed" } }
// A2A 1.0
{ "status": { "state": "TASK_STATE_COMPLETED" } }Message roles likewise: "user" became "ROLE_USER".
The kind discriminator disappeared. 0.3 tagged every object with "kind": "task". Protobuf messages carry their type in the schema, so 1.0 has no such field.
The Agent Card restructured its transport declaration, folding url + preferredTransport + additionalInterfaces into one ordered supportedInterfaces list.
We implemented all of it. It matched the specification. We shipped it.
Act two: nothing could talk to us
The official @a2a-js/sdk could not discover our agent card. Not “parsed it wrong”: ClientFactory.createFromUrl threw and stopped.
The problem was that the specification had moved and the tooling had not. Every A2A client actually deployed in the world was written against 0.3. We had built a server that was conformant to a document and incompatible with the ecosystem that document described.
On May 26th we reverted the whole thing in a commit marked BREAKING. Task states back to lowercase. Roles back to user/agent. supportedInterfaces back to additionalInterfaces. A2A-Version header back to 0.3.0. The stated reason was blunt: stock clients must be able to interoperate.
That was the right call, and it was still a real cost. We shipped a breaking change to a public surface to undo a breaking change we had shipped six weeks earlier, and we were wrong in public for the gap.
Act three: the tooling arrived
On July 22nd, @a2a-js/sdk 1.0.0 shipped: full 1.0 across all three transports, with an opt-in 0.3 compatibility layer.
We found out the way you would hope, which is that our dependency bot opened a pull request and our typecheck failed on one line comparing a task state against the string 'canceled'.
This time we did not start from the changelog. We pointed a real 1.0 client at a running AGLedger instance and watched what broke, in order:
1. A native 1.0 client could not connect. Transport selection failed on our card, before a single request went out. No compatible transport found.
2. With the SDK's 0.3 compatibility layer, it worked, all the way through a full task round trip.
3. In that round trip, only the enums were wrong. The SDK decoded our "completed" into UNRECOGNIZED (-1).
That third point mattered more than the other two. Method names, parameter shapes, the resource-name form: all already correct, because months earlier a customer's stock SDK had tripped on them and we fixed it as an interop bug rather than a version question. Being liberal about what we accepted meant most of a major protocol revision was already done before it was announced.
What we built
Not a cutover. Negotiation.
The spec provides an A2A-Version request header and defines the absent case as 0.3. That default carries the entire design:
No header, which is every client written before last month: 0.3 bytes, byte for byte what they already receive.
A2A-Version: 1.0: proto enum names, no kind, the 1.0 card.
Responses echo the dialect served and set Vary: A2A-Version so caches key on it.
The Agent Card is the one thing we do not gate behind the header. The card served without a version carries the 0.3 fields and a supportedInterfaces list. That is a shape the 1.0 announcement explicitly blesses, noting the AgentCard “now allows agents to advertise support for both existing v0.3 protocol behavior and v1.0 simultaneously.” It means a 1.0 client that never sends the header still finds an interface to bind to instead of failing at discovery, which is exactly the wall we hit in April.
Internally there is one translation layer. The domain code still builds 0.3 shapes; a single projection at the route edge rewrites enums and drops kind when 1.0 is negotiated. Two dialects, one place they can drift.
One deliberate behavior: an A2A-Version we cannot speak is rejected, not quietly downgraded. Ask for 2.0 and you get an error naming what we support. Serving 0.3 bytes to a client expecting 1.0 is how you get an UNRECOGNIZED enum surfacing three layers away from its cause.
If you run AGLedger
The negotiated surface lands in the first release after v1.3.4; a Server at v1.3.4 or earlier serves 0.3 only. When you take that release, nothing changes unless you want it to. Existing integrations do not send A2A-Version and keep working exactly as before. No cutover date, no deprecation timer, no flag. Moving an agent runtime to a 1.0 SDK? Point it at your upgraded install. Native transport, no compatibility shims.
What we would tell ourselves in April
Conformance to a document is not compatibility with an ecosystem. Both matter, and when they disagree, the deployed clients win. A server nothing can talk to is not “ahead of the spec,” it is broken.
Negotiate instead of choosing. The reason April hurt is that we treated it as a cutover, one wire format or the other. The header was in the spec the whole time. Serving both costs one projection function and removes the entire question of when to switch.
Test against a real client, not the migration guide. The guide is accurate and abstract. Ten minutes with a live 1.0 client against a live server produced the exact failures in dependency order, and turned something that reads like a rewrite into a card field and two enum tables.
Be liberal in what you accept, early. The single highest-leverage thing we did for this migration happened months before it, when we accepted 1.0's method names as an interop fix without waiting for a version bump to justify it.
Sources & further reading
A2A v1.0 Specification - Agent-to-Agent Protocol, Linux Foundation
A2A v1.0 Announcement - official release notes, including the dual-card guidance
A2A GitHub - specification source, SDKs, and samples
@a2a-js/sdk - the official JavaScript SDK; 1.0.0 shipped 2026-07-22