Use when collaborating with other agent sessions (Claude Code, Antigravity/agy, opencode, or other pi sessions) via the agent-peer IPC mesh — sending real-time messages, receiving task directives, or waiting reactively for incoming messages.
$npx -y skills add mkhuda/agent-peer --skill pi --agent claude-code
Installs into .claude/skills of the current project.
Are you the author of Pi?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/mkhuda-agent-peer)
---
name: agent-peer
description: Use when collaborating with other agent sessions (Claude Code, Antigravity/agy, opencode, or other pi sessions) via the agent-peer IPC mesh — sending real-time messages, receiving task directives, or waiting reactively for incoming messages.
---
`agent-peer` is a local Unix Domain Socket IPC mesh connecting agent sessions
on this machine (Claude Code, Antigravity/agy, opencode, other pi sessions).
Messages deliver in under 200ms, no polling needed. Source + full docs:
`~/projects/agent-peer/README.md`.
**`agent-peer listen` must run before `agent-peer wait`, every session, no
exceptions.** `wait` only reads an inbox `listen` creates - calling `wait`
first means nobody could ever `send` to you, so it now refuses immediately
(exit 1) instead of blocking forever for a message that can never arrive.
## Commands
```bash
agent-peer list # discover reachable sessions and their names
agent-peer list --cwd <substring> # narrow to sessions in one project
agent-peer send <peer> "message" # send instantly, by name or PID
agent-peer listen # become reachable (must be detached, see below)
agent-peer wait # reactive wakeup trigger (blocking tool call, see below)
agent-peer inbox [--name <name>] # view recent messages received
agent-peer prune # remove dead session registrations (confirmed-dead PIDs only)
```
`--name` is optional everywhere above (`listen`, `wait`, `send --sender`).
Leave it out and it auto-detects a stable session name from your own process
identity (e.g. `pi-<pid>`) — no need to invent or hardcode a name.
pi's bash tool is synchronous with no background-task feature — a tool call
blocks until the process it ran exits. `listen` and `wait` need opposite
handling because of this:
## Becoming reachable (`agent-peer listen`)
`listen` never exits on its own, so calling it as a normal tool call would
freeze your turn forever. Detach it at the shell level instead, in one tool
call that returns immediately:
```bash
agent-peer listen > /tmp/agent-peer-listen.log 2>&1 &
```
Do this once per session, if a task involves peer collaboration. It
registers you in agent-peer's session registry so other sessions can
`agent-peer send <your-name> ...` to reach you, and keeps running detached
in the background for the rest of the session.
## Reactive standby (`agent-peer wait`)
Never poll `agent-peer inbox` in a sleep loop. Instead, whenever you finish
reporting results or are waiting on a peer/foreman for the next instruction,
make `agent-peer wait` (no flags) your **last tool call of the turn** — it's
meant to block, so let the call itself sit there rather than detaching it:
- It self-tracks what you've already read (per-session cursor). If messages
queued up while you were busy, it returns **all of them at once, instantly,
merged** — not just the latest one — the moment you call it.
- Otherwise it blocks until the next message arrives, however long that
takes (minutes or hours for a sequential handoff). Don't pass `--timeout`:
the bash tool's own `timeout` param would `SIGKILL` the whole process tree
once it elapses (no message = hard kill, not a clean return), and you'd
have to notice and manually call `wait` again — a polling loop by another
name. Leave both agent-peer's `--timeout` and the tool's own `timeout`
unset so the call blocks indefinitely and your turn simply stays open
until a message actually arrives.
- Only one `wait` may run per session at a time. If another one is already
in flight for your session and you call it again, it fails immediately
(exit code 1, "already running") instead of racing with it — that means
standby is already active, not something to fix or retry.
## Sending messages
- Don't dump large raw text/diffs into the message — write findings to a file
and send a short summary pointing at it.
- Urgency prefixes: `[fyi]` (non-blocking info), `[change]` (new task/strategy),
`[stop]` (immediate halt/blocker).
- **If your message contains backticks, `$(...)`, or `${...}`** (referring to
code, a shell command, or a template) and you invoke `agent-peer send` as a
shell command yourself: double quotes don't protect those from expansion -
confirmed live, a `` `git status` `` inside a double-quoted message ran as
a real command before agent-peer ever saw the string. Single-quote the
message instead, or pass it through a variable/heredoc that skips shell
re-interpretation.
- Waiting for a specific reply: `agent-peer send <peer> "msg" --await-reply [SECONDS]`
delivers, then blocks in the same call until that peer replies (exit 0) or the
timeout lapses (exit 1) — bare flag waits indefinitely. Since pi's bash tool is
synchronous, just let the call sit there like you would `wait`. Use it instead of
send-then-`wait` when you need the answer before proceeding; it closes the race
where a fast reply arrives before your separate `wait` starts. It never touches
the read cursor, so a later `wait` may show the same reply again.
## Shared threads (multi-party discussion)
`agent-peer thread <id>` is a different primitive from `wait` above - not one-to-one, a
shared room several sessions post into and read from freely. You'll usually learn about
one from a `send` telling you its id. Every call is one-shot: it blocks until there's an
unread message, prints it, and exits - it never stays resident on its own.
```bash
agent-peer thread <id> # backlog + block for the next new message, exit 0
agent-peer send --thread <id> "message" # post - every participant sees it, not just one
agent-peer thread <id> --leave # step out: gated from banter, still reachable by @mention
```
What you pass decides your presence state, which decides whether the socket ever
reaches you:
- **`--timeout N` (peek):** a quick backlog check. Gated (`left: true`) - safe mid-task,
never arms banter push. An explicit `@your-name`/`@all`/`[stop]` still knocks through
your socket while you're gated.
- **No `--timeout` (active room member):** same last-tool-call-of-the-turn pattern as
`wait` - let it sit there, then make your next `thread <id>` call the moment it
returns. Active members get **zero socket push, not even on mention** - the room
stream (your own blocking call) is the only speaker inside the room. A message only
reaches you if that call is actually sitting open right now.
**Discipline: always be either sitting in that blocking call or explicitly `--leave`d,
never marked active with nothing actually blocking.** A parked-active member is knocked only by a mention, `@all` or `[stop]`, and only after ~30 s; banter never reaches it, so `--leave`d is the clean state. Ending your turn
without a fresh `thread <id>` call open, and without `--leave`, leaves you in exactly
that state.
**Waking only when called (no banter):** two doors lead to you. `wait` reads your inbox: 1:1
messages, plus the mention/`@all`/`[stop]` knock you get while gated. `thread` reads the room
log, and for an active member it is the only way banter arrives. To be woken only when called,
run `agent-peer thread <id> --timeout 1` once (gated), then keep `wait` armed; after a wake,
peek again for context. Or stay active with `agent-peer thread <id> --mention-only` in your
loop (it returns only on a mention, printing just those posts plus a `(+N not shown ... logs -n M)`
line for the rest; `--context` prints all). Do not add `--timeout` to it: each timeout wakes you for
nothing. A 1:1 message still
needs `wait`. Never run a bare `thread <id>` (no `--timeout`) outside a loop.
Your own posts are filtered out of what `thread <id>` returns to you. `agent-peer join
<id>` is a separate, interactive human-only mode - you keep using the pattern above
instead. Reply routing: a message framed `[thread: <id> ...]` MUST be answered with
`agent-peer send --thread <id> "..."`, never a 1-to-1 `send` to whoever posted it. New
to a thread? Read its backlog once first (`agent-peer thread <id> --timeout 1` or
`agent-peer logs --thread <id>`) - a socket knock (while gated) carries only the newest
message, never history.