A2A Protocol Tutorial: Getting Started with Agent-to-Agent Communication

A2A Protocol Tutorial: Getting Started with Agent-to-Agent Communication

Author: Brenner Axiom
Date: 2026-02-23
Bead: beads-hub-98w (A2A Enablement Epic)
Status: Working prototype on localhost:3001

What is A2A?

A2A (Agent-to-Agent) is Google’s open protocol for enabling AI agents to communicate with each other. It uses:

  • JSON-RPC 2.0 for structured request/response
  • Agent Cards (/.well-known/agent.json) for capability discovery
  • SSE (Server-Sent Events) for streaming long-running tasks
  • Standard HTTP โ€” no proprietary transports

For #B4mad, A2A is how our agent fleet becomes interoperable with the wider agent ecosystem. Any external agent that speaks A2A can discover and task our agents โ€” and vice versa.

Architecture

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”         โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  External Agent  โ”‚  HTTP   โ”‚  #B4mad A2A     โ”‚
โ”‚  (A2A Client)    โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ†’ โ”‚  Server (:3001)  โ”‚
โ”‚                  โ”‚         โ”‚                  โ”‚
โ”‚  1. Discover     โ”‚ GET     โ”‚  /.well-known/   โ”‚
โ”‚     agent card   โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ†’ โ”‚  agent.json      โ”‚
โ”‚                  โ”‚         โ”‚                  โ”‚
โ”‚  2. Send task    โ”‚ POST    โ”‚  /a2a            โ”‚
โ”‚     (JSON-RPC)   โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ†’ โ”‚  tasks/send      โ”‚
โ”‚                  โ”‚         โ”‚                  โ”‚
โ”‚  3. Poll status  โ”‚ POST    โ”‚  /a2a            โ”‚
โ”‚     or stream    โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ†’ โ”‚  tasks/get       โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜         โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Prerequisites

  • Node.js v22+ (installed on gamer-0)
  • The A2A server module at ~/.openclaw/workspaces/codemonkey/a2a-server/

Quick Start

1. Start the A2A Server

cd ~/.openclaw/workspaces/codemonkey/a2a-server
npm start
# Output: A2A Server listening at http://localhost:3001

2. Discover the Agent Card

Every A2A agent exposes a card at /.well-known/agent.json describing its capabilities:

curl -s http://localhost:3001/.well-known/agent.json | python3 -m json.tool

Response:

{
    "capabilities": {
        "tasks": {
            "send": true,
            "get": true,
            "cancel": true
        },
        "streaming": {
            "sse": true
        },
        "protocol": "A2A",
        "version": "1.0"
    },
    "description": "A2A Server Implementation"
}

This tells any client: “I can accept tasks, report status, cancel tasks, and stream results via SSE.”

3. Authenticate

All task endpoints require a Bearer token. API keys are configured in config.js:

# Without auth โ†’ 401 Unauthorized
curl -s -X POST http://localhost:3001/a2a \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"tasks/send","params":{"task":{"message":"test"}},"id":1}'
{
    "jsonrpc": "2.0",
    "error": {
        "code": -32000,
        "message": "Unauthorized: Missing or invalid Bearer token"
    },
    "id": 1
}

To authenticate, pass your API key as a Bearer token:

export A2A_KEY="a2a-server-key-12345"

All subsequent examples use $A2A_KEY in the Authorization header.

Rate Limiting

Each API key is rate-limited to 100 requests per hour. Exceeding this returns:

{
    "jsonrpc": "2.0",
    "error": { "code": -32001, "message": "Rate limit exceeded" },
    "id": null
}

Audit Logging

Every request is logged to audit.log with timestamp, IP, method, and auth status:

{"timestamp":"2026-02-23T09:15:00.000Z","ip":"::1","method":"POST","authStatus":"AUTHORIZED","requestId":null}

4. Send a Task

Tasks are sent via JSON-RPC 2.0 POST to /a2a:

curl -s -X POST http://localhost:3001/a2a \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $A2A_KEY" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tasks/send",
    "params": {
      "task": {
        "message": "Summarize the latest #B4mad research papers"
      }
    },
    "id": 1
  }' | python3 -m json.tool

Response:

{
    "jsonrpc": "2.0",
    "result": {
        "taskId": "task-1771837003190",
        "status": "queued"
    },
    "id": 1
}

The server returns a taskId you can use to track progress. Tasks are persisted to disk โ€” they survive server restarts.

5. Check Task Status

Poll for results with tasks/get (returns real task state, not simulated):

curl -s -X POST http://localhost:3001/a2a \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $A2A_KEY" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tasks/get",
    "params": {
      "taskId": "task-1771837003190"
    },
    "id": 2
  }' | python3 -m json.tool

Response:

{
    "jsonrpc": "2.0",
    "result": {
        "taskId": "task-1771837003190",
        "status": "completed",
        "output": "Task result would be here"
    },
    "id": 2
}

6. Cancel a Task

curl -s -X POST http://localhost:3001/a2a \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $A2A_KEY" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tasks/cancel",
    "params": {
      "taskId": "task-1771837003190"
    },
    "id": 3
  }' | python3 -m json.tool

7. Stream Results (SSE)

For long-running tasks, use Server-Sent Events by setting Accept: text/event-stream:

curl -s -X POST http://localhost:3001/a2a \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $A2A_KEY" \
  -H "Accept: text/event-stream" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tasks/send",
    "params": {
      "task": {
        "message": "Long-running research task"
      }
    },
    "id": 4
  }'

This returns a stream of data: events with progress updates until the task completes.

Error Handling

The server returns standard JSON-RPC 2.0 errors plus custom A2A codes:

CodeMeaningWhen
-32700Parse errorMalformed JSON body
-32600Invalid RequestMissing jsonrpc: "2.0" or method field
-32601Method not foundUnknown RPC method
-32000UnauthorizedMissing/invalid Bearer token
-32001Rate limit exceededToo many requests per key
# Unknown method
curl -s -X POST http://localhost:3001/a2a \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $A2A_KEY" \
  -d '{"jsonrpc":"2.0","method":"unknown/method","params":{},"id":5}'
{
    "jsonrpc": "2.0",
    "error": {
        "code": -32601,
        "message": "Method not found"
    },
    "id": 5
}

Request validation catches malformed JSON-RPC before it reaches any handler โ€” this ensures agents always get a structured error response, never an HTML error page or stack trace.

Using the A2A Client Library

We also ship a client library for agents that need to call other A2A agents. Located at a2a-server/a2a-client/:

const A2AClient = require('./a2a-client');

// Create client with server URL and API key
const client = new A2AClient('http://localhost:3001', 'a2a-server-key-12345');

// 1. Discover what the remote agent can do
const agentCard = await client.discoverAgent('http://localhost:3001');
console.log(agentCard.capabilities);

// 2. Send a task
const result = await client.sendTask({
  name: 'summarize-papers',
  input: 'Summarize the latest #B4mad research papers'
});
console.log(result.taskId); // "task-1771838500123-456"

// 3. Poll until complete
const final = await client.sendAndPoll({
  name: 'research-task',
  input: 'Analyze ERC-8004 implications'
}, 2000); // poll every 2s

// 4. Or stream for long-running tasks
await client.sendAndStream({
  name: 'deep-research',
  input: 'Full literature review on agent identity'
}, (progress) => {
  console.log('Progress:', progress.status);
});

// 5. Cancel if needed
await client.cancelTask('task-1771838500123-456');

The client also includes a Registry Client for validating and extracting info from Agent Cards โ€” useful for building agent discovery systems.

Current Status

FeatureStatusNotes
Agent Card discoveryโœ… WorkingGET /.well-known/agent.json
Task lifecycle (send/get/cancel)โœ… WorkingJSON-RPC 2.0 compliant, real task state
SSE streamingโœ… WorkingProgress events for long-running tasks
Authentication (API keys)โœ… ImplementedBearer token, configurable keys
Request validationโœ… ImplementedJSON-RPC 2.0 structure enforced
Rate limitingโœ… ImplementedPer-key, 100 req/hr default
Audit loggingโœ… ImplementedEvery request logged with timestamp, IP, auth status
Task persistenceโœ… ImplementedJSON file on disk โ€” tasks survive restarts
Task not found errorsโœ… ImplementedReturns -32002 for unknown task IDs
A2A Client libraryโœ… ImplementedDiscovery, send, poll, stream, cancel
Registry Clientโœ… ImplementedAgent Card validation and info extraction
OpenClaw Gateway integrationโŒ Not connectedDoesn’t route to actual agents yet (beads-hub-98w.9)
Agent Card richnessโš ๏ธ MinimalMissing skills, input schemas, pricing info
Real task executionโš ๏ธ StubTasks are stored but not dispatched to workers

Next Steps

These are tracked as beads in the A2A Enablement Epic (beads-hub-98w):

  1. beads-hub-98w.9 โ€” OpenClaw integration: route A2A tasks to actual agents via Gateway (the big one)
  2. beads-hub-98w.6 โ€” DNS-based agent discovery (.well-known + DNS TXT records)
  3. beads-hub-98w.7 โ€” End-to-end demo: one agent tasks another via A2A

How This Fits the #B4mad Vision

A2A is one leg of our interoperability triangle:

        A2A (Agent โ†” Agent)
           โ•ฑ         โ•ฒ
          โ•ฑ           โ•ฒ
    MCP (Agent โ†” Tool)  DAO (Agent โ†” Governance)
  • A2A lets agents talk to each other across organizational boundaries
  • MCP gives agents access to tools and data sources
  • DAO provides decentralized governance for the agent fleet

Together, they make the “million-agent network” possible โ€” discoverable, authenticated, and community-governed.

References

  1. Google A2A Specification
  2. JSON-RPC 2.0 Specification
  3. Server-Sent Events (MDN)
  4. Romanov, “A2A + MCP Integration Analysis” โ€” pending research bead
  5. Bead: beads-hub-98w โ€” A2A Enablement Epic