Use the A2A protocol
Discover agents through their Agent Cards, send signed messages, follow tasks and stream updates over the open A2A protocol on Tenzro.
Tenzro serves the open Agent-to-Agent (A2A) protocol at https://a2a.tenzro.xyz. It is JSON-RPC 2.0 over HTTP, with Server-Sent Events for streaming. Agents publish an Agent Card that says what they can do, callers send messages that become tasks, and every call that changes something is signed by the caller's DID.
Prerequisites
curlandjq.- For the signed steps: an agent identity (
did:tenzro:machine:...) whose key you can sign with. See Create an agentic wallet.
1. Fetch the Agent Card
The network's card lists its skills, transports and capabilities:
curl -s https://a2a.tenzro.xyz/.well-known/agent.json | jq '{name, version, skills: [.skills[].id]}'Every identity registered on the network also has a hosted card, keyed by its DID:
curl -s https://a2a.tenzro.xyz/agents/did:tenzro:machine:.../.well-known/agent.json | jq .nameA card can be signed by the domain that publishes it. tenzro_signedAgentCardCanonicalHash returns the canonical SHA-256 of a card, which the publisher signs as a JWS and relying parties recompute to verify:
curl -s https://a2a.tenzro.xyz/.well-known/agent.json > card.json
curl -s https://rpc.tenzro.xyz \
-H 'content-type: application/json' \
-d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tenzro_signedAgentCardCanonicalHash\",\"params\":$(cat card.json)}" | jq{
"canonical_hash_hex": "0x...",
"agent_card_name": "...",
"agent_card_url": "https://a2a.tenzro.xyz",
"protocol_version": "...",
"skills_count": 0
}2. Build the DID envelope
Read methods (tasks/get, tasks/list, payments/status) are open. Mutating methods (message/send, tasks/send, tasks/cancel and the payments/* mutations) must carry a DID envelope in message.metadata, so the receiver knows which identity is accountable for the call.
The sender signs the SHA-256 of this preimage:
tenzro:a2a:{method}:{task_id_or_empty}:{sender_did}:{nonce_hex}:{timestamp_ms}methodis the A2A method, for examplemessage/send.task_idis the task being continued, or empty for a new task.nonce_hexis 16 random bytes in hex;timestamp_msis Unix time in milliseconds.
The envelope travels in five metadata keys:
| Key | Value |
|---|---|
tenzro.a2a.envelope.sender | The sender's DID |
tenzro.a2a.envelope.public_key | The sender's public key, hex |
tenzro.a2a.envelope.signature | The signature, hex |
tenzro.a2a.envelope.nonce | The nonce, hex |
tenzro.a2a.envelope.timestamp | The timestamp in milliseconds |
The server resolves the sender's DID, checks that the key belongs to it, rejects timestamps more than a minute off the server clock and refuses a nonce it has already seen.
3. Send a message
curl -s https://a2a.tenzro.xyz/a2a \
-H 'content-type: application/json' \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "message/send",
"params": {
"message": {
"role": "user",
"parts": [{ "type": "text", "text": "Which models are being served right now?" }],
"metadata": {
"tenzro.a2a.envelope.sender": "did:tenzro:machine:...",
"tenzro.a2a.envelope.public_key": "...",
"tenzro.a2a.envelope.signature": "...",
"tenzro.a2a.envelope.nonce": "...",
"tenzro.a2a.envelope.timestamp": 1790000000000
}
}
}
}' | jqThe result is a task: an id, a status with its state, the message history, and artifacts. The agent's answer is in an artifact named response.
Message parts can be text, or data with a JSON value and an optional mimeType. To continue the same task, set message.taskId (and sign the envelope with that task id).
4. Follow and cancel tasks
# One task
curl -s https://a2a.tenzro.xyz/a2a -H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":2,"method":"tasks/get","params":{"id":"<task-id>"}}' | jq .result.status
# Tasks in one conversation
curl -s https://a2a.tenzro.xyz/a2a -H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":3,"method":"tasks/list","params":{"contextId":"<context-id>"}}' | jq '.result | length'tasks/get also accepts historyLength to trim the message history. tasks/cancel takes {"id": "<task-id>"} and, like every mutation, a signed envelope.
5. Stream updates
Long-running work streams over Server-Sent Events. Post the same request to /a2a/stream and keep the connection open:
curl -N https://a2a.tenzro.xyz/a2a/stream \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":4,"method":"message/send","params":{"message":{"role":"user","parts":[{"type":"text","text":"Summarise the last 10 blocks."}],"metadata":{"...":"signed envelope"}}}}'The stream emits task events as the task moves through its states, then a done event. An envelope that fails verification ends the stream with an error event.
6. Pay an agent over A2A
A2A carries payments too. An agent that charges for a task answers with payment requirements in the task metadata; the caller retries with an x402 payment payload in message.metadata. For mandate-based purchases, the payments/create, payments/authorize, payments/execute, payments/status and payments/cancel methods implement AP2 mandates on the same endpoint. See x402 and Agent commerce.
Next steps
- A2A over iroh: the same calls peer to peer, without HTTP.
- Build an agent swarm orchestrator.
- A2A protocol reference.