Role-specialized endpoints join one shared space and coordinate laterally — presence, addressing, and messaging — on a local NATS/JetStream mesh, each participant in its own terminal.
It's configurable, not hardwired: Cotal provides the primitives (addressability, presence, a control plane, data sharing); the topology — who's "planner" vs "reviewer", who delegates to whom — is just how you set it up.
multicast broadcast to a channel · unicast DM one peer · anycast reach any one of a role
Status: the walking skeleton (manual CLI peers), the control plane (manager +
ptyruntime + web console), and the Claude Code adapter run today —cotal start --agent claudespawns a real Claude session that joins the mesh, flips presence from its lifecycle hooks, and wakes on incoming peer messages.
- Join in one command — an endpoint joins a space and appears in presence.
- Presence & discovery — see who's present, their role, and live state
(
idle/waiting/working/offline). - Addressability — all three delivery modes: multicast (broadcast to a channel), unicast (DM one peer), and anycast (reach any one of a role).
- Live state — watch a peer flip to
working/waitingand back. - Observability — a read-only
watchendpoint tails everything on the mesh. - Graceful leave / drop — a peer that quits (or whose heartbeat lapses) shows
offline. - Late join — a peer joining late immediately sees the current roster (presence snapshot).
- Node ≥ 20, pnpm, and
nats-server(v2.11+). macOS:brew install nats-server. - Install deps once, from the repo root:
pnpm install.
1. Start the mesh (one terminal — stays running):
pnpm cotal up --open --channels examples/01-lateral-coordination/channels.json
If a nats-server is already listening on :4222, Cotal detects it and reuses it. The
optional --channels file seeds the channel registry (per-channel replay policy +
descriptions); edit it live with cotal channels set <name> [--replay|--no-replay] [--desc …].
2. Join as a few peers (one terminal each):
pnpm cotal join --space demo --name alice --role planner
pnpm cotal join --space demo --name bob --role builder
pnpm cotal join --space demo --name carol --role reviewer
3. Watch everything (optional — one terminal):
pnpm cotal console --plain --space demo
Instead of opening a terminal per peer, run the manager and drive it over the control
plane. The manager owns each peer's process in a pseudo-terminal (pty runtime).
# one terminal — the supervisor (composition root: picks the cotal + claude connectors)
(cd examples/01-lateral-coordination && pnpm manager)
# then, from anywhere
cotal start --space demo --name alice --role planner # manager spawns alice in a PTY
cotal ps --space demo # list managed peers + mesh status
cotal attach --space demo --name alice # stream + drive her terminal (Ctrl-] detaches)
cotal stop --space demo --name alice # kill the process
cotal start --agent claude spawns a real Claude Code session the same way (see below).
The manager hosts a console (in-process, loopback) — a lightweight xterm.js page that
shows one live terminal per managed agent. PTY bytes stream over a direct WebSocket (the same
stream cotal attach consumes), never the mesh. One example, all three surfaces together:
pnpm cotal up --open # 1. mesh, unauthenticated (terminal stays running)
(cd examples/01-lateral-coordination && pnpm manager) # 2. manager + console → prints http://127.0.0.1:7878/
# 3. drive it from the CLI — the console updates live
pnpm cotal start --space demo --agent claude --name ada --role planner
pnpm cotal start --space demo --agent claude --name linus --role reviewer
Open http://127.0.0.1:7878/ — two panes appear, each a real Claude Code TUI you can type
into. cotal ps / stop / start from any terminal and the grid reconciles (panes added,
removed, status dot flips green→red on exit). Port: COTAL_CONSOLE_PORT (default 7878).
A real coding agent joins through the manager — cotal start --agent claude does the native
launch in a PTY pane (no wrapper in front, an ordinary Claude session):
# one-time, per machine: install the bundled plugin for this repo only
claude plugin install cotal@cotal-mesh --scope local
cotal start --space demo --agent claude --name dave --role builder # manager spawns a real claude
Role + identity + a persona can come from an agent file instead of flags — the frontmatter is the identity, the Markdown body is the system prompt:
cotal start --agent claude --name dave --config examples/01-lateral-coordination/agents/dave.md # manager, detached PTY
cotal spawn --agent claude --name dave --config examples/01-lateral-coordination/agents/dave.md # foreground, in this terminal
cotal spawn dave # …or the shorthand (./.cotal/agents/dave.md)
A bare cotal start --name dave also auto-discovers .cotal/agents/dave.md in the manager's
workspace (gitignored, user-local). See agent files.
The bundled plugin reads COTAL_* from the env at spawn and auto-joins the mesh; the manager
auto-clears the one-time dev-channel prompt, so the launch is hands-free. From there the agent is
a peer like any other: its presence flips working / idle from lifecycle hooks, and mesh
messages reach it two ways — hook injection at turn boundaries (the spine) and a channel
nudge that wakes it the instant a message arrives while idle. See
claude-code-integration.md for the launch / install /
channel mechanics, and architecture.md for the surface mapping.
Type a line to broadcast it to the channel. Commands:
| Command | Effect |
|---|---|
/who |
show the roster (names, roles, states) |
/dm <name> <msg> |
unicast a direct message to one peer |
/anycast <role> <msg> |
anycast — reach any one instance of a role |
/working [what] |
set your state to working (+ optional activity) |
/waiting [why] |
set your state to waiting |
/idle |
set your state to idle |
/me <activity> |
update your activity text |
/quit |
leave (others see you go offline) |
- Join as
aliceandbobin two terminals — each sees the other join. alice: typekicking off the auth refactor→bobsees it on#general.alice:/working auth refactor→bob's roster showsalice ● working.bob:/dm alice on it — taking the tests→alicegets a direct message.- A late
carol(reviewer) joins → immediately sees the current roster. alice:/anycast reviewer take a look at the diff→ exactly one reviewer (carol) gets it.- Quit
bob(/quitor Ctrl-C) →alicesees← bob went offline.
pnpm smoke
Runs a non-interactive end-to-end check against a running mesh: two endpoints exchange a
broadcast and a DM, observe a working state change, and detect offline on leave.
The manager defaults to pty. To spawn agents into a tmux window or cmux tab instead,
import the matching extension in the composition root and select it at launch:
# tmux — each agent gets its own window in the cotal session
cotal supervise --runtime tmux # requires: import "@cotal-ai/tmux" in the composition root
# cmux — each agent gets its own tab
cotal supervise --runtime cmux # requires: import "@cotal-ai/cmux" in the composition rootWith --runtime tmux or --runtime cmux and the matching package not imported, the manager
throws a clear "import @cotal-ai/<runtime>" error — no silent fallback to pty.


