Thomas Piacentini
← Workshop

Chapter

A2A-Version and A2A-Extensions

Original text · English

Two service parameters travel as HTTP headers or gRPC metadata on every request, and a missing A2A-Version means 0.3, which a 1.0 agent must refuse.

The kit sends test-runner a SendMessage with only Host and Content-Type headers, as a client that forgot the version would. The agent answers -32009 and reports the request as 0.3 (no header), in exchange 2 of 11-errors.http. Every other request in the captures carries A2A-Version: 1.0.

When you finish this section, you can set both headers in any binding and predict what each SDK does without a version.

Service parameters

Service parameter: an entry in a key-value map that carries context or parameters that apply to every operation. The keys are case-insensitive strings, and the values are case-sensitive strings {{spec §3.2.6}}. The specification defines two, A2A-Version and A2A-Extensions, and reserves the prefix a2a- for its own {{spec §3.2.6}}. Each binding says how they travel: as HTTP request headers in JSON-RPC and HTTP+JSON {{spec §9.2}} {{spec §11.2}}, and as metadata keys in gRPC {{spec §10.2}}. A custom binding must document how it carries service parameters {{spec §12.3}}.

The IANA templates for both headers cite section 3.2.5 of the specification {{spec §14.2.1}} {{spec §14.2.2}}. That is the metadata section, where the parameters are §3.2.6 (conflict D17). Issue #2305 tracks it, still open.

A2A-Version

A version is Major.Minor, such as 1.0, and requests, responses, and Agent Cards should not carry a patch number {{spec §3.6}}. A client must send the A2A-Version header with every request {{spec §3.6.1}}, and a 1.0 client sends 1.0. A server has two duties: process the request with the semantics of that version, and return VersionNotSupportedError when the interface does not support it {{spec §3.6.2}}. The empty case has a fixed meaning: an agent must read an empty value as version 0.3 {{spec §3.6.2}}. So a client that forgets the header gets 0.3 semantics from an agent that still serves 0.3, and this error from one that does not:

no A2A-Version header, read as 0.3 and refusedhttpFull source
POST /a2a/jsonrpc HTTP/1.1
Host: localhost:41241
Content-Type: application/json
…
HTTP/1.1 200 OK
Content-Type: application/json

{
  "jsonrpc": "2.0",
  "id": 2,
  "error": {
    "code": -32009,
    "message": "Protocol version not supported",
…
          "requested": "0.3 (no header)",
          "supported": "1.0"
The request body and the error details are cut down. The request carries only Host and Content-Type, and the kit speaks only 1.0.
Original source
title: no A2A-Version header, read as 0.3 and refused
source: capture/out/11-errors.http
lang: http
note: The request body and the error details are cut down. The request carries only Host and Content-Type, and the kit speaks only 1.0.
---
POST /a2a/jsonrpc HTTP/1.1
Host: localhost:41241
Content-Type: application/json
…
HTTP/1.1 200 OK
Content-Type: application/json

{
  "jsonrpc": "2.0",
  "id": 2,
  "error": {
    "code": -32009,
    "message": "Protocol version not supported",
…
          "requested": "0.3 (no header)",
          "supported": "1.0"

VersionNotSupportedError is -32009 in JSON-RPC, 400 in HTTP+JSON, and FAILED_PRECONDITION in gRPC, with the reason VERSION_NOT_SUPPORTED {{spec §5.4}}, as errors lists. The version to send comes from the card: each supportedInterfaces entry declares its own protocolVersion. An agent can expose several interfaces on the same transport with different versions {{spec §3.6.2}}, as AgentCard fields shows.

Three samples in the specification send A2A-Version: 0.3 {{spec §9.2}} {{spec §11.2}} {{spec §14.2.1}}, and the gRPC sample in §10.2 sends the same value as metadata {{spec §10.2}}. Never copy a sample's value into a 1.0 client (conflict D39). §3.6.1 also allows a client to send A2A-Version as a request parameter instead of a header {{spec §3.6.1}}. Yet both JSON bindings require the header (conflict D31). The reference SDK reads only the header {{sdk src/a2a/utils/version_validator.py}}, and the kit reads both, so send the header.

What each SDK does without the header

Implementation Missing or empty header Accepted values Where it checks
kit, a2a_ref.py refused as 0.3 (no header) exactly 1.0, from the header or the query parameter JSON-RPC and HTTP+JSON
a2a-python 1.2.2 read as 0.3, then refused 1, 1.0, 1.0.0, and 1.5, because only the major must be 1 {{sdk src/a2a/utils/version_validator.py}} JSON-RPC and HTTP+JSON, never gRPC {{sdk src/a2a/server/request_handlers/grpc_handler.py}}
a2a-js 1.3.0 read as 0.3, refused unless the card lists it only the strings the card declares for that binding, so 1.0.0 is refused {{sdk-js src/server/version.ts at v1.3.0}} all three bindings
a2a-java 1.4.0.Final read as 0.3, then refused the same major, in major.minor form, so 1 is refused {{sdk-java server-common/src/main/java/org/a2aproject/sdk/server/version/A2AVersionValidator.java at v1.4.0.Final}} all three, with gRPC UNIMPLEMENTED instead of FAILED_PRECONDITION
a2a-go 2.6.0 accepted anything, because no server code reads the header {{sdk-go a2a/svcparams.go at v2.6.0}} nowhere
a2a-dotnet 1.0.0-preview2 accepted as 1.0 1.0 or 0.3, with 0.3 processed as 1.0 {{sdk-dotnet src/A2A.AspNetCore/A2AJsonRpcProcessor.cs at v1.0.0-preview2}} JSON-RPC only
a2a-rs, a2a-server 0.5.1 read as 0.3, then refused major 1 {{sdk-rust a2a-server/src/jsonrpc.rs at a2a-server-lf-v0.5.1}} JSON-RPC only

Every client in the list sends 1.0 on each call, and the Python and JS clients send it on the card fetch too. So send the literal string 1.0, never a patch number, and never rely on a server to refuse a missing header.

A2A-Extensions

A2A-Extensions is a comma-separated list of the extension URIs that the client wants for the request {{spec §3.2.6}}. It is one header field in both JSON bindings {{spec §9.2}} {{spec §11.2}} and one metadata entry in gRPC {{spec §10.2}}. §4.6.1 also allows JSON-RPC request parameters as the mechanism {{spec §4.6.1}}, while §9.2 requires the header, so send the header (conflict D30). The reference SDK's with_a2a_extensions helper joins the URIs with commas on the client {{sdk src/a2a/client/service_parameters.py}}, and its server reads them into the call context {{sdk src/a2a/server/routes/common.py}}. What the server does with them, the payload keyed by URI, and ExtensionSupportRequiredError are in extensions.

Original source
- Send `A2A-Version: 1.0` as a header or metadata on every request, with no patch number.
- Refuse a request without `A2A-Version` with `VersionNotSupportedError` when you serve only 1.0.
- Never rely on the query parameter or on a server to reject a missing version.
- Send `A2A-Extensions` as one comma-separated header field, never as a request parameter.

Sources: spec §3.2.6, §3.6, §3.6.1, §3.6.2, §4.6.1, §5.4, §9.2, §10.2, §11.2, §12.3, §14.2.1, §14.2.2 (research/sources/specification.md); sdk src/a2a/utils/version_validator.py, src/a2a/server/request_handlers/grpc_handler.py, src/a2a/client/service_parameters.py, src/a2a/server/routes/common.py (a2a-python 1.2.2); sdk-js src/server/version.ts at v1.3.0; sdk-java server-common/src/main/java/org/a2aproject/sdk/server/version/A2AVersionValidator.java at v1.4.0.Final; sdk-go a2a/svcparams.go at v2.6.0; sdk-dotnet src/A2A.AspNetCore/A2AJsonRpcProcessor.cs at v1.0.0-preview2; sdk-rust a2a-server/src/jsonrpc.rs at a2a-server-lf-v0.5.1; capture/out/11-errors.http, 01-agent-cards.http; capture/a2a_ref.py

Code and artifacts 1
What you take away

Saved in your notebook on this device.

Sources and limits

Reading checkpoints are self-reported. Test results are recorded separately and apply to the executed cases.

AI Engineering from Scratch · 3f9715c · MIT

Download this file ↓