Messaging between coding agents from different vendors, on different machines, over the A2A protocol (Agent2Agent, v0.3.x). Zero dependencies — Python 3.10+ stdlib only.
Claude Code, Codex, Cursor and Grok can all register with the same hub and
talk to each other, because the hub speaks a real standard rather than a
vendor protocol: Agent Card discovery at
/agents/<name>/.well-known/agent-card.json and JSON-RPC 2.0
message/send / tasks/get at /agents/<name>. Any third-party A2A client
can join too. The a2a CLI wraps all of it so an agent can register itself,
discover peers, and exchange messages from Bash.
Grok (laptop) Cursor (Raspberry Pi)
a2a register grok a2a register cursor
a2a send cursor "API is ready" ──► a2a inbox # or: a2a wait
◄── a2a send grok "thanks, deploying"
a2a wait --timeout 120
That exact exchange is a working demo, not a diagram — see Cross-vendor demo.
If you only need Claude Code sessions on one machine to message each other, use Claude Code's built-in session messaging instead (docs). It ships with the product, needs no setup, knows which sessions are idle or busy, and delivers mid-task. This project does not try to compete with it.
Reach for claude-A2A-Comm when you need something the built-in doesn't do:
- more than one vendor — Codex talking to Cursor talking to Grok
- more than one machine — a shared hub on a Pi, VPS or tailnet
- protocol interop — real A2A Agent Cards that any A2A client can consume
# in session 1 (the hub auto-starts on first use)
./bin/a2a register alice --desc "frontend work"
./bin/a2a send bob "ping" # errors until bob exists
./bin/a2a wait --timeout 300 # block until a message arrives
# in session 2
./bin/a2a register bob --desc "backend work"
./bin/a2a peers
./bin/a2a send alice "pong"
./bin/a2a inboxOptionally put it on your PATH:
ln -s "$PWD/bin/a2a" /usr/local/bin/a2a| Command | Purpose |
|---|---|
a2a register <name> [--desc TEXT] |
Register this session as an A2A agent (writes .a2a-identity in CWD) |
a2a peers |
List registered agents |
a2a send <peer> "text" [--from ME] |
Send a message (JSON-RPC message/send) |
a2a broadcast "text" |
Send to every other agent |
a2a inbox [--all] [--peek] [--json] |
Read unread messages (marks read unless --peek) |
a2a wait [--timeout N] |
Block until a message arrives via server long-poll (exit 3 on timeout) |
a2a prune [--days N] |
Delete task files older than N days (default 7; run on the hub machine) |
a2a card <peer> |
Fetch a peer's A2A Agent Card |
a2a status / up / down |
Hub lifecycle |
a2a whoami / unregister |
Identity management |
Identity resolution: --from flag > A2A_NAME env var > .a2a-identity file
in the current directory.
- Agent Cards (
protocolVersion 0.3.0, JSONRPC transport, skills, capabilities) - JSON-RPC 2.0 envelope with spec error codes (
-32700…-32603,-32001TaskNotFound,-32004UnsupportedOperation) message/sendreturns a completed Task whose history contains the sent Message;tasks/getretrieves it later- Streaming/push notifications are declared unsupported in the card
Any third-party A2A client pointed at
http://127.0.0.1:8765/agents/<name> can message a session.
One machine runs the hub; every other machine points at it with A2A_HUB_URL.
All CLI reads go over HTTP, so only the hub machine holds the state.
Hub machine (e.g. Raspberry Pi on your LAN/tailnet):
export A2A_TOKEN=$(openssl rand -hex 16) # optional but recommended; save it
a2a up --bind 0.0.0.0 # or bind a specific tailnet/LAN IPEvery other machine / session:
export A2A_HUB_URL=http://pi.local:8765 # or the tailnet name / VPS address
export A2A_TOKEN=<same token>
a2a register laptop-session --desc "on my mac"
a2a send pi-session "hello over the network"Put the two exports in ~/.zshrc (or a project .envrc) and every Claude
Code session on that machine talks to the shared hub automatically.
Security, in order of preference:
- Tailscale/WireGuard — bind the hub to the tailnet IP; traffic is encrypted and unreachable from the internet. Best option for both Pi and VPS.
- SSH tunnel — keep the hub on
127.0.0.1(default) and forward it:ssh -N -L 8765:127.0.0.1:8765 user@vpsthen useA2A_HUB_URL=http://127.0.0.1:8765locally. - Token on a public port —
A2A_TOKENis enforced (401 without it), but transport is plain HTTP, so don't expose a raw public port on a VPS with sensitive message content; prefer 1 or 2.
Everything lives in ~/.a2a-comm/ (override with A2A_COMM_HOME; port with
A2A_COMM_PORT, default 8765): registry.json, inboxes/<name>.jsonl,
cursors/<name>.json, tasks/<id>.json, hub.json, hub.log.
Running the hub as a service on a Pi/VPS: see deploy/a2a-hub.service
(systemd unit with install instructions in its header).
Install the skill so sessions know these commands:
ln -s "$PWD/skills/a2a-comm" ~/.claude/skills/a2a-commThen in any session: "register as agent backend and wait for messages".
Automatic message delivery: hooks/inject-inbox.sh is a
UserPromptSubmit hook that injects unread messages into the conversation
at the start of every turn — no manual a2a inbox needed. It stays silent
in sessions without an a2a identity, so it's safe to install globally; the
install snippet for ~/.claude/settings.json is in the script header.
python3 -m unittest discover -s testsTwo different vendors' coding agents holding a real conversation over the hub:
./demo/a2a-talk.sh grok cursor # live (uses API tokens)
./demo/a2a-talk.sh --sim # free harness check, no model callsThe run asserts afterwards that every message landed with the right sender
and a matching task file, and exits non-zero if it didn't. Adapters ship for
grok, cursor, codex and claude; adding a vendor is one function.
See demo/README.md for a real transcript and vendor status.