What A2A v1.0 changed in the Agent Card

9 min read
A2A v0.xA2A v1.0urlsupportedInterfaces[0].urlpreferredTransportsupportedInterfaces[] orderadditionalInterfacessupportedInterfaces[]protocolVersionsupportedInterfaces[].protocolVersionsupportsAuthenticatedExtendedCardcapabilities.extendedAgentCardprovider.nameprovider.organizationcapabilities.stateTransitionHistoryremoved in v1.0
Four top-level transport fields collapsed into one ordered array. Everything else is a rename or a removal.

A2A v1.0 is not a cosmetic renumbering. The transport section of the Agent Card was restructured, which means a v0.x card does not degrade gracefully — it stops describing anything a v1.0 client can address, while still parsing as perfectly good JSON.

Why this one is not a renumbering

Most of what changed in the A2A specification is a rename you could handle with sed. One change is not: four separate top-level fields that described how to reach an agent were replaced by a single ordered array. That is a structural change, and it is why a v0.x card fails silently rather than loudly. A v1.0 client looks for supportedInterfaces, does not find it, and concludes your agent has no reachable interface.

The change came with the protocol's move from its launch at Google to Linux Foundation governance and multi-vendor development — context that matters mostly because it explains the direction of travel: fewer implicit assumptions, more things stated per-endpoint instead of per-agent.

supportedInterfaces: four fields collapsed into one

In v0.x, reaching an agent meant assembling information from four places:

v0.x
{
  "url": "https://agents.example.com/a2a",
  "preferredTransport": "JSONRPC",
  "additionalInterfaces": [
    { "url": "https://agents.example.com/grpc", "transport": "GRPC" }
  ],
  "protocolVersion": "0.3.0"
}

The primary endpoint lived in one field, its transport in another, alternatives in a third, and the protocol version applied to all of them at once — which was never quite true. v1.0 states everything per endpoint:

v1.0
{
  "supportedInterfaces": [
    { "url": "https://agents.example.com/a2a",
      "protocolBinding": "JSONRPC",
      "protocolVersion": "1.0" },
    { "url": "https://agents.example.com/grpc",
      "protocolBinding": "GRPC",
      "protocolVersion": "1.0" }
  ]
}

Three consequences worth internalising:

  • Order carries meaning. preferredTransport disappeared because position replaced it — the first entry is the preferred interface. Reordering the array is a behavioural change.
  • Every entry needs all three keys. url, protocolBinding and protocolVersion. A partially specified entry is an error, not a fallback.
  • The vocabulary changed too. transport became protocolBinding, with core values JSONRPC, GRPC and HTTP+JSON. Custom bindings should be identified by a URI so they cannot collide with future core values.

The renames

v0.xv1.0Note
supportsAuthenticatedExtendedCardcapabilities.extendedAgentCardMoved inside capabilities, where the other feature flags live
provider.nameprovider.organizationThe value is the organisation, not a contact person — the rename makes that explicit
additionalInterfaces[].transportsupportedInterfaces[].protocolBinding"Binding" because it names the protocol mapping, not the network transport

What disappeared

capabilities.stateTransitionHistory is gone. It was a boolean claiming an agent could report the history of a task's state changes, and it was underspecified enough that clients could not rely on it. A v1.0 validator flags it as not part of the specification.

If your agent really does expose task history, declare it as an extension instead — that is what capabilities.extensions exists for, and an extension carries a URI you can point at documentation:

Modelling a removed capability as an extension
"capabilities": {
  "streaming": true,
  "pushNotifications": true,
  "extensions": [
    {
      "uri": "https://northwind.example.com/a2a/ext/task-history/v1",
      "description": "Exposes the full state-transition history of a task.",
      "required": false
    }
  ]
}

Skill tags became required

In pre-1.0 drafts, tags on a skill were optional and widely skipped. In v1.0 they are required, because skill-level discovery filters on them. This is the change most likely to turn an otherwise clean migration into a failing card, since it affects every skill in every card written before the change.

Before and after, per skill
// v0.x — accepted
{ "id": "reconcile", "name": "Reconcile", "description": "Matches payments." }

// v1.0 — tags are required
{ "id": "reconcile-payment-batch",
  "name": "Reconcile a payment batch",
  "description": "Given a bank statement export, match each line to an open
                  invoice and report exceptions with reasons.",
  "tags": ["finance", "invoices", "reconciliation"] }

The discovery path moved

v1.0 registers /.well-known/agent-card.json as the standard RFC 8615 discovery path. The pre-1.0 path was /.well-known/agent.json, and it is still where every older SDK looks first. The response should carry the media type application/a2a+json.

Serve the canonical path, alias the legacy one
GET /.well-known/agent-card.json   -> 200  application/a2a+json
GET /.well-known/agent.json        -> 308  Location: /.well-known/agent-card.json

A card, before and after

v0.x
{
  "name": "Invoice Reconciliation Agent",
  "description": "Matches payments to invoices.",
  "url": "https://agents.northwind.example.com/a2a",
  "preferredTransport": "JSONRPC",
  "protocolVersion": "0.3.0",
  "supportsAuthenticatedExtendedCard": false,
  "provider": { "name": "Northwind Finance",
                "url": "https://northwind.example.com" },
  "capabilities": { "streaming": true,
                    "stateTransitionHistory": true },
  "defaultInputModes": ["text/plain"],
  "defaultOutputModes": ["application/json"],
  "skills": [
    { "id": "reconcile", "name": "Reconcile",
      "description": "Matches payments." }
  ]
}
v1.0
{
  "name": "Invoice Reconciliation Agent",
  "description": "Matches incoming payments against open invoices, flags
                  partial and duplicate payments, and drafts follow-ups
                  for anything it cannot reconcile.",
  "version": "3.1.0",
  "supportedInterfaces": [
    { "url": "https://agents.northwind.example.com/a2a",
      "protocolBinding": "JSONRPC",
      "protocolVersion": "1.0" }
  ],
  "provider": { "organization": "Northwind Finance",
                "url": "https://northwind.example.com" },
  "capabilities": {
    "streaming": true,
    "pushNotifications": false,
    "extendedAgentCard": false
  },
  "defaultInputModes": ["text/plain", "application/json"],
  "defaultOutputModes": ["application/json"],
  "skills": [
    { "id": "reconcile-payment-batch",
      "name": "Reconcile a payment batch",
      "description": "Given a bank statement export, match each line to an
                      open invoice and report exceptions with reasons.",
      "tags": ["finance", "invoices", "reconciliation"] }
  ]
}

Note what the migration forced beyond the mechanical moves: a real version, a description worth reading, and a skill with tags. That is not incidental — most of the v1.0 changes push the card toward being useful to a stranger rather than merely well-formed.

Migrating without stranding existing clients

If nobody is integrated with you yet, publish the v1.0 card at both paths and move on. If you have live pre-1.0 callers, you have three options, in increasing order of effort:

  1. Cut over. Serve the v1.0 card at both paths on a scheduled date and tell integrators. Honest, simple, and fine when you know who your callers are.
  2. Split by path. Serve the v1.0 card at agent-card.json and keep the old document at agent.json for a deprecation window. Two documents to keep in sync, so put an end date on it.
  3. Serve a transitional card. Include supportedInterfaces and retain the legacy top-level url, so v1.0 clients read the array and pre-1.0 clients read the field.

Migration checklist

  1. Replace url, preferredTransport and additionalInterfaces with one ordered supportedInterfaces[], preferred entry first.
  2. Give every interface entry all three of url, protocolBinding and protocolVersion; rename transport to protocolBinding.
  3. Set top-level version to your agent's build — not the protocol version you just moved.
  4. Move supportsAuthenticatedExtendedCard to capabilities.extendedAgentCard.
  5. Rename provider.name to provider.organization.
  6. Delete capabilities.stateTransitionHistory, or re-model it under capabilities.extensions.
  7. Add non-empty tags to every skill, using the words a searcher would use.
  8. Serve at /.well-known/agent-card.json with content type application/a2a+json, and alias /.well-known/agent.json.
  9. Run the card through the validator, then check the live domain with the well-known URL checker.

The schema reference documents every v1.0 field with its type, requirement level and the mistake most often made with it — useful as the target state while you work through the list above.

Frequently asked questions

Will a v0.x Agent Card still work with v1.0 clients?

Not usefully. A v1.0 client looks for supportedInterfaces to find your endpoint; a v0.x card has a top-level url instead, so the client finds no addressable interface. The card parses as JSON and conveys nothing actionable — which is worse than a hard failure, because it looks fine.

Where did protocolVersion go?

Into each entry of supportedInterfaces. The protocol version is now a property of an endpoint rather than of the whole agent, which is what lets one agent expose a v1.0 JSON-RPC interface and a differently versioned interface alongside it. The top-level version field means your agent build.

Do I have to serve both /.well-known/agent.json and agent-card.json?

You should. agent-card.json is the v1.0 path and the one new clients try; agent.json is where every pre-1.0 SDK looks. Serving the second as an alias or a 308 redirect to the first costs one route and removes an entire category of "why can nothing find my agent" reports.

What happened to stateTransitionHistory?

It was removed from capabilities in v1.0. If your agent genuinely exposes task state history, model it as an extension under capabilities.extensions rather than reintroducing the old boolean — a v1.0 validator flags the field as unrecognised.

Is A2A still a Google project?

It began at Google and is now developed under Linux Foundation governance with multiple vendors participating. Practically, that means the specification is versioned and published independently, which is exactly why pinning to a protocol version in each interface entry matters.

References

  1. A2A protocol specification (a2aproject/A2A)github.com/a2aproject/A2A
  2. A2A: a new era of agent interoperability — Google Developers Blogdevelopers.googleblog.com/en/a2a-a-new-era-of-agent-interoperability/
  3. RFC 8615 — Well-Known Uniform Resource Identifierswww.rfc-editor.org/rfc/rfc8615

Related tool: Agent Card Validator

Next step

Find out which v0.x fields your card still carries.

The validator detects every pre-1.0 field, names its v1.0 replacement, and reports the rest of the card against the current specification.