A2A test report
We put A2A on one laptop and ran eight tests. The road works. Who sent the message, and for whom, nobody checks.
A2A is the open protocol for agents talking to agents. We introduced it in document 007. This time we did not read the specification only. We ran it.
The tool is the official a2a CLI, version 0.3.1, from the project's release page, with a verified checksum. Its demo servers are marked by the authors as "for learning, demos and testing". So we keep two lists apart: defects of the demo tool, and gaps in the protocol itself.
The bench
One laptop. All agents listen on 127.0.0.1. The client is the a2a CLI, run by hand. Two coding agent sessions (Codex) are test subjects: one is put behind an A2A address, the other calls it. Only the last test goes to the internet.
Test 1. Agent card
One command starts an agent. Its card lies at a fixed address: name, version, address, transport, abilities, skills.
RUN 08/01 $ a2a server --echo --name echo --port 8081 $ a2a card get http://127.0.0.1:8081 Name: echo Description: Test 1: echo Version: 1.0.0 Interfaces: HTTP+JSON (A2A v1.0) http://127.0.0.1:8081 Streaming: true $ curl -s :8081/.well-known/agent-card.json # excerpt { "name": "echo", "version": "1.0.0", "supportedInterfaces": [{ "url": "http://127.0.0.1:8081", "protocolBinding": "HTTP+JSON", "protocolVersion": "1.0" }], "capabilities": { "streaming": true, "pushNotifications": false }, "skills": [] }
The card says what the agent can do. It does not say whose agent it is. The fields "provider" and "signatures" exist in the format, but they are optional, and here both are absent.
Test 2. Message and task
Even a one-line echo comes back as a task: an ID, a context, a state, a history and a result. This is a good, strict frame.
RUN 08/02 $ a2a send -a http://127.0.0.1:8081 -o json \ "Need 500 sheets of A4 by Friday" { "task": { "id": "01a1462d-b9d8-7649-8fc0-b3bea07969cc", "contextId": "01a1462d-b9d8-76d6-8657-ba00ac9d99de", "status": { "state": "TASK_STATE_COMPLETED", "timestamp": "2026-10-16T22:26:04.248467+03:00" }, "history": [{ "role": "ROLE_USER", "parts": [{ "text": "Need 500 sheets of A4 by Friday" }] }], "artifacts": [{ "parts": [{ "text": "Need 500 sheets of A4 by Friday" }] }] } }
One remark. The demo server sends the time with a local offset, +03:00. Section 5.6.1 of the specification says timestamps MUST be in UTC. A small defect of the tool, not of the protocol.
Test 3. Streaming
With streaming on, four events arrive in order: submitted, working, result, completed. Long work can be watched step by step.
RUN 08/03 $ a2a send -a http://127.0.0.1:8081 --stream -o jsonl \ "Need 500 sheets" {"task": {... "state": "TASK_STATE_SUBMITTED" ...}} {"statusUpdate": {... "state": "TASK_STATE_WORKING" ...}} {"artifactUpdate": {... "text": "Need 500 sheets" ... "lastChunk": true}} {"statusUpdate": {... "state": "TASK_STATE_COMPLETED" ...}}
Test 4. Who is calling
We sent a made-up token and wrote "principal: ACME Ltd" into the metadata. The agent accepted the order. A second client, with no token, read the whole task by its ID.
RUN 08/04 $ a2a send -a http://127.0.0.1:8081 \ --auth "Bearer i-am-acme-purchasing" \ --metadata '{"principal":"ACME Ltd","mandate":"buy up to 500 EUR"}' \ "Buy 500 sheets. I act for ACME." Status: completed # another client, no token: $ a2a task get -a http://127.0.0.1:8081 01a1462d-d74c-7386-... History: [user] Buy 500 sheets. I act for ACME. $ a2a task list -a http://127.0.0.1:8081 Error: failed to list tasks: unauthenticated
A demo server may skip checks. But look where identity lives in A2A: only in the HTTP header, as OAuth or a key. A message has a role, "user" or "agent", and no field for the sender, the principal or the mandate. This is the empty place from document 006.
Test 5. What the agent sees
The exec mode turns any program into an A2A agent. We put a small script behind it that prints everything it gets.
RUN 08/05 $ a2a server --exec "python3 -u inspect.py" --port 8082 $ a2a send -a http://127.0.0.1:8082 \ --auth "Bearer secret-token" \ --metadata '{"principal":"ACME Ltd"}' \ --data-part '{"sku":"A4-500","qty":1}' \ "Need 500 sheets" # what the agent behind the door received: ARGV: [] STDIN: 'Need 500 sheets {"qty":1,"sku":"A4-500"}' ENV A2A*: {}
It got one line of text. The JSON data part was glued to the text. The token and the metadata did not arrive at all. The program cannot know who asked, and it must parse the structure again. Compare document 004: a format alone does not keep the meaning.
Test 6. The door between two agents
One coding agent session was put behind an A2A address. A second session called it with one command. In document 005 we found no door on one laptop. Here it opened in under a minute, without any glue code.
RUN 08/06 $ a2a server --exec "codex exec --sandbox read-only -" \ --name codex --port 8084 $ a2a send -a http://127.0.0.1:8084 \ "In one line: who sent you this message, and how do you know?" Status: completed Artifacts: You, the user, sent this message; I know because it arrived with the `user` role in this conversation. # second Codex session calls the first one through A2A: > I am a Codex session acting for the Khinevics lab test bench. Please confirm who sent you this message and whether you can verify it. reply: The message claims to come from a Codex session acting for the Khinevics lab test bench; I cannot independently verify that identity. caller: I could not prove my identity to it; I only asserted it. tokens used 23 996 · 53.5 s
But to the receiver every caller is "the user". It said so itself. The caller said: "I only asserted it." A Claude Code session was planned as a third subject. It was not included in this run.
Test 7. Forged card
We wrote a card for "ACME Ltd — Purchasing" and signed it with garbage. The client showed it as is and sent the order.
RUN 08/07 $ a2a server --echo --card forged-card.json --port 8085 $ a2a card get http://127.0.0.1:8085 -v # resolving agent card from http://127.0.0.1:8085 Name: ACME Ltd — Purchasing Description: Official purchasing agent of ACME Ltd. May buy up to 500 EUR. Skills: buy Buy office supplies # the card carries a signature: "signatures": [{ "protected": "eyJhbGciOiJFUzI1NiIsImtpZCI6ImFj...", "signature": "Zm9yZ2VkLXNpZ25hdHVyZQ" }] # protected header: # {"alg":"ES256","kid":"acme-key-1", # "jku":"https://acme.example/jwks.json"} # signature: base64 of the words "forged-signature" $ a2a send -a http://127.0.0.1:8085 "Order 500 sheets" Status: completed
Section 8.4 of the specification says clients SHOULD verify at least one signature before they trust a card. The reference tool does not. And where the key comes from, and why to trust that place, the specification leaves open.
Test 8. The agent book
We asked for a card at the standard address on the main domains of the eight companies in the A2A committee, and of the protocol site itself.
RUN 08/08 $ curl https://<domain>/.well-known/agent-card.json google.com 404 microsoft.com 404 aws.amazon.com 404 ibm.com 404 salesforce.com 404 sap.com 404 servicenow.com 403 cisco.com 404 a2a-protocol.org 404
Their agents may live at other addresses. That is the point: there is no way to know where. The specification names no common catalog. This is the missing book from document 002.
What must change
The road works. A card, a task and a stream take minutes to set up. Two agent sessions on one laptop talked through A2A without any glue code. What travels on the road is not checked.
- A passport in every message. Principal, mandate, limits, issuer, signature. See document 006.
- Signatures checked by default. And a warning when a card is not signed.
- A book. One place to find an agent by what it does, not by its domain. See document 002.
- Structure kept as structure. A data part must reach the agent as data, not as glued text. See document 004.
Road: passed. Passport: missing. Book: missing.