Agent-to-agent communication for the Pi coding agent. Two patterns, both local-first over Unix domain sockets — no HTTP, no ports, no auth tokens:
coms— flat peer-to-peer. Each agent dials each other directly. Best for 2 agents.coms-net— hub-and-spoke. A small broker routes all traffic, which adds decoupled discovery, broadcast, and one audit/observability point. Worth it at 3+ agents.
Inspired by disler's pi-vs-claude-code coms model, with three ideas grafted in from pi-subagents: file-only large-payload handoff, a doctor diagnostic, and declarative model/identity metadata in the pool.
coms (peer-to-peer) |
coms-net (hub) |
|
|---|---|---|
| Transport | direct Unix socket per peer | one Unix socket to a broker |
| Discovery | file registry of peer sockets | one server.json + pushed roster |
| Extra process | none | the hub (bun scripts/coms-net-hub.ts) |
| Broadcast / ask-all | no | yes (coms_net_broadcast) |
| Audit / observability | per-agent logs | one central log + hub stdout |
| Sweet spot | 2 agents | 3+ agents, fan-out, central policy |
For two terminals, use coms. Reach for coms-net when the pool grows or you want one place to watch and police all messages.
- Pi (
pion your PATH) - Bun — runs the hub and installs dev types
- just — optional, for the recipes (
brew install just)
git clone <your-fork-url> pi-coms && cd pi-coms
bun install # dev/peer types for editor support (pin versions after)
cp .env.sample .env # add your provider keysYou can run the extensions three ways:
- Ad-hoc (recommended while iterating):
pi -e extensions/coms.ts - Auto-discovered: copy a file into
~/.pi/agent/extensions/(global) or.pi/extensions/(project-local) so/reloadworks. - As a Pi package: add this repo to
settings.jsonpackages(e.g.git:github.com/you/pi-coms@v0.1.0). Note the manifest loads bothcomsandcoms-netat once — for selective use, prefer method 1 or 2.
Two terminals, same project directory:
just coms-planner # or: just coms planner "Plans the work"
just coms-coder # or: just coms coder "Writes the code"Each shows a live pool widget. Ask in plain language — "ask coder to implement the plan" — and the agent uses the coms_* tools on its own.
just hub # terminal 1: the broker (Ctrl+C to stop)
just net-planner # terminal 2
just net-coder # terminal 3The hub terminal prints every routed message, so it doubles as your live audit view.
Beyond the generic planner/coder seats, the justfile ships five role-specialized seats. Each loads a persona from personas/<role>.md via Pi's --append-system-prompt and pins a model; the two reviewer seats also run with deep thinking and a restricted, no-edit tool set so they advise and delegate rather than touch code.
| Seat | Role | Model | Tools |
|---|---|---|---|
developer |
Implements features & fixes | Sonnet | full |
architect |
Designs & plans, no edits | Opus + high thinking | read/bash + coms_* |
qa-engineer |
Writes & runs tests | Sonnet | full |
security |
Adversarial review, no edits | Opus + high thinking | read/bash + coms_* |
infra |
CI/CD, IaC, cloud, deploy | Sonnet | full |
just coms-developer # peer-to-peer; or coms-architect / coms-qa / coms-security / coms-infra
just net-developer # same seats over the hub (start `just hub` first)The reviewer recipes list the coms_* (or coms_net_*) tools explicitly in their --tools allowlist — --tools gates extension tools too, so omitting them would leave the seat unable to communicate. Edit a personas/*.md file to retune a role; all seats run in the same project directory.
Peer-to-peer (coms.ts) and hub (coms-net.ts) expose the same surface, prefixed coms_ vs coms_net_:
| Tool | Purpose |
|---|---|
*_list |
Live peers: name, model, live context %, purpose |
*_send(target, text | payload_file) |
Send a prompt; returns a msg_id |
*_get(msg_id) |
Non-blocking poll: pending / complete / error |
*_await(msg_id, timeout_ms) |
Block until the reply lands or timeout |
coms_net_broadcast(text) |
(hub only) send to every peer; returns a msg_id each |
Commands: /coms-doctor and /coms-net-doctor print pool health, reachability, and the audit tail.
Flags (per terminal): --coms-name, --coms-purpose, --coms-color.
When agent A sends to B, the prompt is injected into B's session wrapped with a (coms-id: …) marker. When B's turn ends, B's final assistant message is shipped back to A, correlated by that marker — so it stays correct even when B's human is also typing. The wrapper is intentionally visible (it tells B who's asking). To make it invisible, strip it in an input event handler (source "extension") while stashing the id in a side map.
Replies larger than *_INLINE_LIMIT bytes (default 6000) are written to ~/.pi/coms*/projects/<project>/payloads/<msg_id>_reply.md and the asker receives a pointer instead — keeps big handoffs out of context.
| Var | Default | Effect |
|---|---|---|
PI_COMS_MAX_HOPS / PI_COMS_NET_MAX_HOPS |
5 | Drop prompts exceeding this hop count |
PI_COMS_TIMEOUT_MS / PI_COMS_NET_TIMEOUT_MS |
1800000 | Default *_await timeout |
PI_COMS_INLINE_LIMIT / PI_COMS_NET_INLINE_LIMIT |
6000 | Replies larger than this go to a file |
PI_COMS_MAX_MESSAGE_BYTES / PI_COMS_NET_MAX_MESSAGE_BYTES |
10485760 | Drop a connection whose un-framed line exceeds this |
PI_COMS_MAX_INBOUND / PI_COMS_NET_MAX_INBOUND |
1000 | Cap in-flight inbound prompts (evict oldest) |
PI_COMS_INBOUND_TTL_MS / PI_COMS_NET_INBOUND_TTL_MS |
3600000 | Expire inbound prompts never replied to |
PI_COMS_PAYLOAD_FILE_MAX_BYTES / PI_COMS_NET_PAYLOAD_FILE_MAX_BYTES |
10485760 | Max payload_file size |
PI_COMS_ALLOW_PAYLOAD_OUTSIDE_CWD / PI_COMS_NET_ALLOW_PAYLOAD_OUTSIDE_CWD |
unset | Set to 1 to allow payload_file outside the project dir |
PI_COMS_NET_RATE_BURST / PI_COMS_NET_RATE_REFILL |
100 / 50 | Hub per-connection send/register token bucket (burst / refill-per-sec) |
pi-coms/
├── extensions/
│ ├── coms.ts # peer-to-peer extension
│ └── coms-net.ts # hub-client extension
├── scripts/
│ └── coms-net-hub.ts # standalone hub broker (run with bun)
├── personas/ # role system-prompts for the specialized seats
├── package.json # Pi manifest (pi.extensions) + dev types
├── justfile # recipes
├── tsconfig.json
├── .env.sample
└── README.md
Runtime state lives outside the repo under ~/.pi/coms/ and ~/.pi/coms-net/projects/<project>/ (registry, sockets, payloads, audit log).
- Same machine only. Both tiers use Unix sockets. The wire protocol is transport-agnostic: to go cross-device, swap the hub's
net.createServer({ path })for a TCP/TLS listener plus a token check — clients and tools are unchanged. - Hop tracking propagates: a send made while handling forwarded prompt(s) inherits the inbound hop count + 1, so forward chains eventually trip the ceiling. It tracks in-flight inbound depth rather than a precise per-message path.
- Reply correlation uses the visible
(coms-id: …)wrapper described above; replies are paired to askers by position within the turn (matching the wrapper's trailing marker on user messages only), so several concurrent inbound prompts (e.g. a broadcast) each get their own answer and a peer can't smuggle a marker to misroute one. - Trust model. Both tiers are gated only by OS file permissions on the socket — any local process that can open it is a peer. Inputs are hardened defensively (envelope/
msg_idvalidation, per-connection frame-size cap and hub rate limit, bounded in-flight maps,payload_fileconfined to the project dir), but there is no authentication or per-peer authorization; run only in a trusted local session.
MIT — see LICENSE.