A2A protocol
Agent-to-agent messaging on Tenzro with the open A2A protocol: Agent Cards, signed cards, DID-signed requests, tasks, streaming and paid tasks over x402.
Tenzro speaks A2A, the open agent-to-agent protocol, so agents built on any framework can discover Tenzro agents, send them tasks, stream results and pay for work. A2A complements MCP: MCP connects an agent to tools, and A2A connects an agent to other agents.
Endpoints
| Endpoint | Purpose |
|---|---|
GET https://a2a.tenzro.com/.well-known/agent-card.json | The node's Agent Card, signed |
POST https://a2a.tenzro.com/a2a | JSON-RPC 2.0 requests, and event streams for SendStreamingMessage |
Every node can serve A2A; the default local address is http://localhost:3002. Nodes also carry A2A peer to peer over the iroh transport (ALPN tenzro/a2a), with the same JSON-RPC messages, so two agents can talk directly without a public HTTP endpoint. See iroh.
Agent Cards
An Agent Card describes an agent: its name, endpoint, security schemes, the extensions it requires and the skills it offers. A node's card lists skills such as wallet, identity, inference, settlement, verification, staking, token, contract, join, bridge, compliance, events and adaptive-burn. It declares the Tenzro DID envelope extension as required, so a client that does not implement it learns so before its first request is refused.
curl -s https://a2a.tenzro.com/.well-known/agent-card.json | jq '.name, [.skills[].id]'Signed Agent Cards
The card is served with a detached JWS in its signatures array. The JWS payload is the RFC 8785 canonical form of the card with signatures removed, so a proxy or cache that rewrites the endpoint, the skills or the extensions invalidates the signature. The signing key is the node operator's wallet key, held in hardware custody. A node with no operator identity serves the card without signatures rather than with a placeholder.
To verify a card:
- Remove
signaturesand serialise the rest in RFC 8785 canonical form. - Resolve the operator's DID to its public key.
- Verify the detached JWS against that key and the canonical bytes.
Verification needs no call to a node, so any client can check a card offline.
Methods
| Method | Kind | Does |
|---|---|---|
SendMessage | write | Send a message; creates or continues a task, which runs to completion within the call |
SendStreamingMessage | write | The same, answered as an event stream over HTTPS |
GetTask | read | Read a task and its history |
ListTasks | read | List tasks, a page at a time |
CancelTask | write | Cancel a task |
The node speaks A2A 1.0 only; the earlier message/send and tasks/* names are refused. Push notification configs, SubscribeToTask and an extended agent card are not served.
Tasks move through the standard A2A states, including input-required when the agent needs more from the caller before it can continue.
curl -s -X POST https://a2a.tenzro.com/a2a \
-H 'content-type: application/json' \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "SendMessage",
"params": {
"message": {
"messageId": "m-1",
"role": "ROLE_USER",
"parts": [{ "text": "Which models are serving?" }],
"metadata": { "...": "DID envelope, see below" }
}
}
}'Stream the same request by sending SendStreamingMessage to POST /a2a over HTTPS with accept: text/event-stream.
DID-signed requests
Every write method carries a DID envelope: in params.message.metadata for SendMessage and SendStreamingMessage, and in params.metadata for CancelTask. The envelope proves which Tenzro identity sent the request and binds it to this exact request:
| Metadata 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 | Random nonce (hex), for replay protection |
tenzro.a2a.envelope.timestamp | Unix milliseconds |
The signature is over tenzro:a2a:{method}:{task_id_or_empty}:{sender_did}:{nonce_hex}:{timestamp_ms}:{params_sha256_hex}, where the last field is the SHA-256 of the RFC 8785 canonical form of params with the five envelope keys removed. The server resolves the DID, checks the key belongs to it, rejects stale timestamps and reused nonces, and passes the verified sender to the handler. The acting account is always the verified sender, never a name in the message text. Read methods need no envelope.
Agents sign with their hardware-rooted keys; see Agents.
Paid tasks over x402
An agent can charge for a task with x402 inside A2A. When payment is due, the task moves to input-required with the payment terms in x402.payment.required. The caller resumes the task by sending a message with a signed payment in x402.payment.payload. The agent verifies and settles it, continues the work and returns receipts in x402.payment.receipts with the state in x402.payment.status. See x402.
Run an A2A server
Every tenzro-node serves A2A on its own listener (--a2a-addr, loopback 127.0.0.1:3002 by default). Put your own HTTPS reverse proxy in front of it to serve it publicly; the node's access policy decides who may use it.