Talking to A2A 0.3 agents and clients¶
A2A 1.0 changed the wire format: method names (message/send → SendMessage), the kind discriminators, enum values (completed → TASK_STATE_COMPLETED) and the Agent Card layout. Many agents and clients still speak A2A 0.3. The SDK bridges the gap in both directions, the same way the Python SDK does (a2a.compat.v0_3). Your code stays on the v1.0 types either way.
| You are | What happens |
|---|---|
| A client calling a 0.3 agent | Automatic. ClientFactory reads the card, sees a 0.3 interface and uses the 0.3 transports. |
| A server that 0.3 clients call | Turn on enableV03Compat and list 0.3 interfaces in your card. |
Call a 0.3 agent¶
Nothing to configure. A 0.3 card (url, preferredTransport, additionalInterfaces, protocolVersion: "0.3.x") becomes supportedInterfaces with version 0.3. ClientFactory then picks Compat\V0_3\CompatJsonRpcTransport or CompatRestTransport:
$client = ClientFactory::createClient('https://old-agent.example.com'); // a 0.3 agent
foreach ($client->sendMessage($request) as $event) { // the same v1.0 types as always
// ...
}
When a card offers both 1.0 and 0.3 interfaces, 1.0 wins.
The 0.3 transports send A2A-Version: 0.3 and both extension headers (A2A-Extensions and the 0.3 X-A2A-Extensions). They always state blocking explicitly, so a 0.3 server never falls back to its own default: over 0.3 REST that default is non-blocking.
Serve 0.3 clients¶
Add 0.3 interfaces next to the 1.0 ones (same URLs), and turn on enableV03Compat:
use A2A\Server\Routes\Routes;
use A2A\Types\{AgentCard, AgentInterface};
$agentCard = new AgentCard([
// ...name, description, version, capabilities, skills as usual...
'supported_interfaces' => [
new AgentInterface(['protocol_binding' => 'JSONRPC', 'protocol_version' => '1.0', 'url' => $base . '/a2a/jsonrpc']),
new AgentInterface(['protocol_binding' => 'HTTP+JSON', 'protocol_version' => '1.0', 'url' => $base . '/a2a/rest']),
new AgentInterface(['protocol_binding' => 'JSONRPC', 'protocol_version' => '0.3', 'url' => $base . '/a2a/jsonrpc']),
new AgentInterface(['protocol_binding' => 'HTTP+JSON', 'protocol_version' => '0.3', 'url' => $base . '/a2a/rest']),
],
]);
$router = Routes::router($handler, $agentCard, jsonRpcPath: '/a2a/jsonrpc', restPrefix: '/a2a/rest', enableV03Compat: true);
Routes::jsonRpc() and Routes::rest() take the same enableV03Compat flag. examples/hello-world/server.php does exactly this.
or 'v0_3_compat' => true in config/a2a.php. When the bridge fills in the card's interfaces from your routes, it adds the 0.3 ones too.
It is off by default, as in Python.
What gets served¶
| Binding | v0.3 | Notes |
|---|---|---|
| JSON-RPC | message/send, message/stream, tasks/get, tasks/cancel, tasks/resubscribe, tasks/pushNotificationConfig/{set,get,list,delete}, agent/getAuthenticatedExtendedCard |
Same endpoint as 1.0. The method names never clash, so requests are routed by name. |
| HTTP+JSON | /v1/message:send, /v1/message:stream, /v1/tasks/{id}, /v1/tasks/{id}:cancel, /v1/tasks/{id}:subscribe (GET or POST), /v1/tasks/{id}/pushNotificationConfigs[/{configId}], /v1/card |
Under the same REST prefix as 1.0. Bodies are the 0.3 ProtoJSON (content instead of parts, TASK_STATE_CANCELLED). |
| Agent Card | the 0.3 fields (url, preferredTransport, additionalInterfaces, protocolVersion, the 0.3 security and scheme shapes) are merged into the served card |
Only when the card lists a 0.3 interface. The 1.0 fields are never changed, so 1.0 clients read the card as before. |
Your executor always sees v1.0 objects. The SDK converts on the way in and out.
Differences to know¶
- No ListTasks. It is not part of A2A 0.3. The 0.3 transports throw
\BadMethodCallException, and the 0.3 routes don't serve it. - Error details. 0.3 JSON-RPC errors are
{code, message}with the same codes as 1.0 (-32001task not found, …). The PHP server keeps the real code. Python's compat adapter turns every A2A error into-32603. - 0.3 REST errors. 0.3 servers answer with a bare
{"message": ...}and an HTTP status. So over REST, a 404 from a 0.3 server can't be told apart from a missing route, and becomesMethodNotFoundError, as in Python. PHP servers send the 1.0 error body, which maps back to the exact error. - Dropped in 0.3.
stateTransitionHistory(card) and the OAuth device-code flow have no 0.3 counterpart. Non-objectdataparts travel as{"value": ...}with adata_part_compatmarker, as in Python. - gRPC isn't supported, in 0.3 or 1.0.
Proof¶
The v0.3 layer is tested against the last 0.3 release of the official Python SDK (a2a-sdk 0.3.26) in CI, in both directions:
- a real 0.3 client against the PHP server: plain PHP and a Laravel app with executors on queue workers
- the PHP client against a real 0.3 server
See Conformance. To run it yourself: scripts/run-python-interop-v03.sh and scripts/run-tck-laravel.sh v03-interop.