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:
| Code | Meaning | When |
|---|---|---|
-32700 | Parse error | Malformed JSON body |
-32600 | Invalid Request | Missing jsonrpc: "2.0" or method field |
-32601 | Method not found | Unknown RPC method |
-32000 | Unauthorized | Missing/invalid Bearer token |
-32001 | Rate limit exceeded | Too 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
| Feature | Status | Notes |
|---|---|---|
| Agent Card discovery | โ Working | GET /.well-known/agent.json |
| Task lifecycle (send/get/cancel) | โ Working | JSON-RPC 2.0 compliant, real task state |
| SSE streaming | โ Working | Progress events for long-running tasks |
| Authentication (API keys) | โ Implemented | Bearer token, configurable keys |
| Request validation | โ Implemented | JSON-RPC 2.0 structure enforced |
| Rate limiting | โ Implemented | Per-key, 100 req/hr default |
| Audit logging | โ Implemented | Every request logged with timestamp, IP, auth status |
| Task persistence | โ Implemented | JSON file on disk โ tasks survive restarts |
| Task not found errors | โ Implemented | Returns -32002 for unknown task IDs |
| A2A Client library | โ Implemented | Discovery, send, poll, stream, cancel |
| Registry Client | โ Implemented | Agent Card validation and info extraction |
| OpenClaw Gateway integration | โ Not connected | Doesn’t route to actual agents yet (beads-hub-98w.9) |
| Agent Card richness | โ ๏ธ Minimal | Missing skills, input schemas, pricing info |
| Real task execution | โ ๏ธ Stub | Tasks are stored but not dispatched to workers |
Next Steps
These are tracked as beads in the A2A Enablement Epic (beads-hub-98w):
- beads-hub-98w.9 โ OpenClaw integration: route A2A tasks to actual agents via Gateway (the big one)
- beads-hub-98w.6 โ DNS-based agent discovery (
.well-known+ DNS TXT records) - 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
- Google A2A Specification
- JSON-RPC 2.0 Specification
- Server-Sent Events (MDN)
- Romanov, “A2A + MCP Integration Analysis” โ pending research bead
- Bead: beads-hub-98w โ A2A Enablement Epic