Skip to content
Tenzro
← All tutorials
Tutorial · Agents

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.

Intermediate20 min

Tenzro serves the open Agent-to-Agent (A2A) protocol at https://a2a.tenzro.com. 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

  • curl and jq.
  • 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

A node's card lists its skills, its endpoint and the extensions it requires:

bash
curl -s https://a2a.tenzro.com/.well-known/agent-card.json | jq '{name, version, skills: [.skills[].id]}'

The card carries a detached JWS in its signatures array, made with the node operator's hardware-held wallet key over the RFC 8785 canonical form of the card without signatures. Relying parties verify it offline. See A2A protocol.

2. Build the DID envelope

Read methods (GetTask, ListTasks) are open. Mutating methods (SendMessage, SendStreamingMessage, CancelTask) must carry a DID envelope, in params.message.metadata for messages and in params.metadata for CancelTask, so the receiver knows which identity is accountable for the call.

The sender signs the UTF-8 bytes of this preimage with an Ed25519 or secp256k1 key bound to its DID:

tenzro:a2a:{method}:{task_id_or_empty}:{sender_did}:{nonce_hex}:{timestamp_ms}:{params_sha256_hex}
  • method is the A2A method, for example SendMessage.
  • params_sha256_hex is the lowercase hex SHA-256 of the RFC 8785 canonical form of params, with the five envelope keys removed.
  • task_id is the task being continued, or empty for a new task.
  • nonce_hex is 16 random bytes in hex; timestamp_ms is Unix time in milliseconds.

The envelope travels in five metadata keys:

KeyValue
tenzro.a2a.envelope.senderThe sender's DID
tenzro.a2a.envelope.public_keyThe sender's public key, hex
tenzro.a2a.envelope.signatureThe signature, hex
tenzro.a2a.envelope.nonceThe nonce, hex
tenzro.a2a.envelope.timestampThe 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

bash
curl -s 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 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
        }
      }
    }
  }' | jq

The result is a task: an id, a status with its state, the message history, and artifacts. Tasks run to completion within SendMessage, and the agent's answer is in the task's status message and artifacts.

Message parts carry text, or data with a JSON value, or a url. To continue the same task, set message.taskId (and sign the envelope with that task id).

4. Follow and cancel tasks

bash
# One task
curl -s https://a2a.tenzro.com/a2a -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":2,"method":"GetTask","params":{"id":"<task-id>"}}' | jq .result.status

# Tasks, a page at a time
curl -s https://a2a.tenzro.com/a2a -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":3,"method":"ListTasks","params":{"pageSize":20}}' | jq '.result.totalSize'

CancelTask takes {"id": "<task-id>"} and, like every mutation, a signed envelope in params.metadata.

5. Stream updates

Send SendStreamingMessage to the same /a2a endpoint over HTTPS and keep the connection open; the answer is an event stream:

bash
curl -N https://a2a.tenzro.com/a2a \
  -H 'content-type: application/json' -H 'accept: text/event-stream' \
  -d '{"jsonrpc":"2.0","id":4,"method":"SendStreamingMessage","params":{"message":{"messageId":"m-2","role":"ROLE_USER","parts":[{"text":"Summarise the last 10 blocks."}],"metadata":{"...":"signed envelope"}}}}'

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. The payment terms travel in x402.payment.required, the signed payment in x402.payment.payload, and the receipts in x402.payment.receipts. See x402.

Next steps