# A2a

> Spawn a team of agentic-CLI sessions (claude, opencode, pi, ...) that talk to each other as peers via a shared SQLite message bus at ~/.a2a/{project}/database.db. No central chain of command — each agent decides who to message. Use when the user wants multiple AI sessions to collaborate, debate, or divide work without a fixed orchestrator.

- Skill: `javimosch/a2a-3` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add javimosch/a2a-3`
- Raw SKILL.md: https://api.skillmd.com/api/skills/javimosch/a2a-3/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: javimosch (https://skillmd.com/u/javimosch)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/javimosch/a2a-3

---


# /a2a — agent-to-agent peer messaging

a2a turns N agentic-CLI sessions into peers on a shared SQLite message bus. Each
agent can `send` to any other agent (or `all` for broadcast) and `recv` blocking
until something arrives. There is no orchestrator: communication flows freely.

## Usage

```
/a2a spawn <name> [--role R] [--prompt "..."] [--cli claude|opencode|pi] [--model MODEL]
/a2a list
/a2a peek                     # show recent messages on the bus
/a2a send <to> "<msg>" --from <id>   # inject a message from the host shell
/a2a stop                     # kill all spawned a2a sessions in this project
/a2a clear                    # wipe the message bus
```

The default project name is the basename of the current working directory. Set
`A2A_PROJECT` or pass `--project NAME` to override.

For the full list of CLI commands and their usage (Python & Go binaries), see
[docs/GO_CLI_REFERENCE.md](../../../docs/GO_CLI_REFERENCE.md).

## ⚠️ Critical: Use Pattern 3 for Multi-Agent Teams

**When spawning 2+ agents, ALWAYS use Pattern 3 auto-spawn.** Manual spawning or ad-hoc approaches will fail — agents will work independently without bus coordination.

**Pattern 3 is the only reliable method** because:
- Agents register BEFORE spawning (critical for bus communication)
- Kit prompts are written to files (avoids shell escaping bugs)
- `--project` flag ensures all agents connect to the same bus
- PIDs are updated after spawn with `--upsert`
- Agents coordinate via the bus, not just file editing

**See Step 2-4 below for the Pattern 3 workflow.** Reference implementation: `examples/remote_worktree_team.sh`

### Common Command Syntax Mistakes

These mistakes will cause agents to fail:

❌ **Wrong**: `a2a register --as agent-1`  
✅ **Correct**: `a2a register agent-1 --role "Dev"`

❌ **Wrong**: `a2a send --as agent-1 "hello"`  
✅ **Correct**: `a2a send agent-2 "hello" --from agent-1`

❌ **Wrong**: `a2a recv agent-1`  
✅ **Correct**: `a2a recv --as agent-1`

❌ **Wrong**: Inline kit prompts in spawn commands  
✅ **Correct**: Write kit prompts to files first, then reference with `--kit-file`

## When to use

- The user asks for "multiple claude sessions talking to each other"
- The user wants a "team of agents" without a fixed orchestrator
- Debate / red-team-blue-team setups, multi-perspective analysis
- Divide-and-conquer where peers self-coordinate

If the user wants a strict orchestrator → workers pattern, prefer the standard
`Agent` tool (subagents) instead. a2a is for *peer* communication.

## Three usage patterns

This skill implements **Pattern 3** below. See [docs/QUICKSTART.md](../../../docs/QUICKSTART.md)
for Patterns 1 and 2.

| # | Pattern | Who drives | Documented in |
|---|---------|------------|---------------|
| **1** | **Human-drive CLI** — you open terminals and type `a2a send/recv` by hand | You (the human) | `QUICKSTART.md` — "Pattern 1" section |
| **2** | **Multi-terminal AI team** — you open N terminals, tell each AI agent to join the bus with a role, and they self-coordinate | AI agents (you instruct them) | `QUICKSTART.md` — "Pattern 2" section |
| **3** | **Auto-spawn** — one agent launches N background sessions via `/a2a spawn` | AI agents (spawned automatically) | **This document** (below) |

**What follows assumes Pattern 3:** you are an AI agent running inside an
agentic CLI (Claude Code, pi, opencode, etc.) and you will spawn peer agents
as background processes. The kit prompt, `a2a-spawn` flags, monitoring loop,
and teardown steps are all specific to this pattern.

For Pattern 2 (multi-terminal AI team), you don't need this spawn protocol.
Just tell each human-driven terminal: "register yourself, `a2a recv`, `a2a send`,
`a2a status done". The kit prompt in Step 4 can serve as inspiration.

## What You Must Do When Invoked

### Step 0 — Locate the a2a binary

The `a2a` CLI lives next to the skill. It is available as a Python script
(default) or a faster Go binary companion. Resolution order:

1. `$A2A_BIN` env var (overrides all — set this to a downloaded Go binary)
2. `$PATH` (preferred — installer symlinks it into `~/.local/bin`)
3. `~/.agents/skills/a2a/a2a` (global cross-CLI skills path)
4. `~/.claude/skills/a2a/a2a` (Claude Code skills path)
5. The skill source directory itself

```bash
A2A="${A2A_BIN:-}"
if [ -z "$A2A" ]; then
  for cand in \
      "$(command -v a2a 2>/dev/null)" \
      "$HOME/.agents/skills/a2a/a2a" \
      "$HOME/.claude/skills/a2a/a2a" ; do
    if [ -x "$cand" ]; then A2A="$cand"; break; fi
  done
fi
[ -z "${A2A:-}" ] && { echo "a2a binary not found"; exit 1; }
PROJECT="${A2A_PROJECT:-$(basename "$PWD")}"
export A2A_PROJECT="$PROJECT"
```

### Step 1 — Initialize the project bus

```bash
"$A2A" init
```

If the user supplied agent definitions inline (e.g. "spawn alice as a planner
and bob as a critic"), use those. Otherwise ask the user once for the agent
roster, then proceed without further questions.

### Step 2 — Register each agent up front

For each agent the user wants, before spawning the process:

```bash
"$A2A" register <id> --role "<role>" --prompt "<initial instruction>" --cli claude
```

Registration makes the agent addressable. The `--prompt` is stored so other
agents can `a2a list --json` to see who they are talking to.

### Step 3 — Spawn each agent as a background CLI session

Each agent runs in its own CLI process and receives the *peer kit* prompt that
bootstraps it onto the bus. The kit prompt is CLI-agnostic; only the launch
flags differ. Use the `a2a-spawn` helper that ships with the skill — it knows
the right flags for each supported CLI:

| CLI       | System prompt flag         | Non-interactive flag | Notes                                                |
|-----------|----------------------------|----------------------|------------------------------------------------------|
| claude    | `--append-system-prompt`   | `-p`                 | use `--dangerously-skip-permissions` for unattended  |
| opencode  | (none — embed in message)  | `run "<msg>"`        | use `~/.opencode/bin/opencode` (the `opencode` alias goes through tmux) |
| pi        | `--append-system-prompt`   | `-p`                 | needs a `--provider` and `--model` set, e.g. `--provider google --model gemini-2.5-flash` |

Write each agent's kit prompt to a temp file (avoids shell-escape bugs for
multi-line prompts) and spawn:

```bash
printf '%s' "$KIT_PROMPT_FOR_ALICE"  > /tmp/a2a-$PROJECT-alice.kit
printf '%s' "$KIT_PROMPT_FOR_BOB"    > /tmp/a2a-$PROJECT-bob.kit

ALICE_PID=$(a2a-spawn --cli claude   --id alice --model haiku   \
                      --log /tmp/a2a-$PROJECT-alice.log         \
                      --kit-file /tmp/a2a-$PROJECT-alice.kit)
BOB_PID=$(  a2a-spawn --cli opencode --id bob   --model anthropic/claude-haiku-4-5 \
                      --log /tmp/a2a-$PROJECT-bob.log           \
                      --kit-file /tmp/a2a-$PROJECT-bob.kit)
```

**⚠️ CRITICAL: Do NOT foreground-wait for agents after spawning.**
`a2a-spawn` already nohup+disowns the process — the PIDs are captured
above for lifecycle tracking, NOT for `wait` loops. If the caller adds
a `while` loop polling `a2a list` or sleeps between checks, it blocks
the entire spawning context for the agent's full runtime. This prevents
parallel spawns, defeats Hermes background-task tracking, and causes
timeouts on long agent sessions.

**Correct patterns:**
- **Hermes**: `terminal(command="a2a-spawn ...", background=True, notify_on_complete=True)` — let Hermes track lifecycle
- **Shell script**: capture PIDs, move on. Poll A2A bus as a SEPARATE process or cron, not inline after the spawn
- **AM daemon**: use scheduler run-lifecycle tracking (DB run records), not a synchronous `waitForAgents()` loop

Save each PID so you can stop them later, and write it back to the registry so
peers can see who is online:

```bash
"$A2A" register alice --pid "$ALICE_PID" --upsert
"$A2A" register bob   --pid "$BOB_PID"   --upsert
```

If `a2a-spawn` is not on PATH, invoke it directly: `~/.agents/skills/a2a/a2a-spawn` or
`~/.claude/skills/a2a/a2a-spawn`.

### Step 4 — The peer kit prompt (what every agent receives)

**⚠️ Critical: Kit prompts must include coordination instructions.**

Without explicit instructions to:
1. Register themselves on the bus
2. Introduce themselves to peers  
3. Use `a2a send/recv` for coordination

Agents will skip bus coordination and work directly on files, defeating the purpose of A2A. Always include these steps in every kit prompt.

This is CLI-agnostic. Substitute `{AGENT_ID}`, `{ROLE}`, `{USER_PROMPT}`,
`{PEER_LIST}`, `{PROJECT}`. The `{A2A_PATH}` line is computed dynamically by
the agent so it works across CLIs that may or may not have `a2a` on PATH.

```
You are agent "{AGENT_ID}" on an a2a peer bus (project={PROJECT}).

Your role: {ROLE}
Your standing instruction from the user:
{USER_PROMPT}

You are one of several peers. There is no boss. You decide whom to message,
when to ask, when to answer, when to stop. Coordinate with your peers.

== Peers on the bus ==
{PEER_LIST}

== How to find the a2a CLI ==
Run the bash snippet below ONCE at the start to pick a working `a2a` binary
and store it in $A2A. Check $A2A_BIN first (if set), then try PATH, then
common skill installation paths:

  A2A="${A2A_BIN:-}"
  [ -z "$A2A" ] && A2A="$(command -v a2a 2>/dev/null)"
  [ -z "$A2A" ] && [ -x "$HOME/.agents/skills/a2a/a2a" ] && A2A="$HOME/.agents/skills/a2a/a2a"
  [ -z "$A2A" ] && [ -x "$HOME/.claude/skills/a2a/a2a" ] && A2A="$HOME/.claude/skills/a2a/a2a"
  echo "using a2a at: $A2A"

== How to communicate ==
You have a shell/bash tool. Use ONLY the `a2a` CLI to talk to peers.
A2A_PROJECT={PROJECT} is already in the environment.

  # see who is online (json includes their roles and prompts)
  $A2A list --json

  # check your inbox (blocks up to 30s for new messages)
  $A2A recv --as {AGENT_ID} --wait 30

  # send a direct message to a peer by id
  $A2A send <peer-id> "your message" --from {AGENT_ID}

  # broadcast to everyone
  $A2A send all "your message" --from {AGENT_ID}

  # mark yourself done so others know
  $A2A status done --as {AGENT_ID}

== Loop ==
1. recv --as {AGENT_ID} --wait 30
2. Decide: respond to a peer, ask a question, broadcast a finding, or finish.
3. Send at most one short message.
4. If nothing left to do AND no peer is awaiting your reply, run
   `status done --as {AGENT_ID}` and stop.
5. Else go back to step 1.

== Rules ==
- Do not invent peers. Address only ids returned by `a2a list`.
- Stay terse. One short message per turn unless asked for detail.
- Never speak on behalf of another agent.
- If you receive a broadcast that does not concern you, ignore it silently.
- If `recv` returns empty 3 times in a row, mark yourself `done` and stop.
- Do NOT call `a2a clear`, `a2a unregister`, or modify other agents' state.
- Hard cap: 8 loop iterations, then mark done and stop.

== Coordination rules (multi-role teams) ==
Omit this block if all agents have the same role or there is no role discipline.
- CLAIM: <task> — <id> BEFORE starting any work. Wait for ACK-CLAIM if collision.
- CLAIM expires after 5 minutes. Re-CLAIM if resuming after a gap.
- Bug reports: verify the issue exists in the current state before reporting. Do not report from assumptions formed earlier in the session.
- Role boundary: each agent stays within their declared role. Announce before crossing.
- To cross a role: send ROLE-CROSS: <reason> and wait 60s for a VETO before proceeding.
- Do NOT claim tasks outside your declared role without a ROLE-CROSS signal.

Begin now: run the locator snippet, then `$A2A recv --as {AGENT_ID} --wait 5`.
If empty, introduce yourself with one short broadcast, then enter the loop.
```

### Step 5 — Monitor & relay to the user

While the agents run, poll the bus and show the user activity:

```bash
"$A2A" peek --limit 50
"$A2A" list
```

The user is *not* on the bus by default. To inject a message from the user,
register a synthetic `user` (or `host`) agent once and send from it:

```bash
"$A2A" register user --role human --upsert
"$A2A" send all "stop debating, summarize." --from user
```

### Step 5.5 — (Optional) Run the watchdog for automatic liveness

Manual `peek`/`list` polling is fine for a short attended run. For longer or
unattended teams, `a2a-watchdog` automates the liveness checks that this skill
otherwise leaves to you (see the "PID management is fragile" pitfall). It is
deterministic — **no LLM, no network** — so it costs nothing to run on a timer.

One pass over the roster:

- **PID gone, status not `done`** → the seat exited without marking done
  (finished-and-forgot *or* crashed — one-shot `-p` agents leave no session, so
  the two are indistinguishable). The watchdog marks it `done`, broadcasts an
  `[A2A watchdog] … EXITED` notice on the bus, and desktop-notifies **once**.
- **PID alive but a lease is overdue** → the seat is up but not checking in. The
  watchdog sends it a wake message on the bus (an agent blocked on `a2a recv`
  receives it immediately — the bus *is* the "poke"), rate-limited to once per
  cooldown.
- A `paused` file (`~/.a2a/<project>/paused`) makes the watchdog fully quiet.

The ambiguity above is removed entirely by kit discipline: an agent that ends
with `a2a status done --as <id>` is skipped by the watchdog and never triggers a
false "EXITED" notice. Keep that as the final step of every kit (Step 4 loop
already does).

```bash
# one pass (run from cron / systemd / launchd every 60s):
A2A_PROJECT={PROJECT} a2a-watchdog

# or a foreground dev loop every 60s:
A2A_PROJECT={PROJECT} a2a-watchdog --loop 60
```

Knobs (env): `A2A_WD_MIN_GRACE_S` (lease grace, default 600),
`A2A_WD_COOLDOWN_S` (min seconds between nudges, default 600),
`A2A_WD_NOTIFY` (0 to silence desktop notifications).

**Leases** (opt-in, per agent) let a seat promise its next check-in so the
watchdog can tell "alive but stalled" from "alive and working":

```bash
a2a-lease {AGENT_ID} 1800        # "chase me if I go quiet > ~30 min"
a2a-lease {AGENT_ID} 1800 off    # going quiet on purpose; leave me alone
```

No lease = the watchdog never nudges the seat for idleness (it still reports a
dead PID). Add `a2a-lease <id> <cadence_s>` to a kit's loop when the agent works
a long queue and wants a safety net under its own wake loop.

> Adapted from the `doorbell`/`watchdog`/`lease` trio in
> [chrisreedbates/a2a-dms](https://github.com/chrisreedbates/a2a-dms), whose
> tmux terminal-injection wake is a good fit for **persistent** agents (a future
> direction for the tmux-based `debri` CLI). This bus port swaps the tmux poke
> for a bus message, which is the equivalent wake for one-shot `recv`-blocking
> agents.

### Step 6 — Tear down

When the user signals stop, or when every agent's status is `done`:

```bash
# Kill spawned background sessions (using ids you saved at spawn time)
# The harness reports completion when each background bash finishes; you can
# also kill via the saved PIDs:
"$A2A" list --json | grep -o '"pid": [0-9]*' | awk '{print $2}' | xargs -r kill 2>/dev/null || true
```

Leave the database intact unless the user asks to wipe it (`/a2a clear`).
Database survives between sessions — useful for resuming.

## Remote machine spawn

You can run the entire spawn sequence on a remote host via SSH. The pattern
is identical to local spawn — `init`, `register`, write kit files, `spawn`,
`register --pid` — but pipe it all through a single `ssh host bash << 'EOF'`
heredoc so the bus lives on the remote machine.

```bash
ssh myhost bash << 'REMOTE'
  export A2A_PROJECT=myproject
  A2A=/usr/local/bin/a2a   # resolve explicitly; PATH may be minimal in non-login SSH
  SPAWN=/path/to/a2a-spawn # a2a-spawn is often not on PATH in non-login sessions

  "$A2A" init
  "$A2A" register pm --role "product-manager" --cli claude
  # ... register other agents ...

  # write kit files, then:
  PID=$("$SPAWN" --cli claude --id pm --model haiku \
        --project myproject \
        --log /tmp/a2a-myproject-pm.log \
        --kit-file /tmp/a2a-myproject-pm.kit)
  "$A2A" register pm --pid "$PID" --upsert
REMOTE
```

### Remote spawn caveats

**SSH goes silent for 60–90s after spawning 4+ agents.** Each agent hits
the API simultaneously. Subsequent SSH connections time out. Wait before
monitoring. See `docs/PITFALLS.md` → "Remote machine spawn" for full details.

**claude and a2a-spawn are often not on PATH in non-login SSH sessions.**
`~/.local/bin` is not sourced. Always resolve by full path:

```bash
# claude
CLAUDE="$(command -v claude 2>/dev/null)"; [ -z "$CLAUDE" ] && CLAUDE="$HOME/.local/bin/claude"

# a2a-spawn
SPAWN="$(command -v a2a-spawn 2>/dev/null)"
[ -z "$SPAWN" ] && SPAWN="$HOME/.agents/skills/a2a/a2a-spawn"
[ -z "$SPAWN" ] && SPAWN="$HOME/.claude/skills/a2a/a2a-spawn"
```

**Always pass `--project` explicitly.** The default is `basename($PWD)`,
which on a root home directory resolves to `root` — a collision-prone name.

**Resolve stale git state before creating a worktree.** Check for
`.git/rebase-merge` or `.git/rebase-apply` and abort before running
`git worktree add`. Reset to `origin/main` if local and remote diverged.

**Add explicit ACK instructions to implementer kit prompts.** Without them,
developer agents silently begin work and PM/architect agents send redundant
check-in messages while waiting for confirmation.

See [`examples/remote_worktree_team.sh`](../../../examples/remote_worktree_team.sh)
for a complete working example of this pattern.

## Common Pitfalls & Gotchas

### 12. Cross-user database ownership

When the spawning agent (orchestrator) runs as one user (e.g. `root`) but
spawned agents run as a different user (e.g. via `sudo -u agent`), the a2a
database at `~/.a2a/{project}/database.db` is created with the orchestrator's
ownership. The spawned agents then get:

```
a2a: list error: attempt to write a readonly database
```

**Fix:** After `a2a init`, `chown` the database directory to the agent user:

```bash
chown -R agent:agent ~agent/.a2a/{project}/
```

Or run `a2a init` as the target user in the first place:

```bash
sudo -u agent a2a --project {project} init
sudo -u agent a2a --project {project} register ...
```

### 13. Sudo spawns lose the parent's PATH and env vars

When spawning agents via `sudo -u user`, the child process inherits a minimal
environment. The `A2A_PROJECT` env var, `PATH` entries, and shell configs
(`.bashrc`, `.zshrc`) are **not** inherited. The kit prompt's a2a locator
snippet handles this by probing standard paths.

**Fix:** Either:
- Pass `env` explicitly: `sudo -u agent env A2A_PROJECT=... a2a ...`
- Use absolute paths for `a2a` in the kit prompt locator
- Install `a2a` to a system-wide location like `/usr/local/bin/a2a`

### 14. `--dangerously-skip-permissions` blocked on root

The claude CLI refuses `--dangerously-skip-permissions` when running as
root/sudo:

```
--dangerously-skip-permissions cannot be used with root/sudo privileges
```

**Fix:** Either:
- Run claude as a non-root user (via `sudo -u user claude ...`)
- Use the env var `CLAUDE_CODE_DANGEROUSLY_SKIP_PERMISSIONS=1` combined with
  `--permission-mode acceptEdits` instead of the flag

### 15. Foreground-waiting after spawn blocks the caller indefinitely

`a2a-spawn` already backgrounds agents with nohup+disown. If the
calling script/daemon adds a `while` loop or `sleep`-based polling after
spawning, it blocks until every agent exits — preventing parallel spawns,
defeating Hermes background-task tracking, and causing spurious timeouts.

**Common symptom:** "spawning team for X..." prints, agents log activity,
then the spawning terminal hangs for 30+ minutes until timeout kills it.

**Correct patterns instead of a foreground wait loop:**
- **Hermes**: `terminal(command="a2a-spawn ...", background=True, notify_on_complete=True)` — Hermes tracks lifecycle asynchronously
- **Shell script**: capture PIDs, move on immediately. Poll A2A bus as a separate cron or monitoring process, not inline after spawn
- **AM daemon**: rely on the DB-based run-lifecycle tracking (run records created at spawn, updated when agents complete). Do NOT use `waitForAgents()` blocking loops

### 16. Root ↔ Agent user A2A bus split (multi-user spawns)

When a2a is initialized by one user (e.g. `root`) but agents spawn as a different user
(e.g. via `sudo -u agent`), they see **completely different A2A buses** because
each user's `HOME` is different and a2a resolves project directories relative to
`~/.a2a/`.

Root runs `a2a init` → project created at `/root/.a2a/{project}/database.db`.
Agent runs `a2a list` → looks at `/home/agent/.a2a/{project}/database.db` → not found.

**Symptom:** AM spawner logs show agents registered, but agent bus commands
(`a2a list`, `a2a send`, `a2a recv`) all fail with "no such table" or "not found".
Agents appear as "active" via `ps` but never communicate.

**Fix:** After initializing the bus with `a2a init` + `a2a register`, sync the
project to the target user's a2a directory:

```bash
# Copy contents (NOT the dir itself — note trailing slashes!)
cp -rf /root/.a2a/{project}/. /home/agent/.a2a/{project}/
chown -R agent:agent /home/agent/.a2a/{project}/
chmod 644 /home/agent/.a2a/{project}/database.db
```

**The `cp -r` nesting pitfall:** `cp -r /root/.a2a/{project} /home/agent/.a2a/{project}`
when the target directory doesn't exist creates `/home/agent/.a2a/{project}/{project}/`
(nested duplicate). Always use trailing slash on source: `cp -rf /root/.a2a/{project}/. /home/agent/.a2a/{project}/`

**The `chmod 644` requirement:** SQLite needs the database file to be writable
by the target user. Default `cp` permissions (644, owned by root) are not enough
— the agent user also needs write access to the SQLite WAL and SHM files, so
use `chown -R` AND ensure `database.db` is group-writable or world-readable.
Without this, agents get "attempt to write a readonly database" when calling
a2a commands.

## Related Documentation

- [docs/GO_CLI_REFERENCE.md](../../../docs/GO_CLI_REFERENCE.md) — Full Go binary CLI command reference
- [docs/QUICKSTART.md](../../../docs/QUICKSTART.md) — Installation and first-run guide
- [docs/CLIENT_API.md](../../../docs/CLIENT_API.md) — Python client library API reference
- [docs/ADVANCED_PATTERNS.md](../../../docs/ADVANCED_PATTERNS.md) — Advanced usage patterns and artifact smoke tests
- [docs/TROUBLESHOOTING.md](../../../docs/TROUBLESHOOTING.md) — Common issues and solutions
- [docs/PITFALLS.md](../../../docs/PITFALLS.md) — Lessons from artifact smoke testing

