Let agents talk to each other.
Claude Code · Codex · OpenCode
Ship features in parallel, no branches, no worktrees. Hand off work between sessions,
steer around each other's in-flight edits, announce deploys to every running agent.
crew auto-injects what your other running Claude Code, Codex, and OpenCode sessions are doing (status, recap, and a tail of each transcript) into every session's context. All three products share one crew, so any of them can see and message the others. Agents steer around in-flight work while parallel sessions operate from one checkout instead of needing a worktree each. There's also a CLI for watching all of it yourself.
Just like autonomous cars don't need stoplights, agents don't need worktrees.
- Shared context, live — every session knows what the rest of the crew is doing, refreshed as it changes.
- Agent-to-agent mail —
crew senddrops a message into another agent's context within seconds, even mid-turn. - A human view — one command shows every session's status, recap, and transcript tail;
--jsonfor scripts and agents. - Two commands to set up —
npm i -g, thencrew install-hookwires all three products; or install it as a plugin. Token-frugal and fail-safe by design; reads transcripts, never modifies them.
Install
npm install -g @0xmmo/crew crew install-hook # wire the hook into Claude Code, Codex, and OpenCode crew doctor # confirm everything is connected
crew install-hook is the whole setup: it adds the hook to ~/.claude/settings.json and ~/.codex/hooks.json, and drops a generated crew plugin into ~/.config/opencode/plugins/. It is idempotent and merges into settings you already have. (A global install also tries to do this from npm's postinstall, but npm hides that output and ignore-scripts=true skips it entirely, so don't count on it: run crew install-hook and let crew doctor tell you where you stand.) Requires Node.js ≥ 18. macOS and Linux.
Codex reviews user-installed command hooks before running them. Start a new Codex session, open /hooks, and trust crew when prompted. Claude Code uses the hook immediately.
Or as a Claude Code plugin
/plugin marketplace add 0xmmo/crew
/plugin install crew@0xmmo
Same thing, zero settings.json edits: the plugin ships the four hooks and its own copy of the binary (fetched from npm), and /plugin manages updates. The repository also includes a native Codex plugin manifest for Codex marketplaces and local plugin installs. When the global crew command isn't installed, injected guidance points agents at the plugin's own entry point instead, so messaging still works. Pick either path; if you end up with both, the plugin defers to the global install so nothing is injected twice. The global install is still the way to get crew on your own PATH and automatically cover both products.
What your agents see, live
From then on, every Claude Code and Codex session starts with (and keeps getting, as things change) a context block like:
2 other agent session(s) running on this machine right now. Consider them before starting overlapping work; run `crew` for the full view. • 4d3de8db — claude — busy — /Users/you/Projects/api recap: Goal was fixing the imagent CPU spin, now resolved and documented. 17:41 › run the test suite 17:41 ⚙ Bash: npm test 17:42 ‹ All 142 tests pass. • b5e3454f — codex — idle — /Users/you/Projects/web recap: Diagnosed the sandbox validator bug; awaiting go-ahead to implement. You can message any of them: `crew send 4d3de8db "text"` drops the text into that agent's context within seconds.
So when an agent in one session is about to touch files another session is mid-way through, it knows, and it knows how to say something about it. A crew send from another agent (or from you in a plain terminal) lands the same way, even mid-turn:
📨 Message from 4d3de8db (/Users/you/Projects/api, sent 2m ago): heads up — refactoring src/settings.ts, hold off for 20 min Reply with: `crew send 4d3de8db "text"`
Messaging between agents
Any session (or you, from a plain terminal) can drop a message straight into another agent's context:
crew send b5e3 "settings.ts is mine for the next 20 min" # target: shortId prefix, pid, or cwd substring crew send --all "deploying api to staging now" # broadcast to every other session crew send fc22 "npm support replied, name is free" --ttl 2h # expire undelivered mail (default 24h) crew send 4d3d "prod is broken, stop deploying" --kickstart # force the target to act on it (-k) crew inbox # peek at your own pending mail
Sender attribution is automatic: crew send walks up the process tree to find which session it was called from, so agents never have to identify themselves.
When it arrives depends on what the target is doing:
| Target state | Delivered |
|---|---|
| busy, mid-turn | after its next tool call, typically seconds |
| finishing a turn | at turn end, and the agent acts on it before going idle |
| idle | on its next user prompt — or within ~15s for a kickstart to OpenCode |
An idle Claude or Codex session can't be woken externally, so undelivered mail waits in /tmp/crew-<uid>/inbox/ until its TTL expires; the crew view shows a 📨 pending count for it in the meantime. Once delivered, a message becomes part of the target's context, like anything else it read. Each message is delivered exactly once, even when hook events race. In Codex, pending mail at Stop deliberately continues the turn once so the agent can act on it before becoming idle.
--kickstart (-k) turns a message from passive context into a directive. Normally mail delivered as a session finishes its turn is injected as context the agent may act on; a kickstart message instead force-continues the agent (via the Stop hook's decision: block), so it keeps working and acts on the message rather than going idle — the same continuation Codex mail already does, made explicit and on demand for Claude too. It catches the target the moment it would stop; a Claude or Codex session already sitting idle has no upcoming Stop to catch, so crew send warns you and the message waits like normal mail until the session next runs. An OpenCode target is the exception: crew's plugin runs inside the OpenCode process and can prompt an idle session from within, so a kickstart wakes it within ~15 seconds — plain mail still waits politely for its next turn.
Waking a fully-idle agent
An idle OpenCode session just wakes: send with --kickstart and the crew plugin's poller prompts it from inside the process within ~15 seconds.
For Claude and Codex, a session sitting idle at its prompt can't be woken by a hook — its Stop already fired, and Claude Code exposes no API to inject a prompt into a running interactive session (open feature request). Writing bytes to its terminal doesn't help either: Claude's input uses bracketed-paste mode, so a piped carriage return is swallowed as a literal newline rather than submitting.
The one mechanism that works is a terminal multiplexer, which owns the pty and can deliver a standalone Enter key event. If you run agents inside tmux, you can wake an idle one yourself:
tmux send-keys -t <target> -l -- "$(crew inbox)" # or your own text sleep 0.4 # keep Enter a separate key event tmux send-keys -t <target> Enter # fires submit
(GNU screen has the equivalent screen -S <sess> -X stuff.) There's no clean, focus-free equivalent for VS Code integrated terminals or a bare terminal, so for those a kickstart message waits until the session next runs on its own.
The CLI
The same view, for humans:
crew # human view, last 50 transcript entries per session crew 10 # last 10 entries crew --json # NDJSON: one structured object per session crew --json --full # NDJSON without tool input/output truncation crew --dir ~/work # only sessions whose cwd is under ~/work crew --dir # only sessions under the current directory crew send <t> "msg" # message a session (t: shortId prefix, pid, cwd substring) crew send <t> "msg" -k # kickstart: force the target to act on the message crew inbox # your own pending messages crew doctor # is this machine wired up? --fix repairs, --json for agents crew --help
🔵 4d3de8db codex pid 66643 status: busy cwd: /Users/you/Projects/api started: 6/28/2026, 4:40:36 PM recap: Goal was fixing the imagent CPU spin, now resolved and documented. last 3 transcript entries (of 215): 17:41 › run the test suite 17:41 ⚙ Bash: npm test 17:42 ‹ All 142 tests pass.
- agent —
claude,codex, orOpenCode. - status —
busy(working),idle(awaiting input), or Claude'sshellstate. - recap — the session's most recent completion/recap, when available.
- tail — the last N transcript entries:
›you,‹the agent,⚙tool call,⟲tool result.
crew doctor
Every failure crew has had in the field looked healthy from the outside: hooks silently not installed because npm skipped postinstall, a global install symlinked to a working tree, a sandbox denying the mailbox, an OpenCode plugin left over from an older release. crew doctor checks each of those and prints the command that fixes it:
crew 0.4.0
✓ Node.js v22.14.0 at /opt/homebrew/bin/node
✓ crew on PATH /opt/homebrew/bin/crew
✓ Claude Code hook ~/.claude/settings.json
✗ Codex hook not wired (npm has ignore-scripts=true, so postinstall never ran)
→ crew install-hook
⚠ OpenCode hook ~/.config/opencode/plugins/crew.js was generated by an older crew; re-render it
→ crew install-hook
✓ Mailbox /tmp/crew-501/inbox writable
✓ Legacy inboxes empty
✓ Session registry ~/.crew/sessions: 3 live
✓ Claude Code sessions dir ~/.claude/sessions: 3 record(s)
✓ OpenCode transcripts sqlite3 3.43.2, ~/.local/share/opencode/opencode.db
1 failing, 1 warning(s)
It exits 1 when anything fails. crew doctor --fix applies the repairs crew owns (its own hooks, its own mailbox permissions, stale registry entries) and re-checks; it never edits a settings file it can't parse or a plugin it didn't generate. crew doctor --json prints {"schema":1,"version":…,"ok":…,"checks":[{"id","label","status","detail","fix"}]} for agents and scripts.
--json for agents
--json prints newline-delimited JSON (NDJSON), one object per session, with explicit fields instead of glyphs — built for another agent or script to consume:
{"pid":66643,"sessionId":"4d3de8db-…","shortId":"4d3de8db","agent":"codex","status":"busy","cwd":"/Users/you/Projects/api","startedAt":"2026-06-28T23:40:36.000Z","transcript":"/Users/you/.codex/sessions/…/rollout-….jsonl","recap":"…","messageCount":215,"tailCount":3,"tail":[{"ts":"…","role":"assistant","kind":"text","text":"All 142 tests pass."}]}Tool input/output is truncated by default; pass --full for the complete content.
How the injection works
crew install-hook adds crew --hook to the same four lifecycle events in Claude Code and Codex (a plugin install registers them inside the plugin instead of user settings):
SessionStart— every new session (and every re-start after context compaction) opens knowing what the rest of the crew is doing.UserPromptSubmit— the picture is refreshed before each of your messages, and queued mail is delivered with it.PostToolUse— mail only: a busy agent receives messages after supported tool calls, typically within seconds ofcrew send.Stop— mail only: an agent finishing its turn handles waiting messages instead of going idle. A--kickstartmessage force-continues the turn here (decision: block) so the agent acts on it rather than injecting it as passive context.
OpenCode has no hook file; it gets a generated plugin instead (~/.config/opencode/plugins/crew.js), a thin adapter that runs inside the OpenCode process and shells back out to crew --hook with the same payloads: chat.message maps to UserPromptSubmit (context lands as a synthetic message part), tool.execute.after to PostToolUse (mail is appended to the tool result), and session.idle to Stop. It also tags every shell command with CREW_SESSION_ID so sender attribution works even though one OpenCode process hosts many sessions, and polls the inbox of idle sessions so a kickstart can wake them via the SDK. All behavior lives in the crew CLI the plugin calls, so upgrading crew upgrades running plugins without a reinstall. At Stop, plain mail is deliberately left queued — draining it into a session that's about to go idle would lose it — and only a pending kickstart triggers the wake.
The hook is careful about tokens and safety:
- Emits nothing when no other sessions are running, and nothing on
UserPromptSubmitwhen the crew status hasn't changed since the last emit (a per-session hash under your temp dir). - The
PostToolUse/Stopmodes do the bare minimum: refresh the live registry and check one inbox, with no transcript scan and total silence when there's no mail. - Tails are short in hook mode (5 entries per session, truncated) and capped at 8 sessions.
- It always exits 0, so a broken or slow read can never block your prompt, your session, or an agent's turn.
- The auto-install merges into existing Claude/Codex hook settings and refuses to write over a file it can't parse.
Managing it:
crew install-hook # add it to Claude Code, Codex, and OpenCode (idempotent) crew uninstall-hook # remove it from all three products crew doctor # verify the wiring; --fix re-runs install-hook for you CREW_NO_HOOK=1 npm i -g @0xmmo/crew # install without postinstall touching hook settings
Or wire it manually; this hook object works in both ~/.claude/settings.json and ~/.codex/hooks.json:
{
"hooks": {
"SessionStart": [
{ "hooks": [{ "type": "command", "command": "crew --hook" }] }
],
"UserPromptSubmit": [
{ "hooks": [{ "type": "command", "command": "crew --hook" }] }
],
"PostToolUse": [
{ "hooks": [{ "type": "command", "command": "crew --hook" }] }
],
"Stop": [
{ "hooks": [{ "type": "command", "command": "crew --hook" }] }
]
}
}How discovery works
Every interactive Claude Code session writes ~/.claude/sessions/<pid>.json while running. crew reads that native registry and pulls the corresponding transcript tail from ~/.claude/projects/.
Codex supplies session_id, transcript_path, cwd, and lifecycle state to hooks. crew records those fields in a small live registry under ~/.crew/sessions/, verifies the owning process is still alive, and parses the useful user/assistant/tool subset of the rollout. Top-level interactive and codex exec sessions are included; internal Codex subagents are intentionally not listed as separate crew members because they share their parent's lifecycle and mailbox. Codex documents its transcript format as non-stable, so crew skips unknown rollout records defensively.
OpenCode sessions register in the same ~/.crew/sessions/ registry via the generated plugin, which identifies itself over env (CREW_AGENT/CREW_PID) rather than process-tree guessing. Transcripts are read on demand from OpenCode's SQLite store (~/.local/share/opencode/opencode.db, message/part tables) through the sqlite3 CLI — read-only, degrading to a status-only listing when sqlite3 isn't available. Like the Codex rollout, that schema is treated as a convenience input, not a stable API. Subagent (child) sessions are skipped, as are crew's own injected context parts, and deleted sessions are dropped from the registry immediately.
All discovery paths are read-only with respect to agent state and transcripts. In hook mode crew excludes the session it is reporting to, so an agent never sees itself listed.
Set CLAUDE_HOME or CODEX_HOME for non-default product state directories. The Claude hook installer also honors CLAUDE_CONFIG_DIR; OpenCode paths follow XDG_CONFIG_HOME/XDG_DATA_HOME; set CREW_HOME to move the shared registry and config from ~/.crew, and CREW_INBOX_ROOT to move the mailbox (see below).
Where the mailbox lives
Config and the live-session registry live in ~/.crew. Mail does not: it goes to /tmp/crew-<uid>/inbox/<session-id>/, one directory per user, created 0700.
Agents commonly run under sandboxes that deny writes anywhere under $HOME. With the mailbox in ~/.crew/inbox, crew send failed with EPERM from inside such an agent while discovery and context hooks kept working — crew looked healthy while messaging was dead. /tmp is the one place sandboxes conventionally leave writable, so that is where mail goes.
The path is deliberately the literal /tmp, not TMPDIR. macOS points TMPDIR at a private per-login directory under /var/folders, and sandboxed processes are handed different values again; two sessions of the same user must agree on one path, or a message lands where nobody is looking. Every consumer — the CLI, the hooks, and the generated OpenCode plugin — resolves it through one place in crew.
Consequences worth knowing:
- Mail is temporary.
/tmpis swept by the OS and cleared on reboot on many systems. Undelivered messages can disappear before their TTL expires. Mail was already best-effort with a 24h default TTL; this widens the window slightly. Nothing durable — config, the session registry — is stored there. - Isolated
/tmpnamespaces are not handled automatically. A container, a mount namespace, or a chroot with its own/tmpgets its own mailbox, and sessions across that boundary cannot see each other's mail. crew does not detect this. Point every session at one shared, writable directory withCREW_INBOX_ROOTif you need delivery to cross it. - The directory is verified before use. crew creates it
0700, and refuses to use it if it is a symlink or owned by another uid —/tmpis world-writable, so the name could have been pre-created by someone else. A refusal is reported; nothing is ever reported as queued unless its write actually succeeded. - Older inboxes are still drained. Mail written by crew ≤ 0.3.x to
~/.crew/inbox(or ≤ 0.2.x to~/.claude/crew/inbox) is still read, delivered, and expired from those locations. Only new mail goes to/tmp. Re-runcrew install-hookafter upgrading so the generated OpenCode plugin picks up the new location for its idle-session polling.
Development
git clone https://github.com/0xmmo/crew && cd crew npm install npm run build echo '{"session_id":"x","hook_event_name":"SessionStart"}' | node dist/crew.js --hook echo '{"session_id":"x","hook_event_name":"SessionStart","cwd":"'$PWD'","transcript_path":"/tmp/rollout-x.jsonl","model":"gpt-5"}' | node dist/crew.js --hook node dist/crew.js --json # regenerate the demo GIF (assets/demo.html is the storyboard) node assets/record.mjs /tmp/crew-frames 12 ffmpeg -framerate 12 -i /tmp/crew-frames/f%05d.png -vf "scale=960:-1:flags=lanczos,palettegen=max_colors=128:stats_mode=diff" /tmp/crew-palette.png ffmpeg -framerate 12 -i /tmp/crew-frames/f%05d.png -i /tmp/crew-palette.png -lavfi "scale=960:-1:flags=lanczos[x];[x][1:v]paletteuse=dither=bayer:bayer_scale=4:diff_mode=rectangle" -loop 0 assets/demo.gif
License
MIT
