Content
# ipclaude
Peer-to-peer messaging between Claude Code sessions, via MCP + tmux.
Run several Claude Code sessions on one machine — a leader, a backend agent, a frontend agent, a tester — and let them message each other directly. A message sent with `ipc_send` is typed into the target session's input box and submitted, so it arrives exactly like a prompt from you. No daemon, no sockets, no polling: the only moving parts are a JSON registry file and `tmux send-keys`.
```
┌─────────────┐ ipc_send("tester", "API contract: POST /sessions …")
│ leader │ ─────────────────────────────────────────────┐
└─────────────┘ ▼
┌─────────────┐ ┌─────────────┐
│ frontend │ ◀── [IPC MESSAGE from tester — …] ──── │ tester │
└─────────────┘ └─────────────┘
each box is a normal Claude Code session in its own tmux session
```
## What's in the box
| File | Role |
|---|---|
| `server.py` | MCP server exposing four tools: `ipc_register`, `ipc_list_sessions`, `ipc_send`, `ipc_unregister`. Self-contained [uv](https://docs.astral.sh/uv/) script — dependencies resolve on first run. |
| `bin/ipclaude` | Launcher that wraps `claude` in a tmux session and (with `-n`/`-r`) pre-registers it for IPC and sets a 🤖-styled tab title. |
| `install.sh` | Symlinks the launcher, registers the MCP server with Claude Code. |
Runtime state is a single file, `~/.claude/ipc/registry.json` (override the directory with `CLAUDE_IPC_DIR`). It maps short agent names to tmux session names and is garbage-collected automatically when sessions die.
## Requirements
- macOS or Linux (anything with tmux and TTYs)
- [tmux](https://github.com/tmux/tmux)
- [uv](https://docs.astral.sh/uv/) (runs the MCP server with its inline dependencies)
- Python ≥ 3.11 (any install; uv will also fetch one if needed)
- [Claude Code](https://claude.com/claude-code) CLI (`claude` on PATH)
## Installation
### Quick (script)
```bash
git clone https://github.com/JoshuaSP/ipclaude
cd ipclaude
./install.sh
```
The script is idempotent and fails loudly if a prerequisite is missing. It symlinks `bin/ipclaude` into `~/.local/bin` and registers the MCP server at **user scope**, so every project gets the `ipc` tools.
### For a model (Claude Code or another coding agent)
If you are an AI agent installing this for a user, follow these steps exactly and verify each one:
1. **Check prerequisites.** Run `command -v tmux uv python3 claude`. All four must resolve. If any is missing, stop and report it — do not substitute another terminal multiplexer or package runner.
2. **Clone the repo** to a permanent location (it is run from the checkout, not copied):
```bash
git clone https://github.com/JoshuaSP/ipclaude ~/ipclaude
```
3. **Install the launcher:**
```bash
mkdir -p ~/.local/bin
ln -sf ~/ipclaude/bin/ipclaude ~/.local/bin/ipclaude
```
Verify: `command -v ipclaude` resolves. If not, `~/.local/bin` is missing from PATH — add `export PATH="$HOME/.local/bin:$PATH"` to the user's shell profile and tell them.
4. **Register the MCP server** (user scope — available in all projects):
```bash
claude mcp add --scope user ipc -- uv run ~/ipclaude/server.py
```
For a single project instead, run the same command with `--scope project` from the project root, which writes `.mcp.json` (see `.mcp.json.example`).
5. **Verify:** `claude mcp list` shows `ipc`. Then start a session with `ipclaude -n test` and confirm the `ipc_list_sessions` tool works inside it (it should report `test (you)`).
6. **Report** to the user: where the repo lives, the scope you registered at, and how to launch (`ipclaude -n <name>`).
## Usage
### Launching sessions
```bash
ipclaude -n backend # new tmux session named "backend", pre-registered for IPC
ipclaude -n frontend # second agent
ipclaude -r backend # resume: reattachable name, re-registers for IPC
ipclaude # anonymous session (register later with ipc_register)
ipclaude -p "fix the bug" # pass any other claude args through
```
`-n`/`-r` both name the tmux session **and** pre-register it in the IPC registry, so the session can send and receive messages immediately — no need to ask Claude to call `ipc_register` first. Run inside an existing tmux session and `ipclaude` reuses it; outside tmux it creates one.
You can also run plain `claude` inside any tmux session and ask it to register itself: *"register for IPC as reviewer"*.
### Messaging
Inside any registered session, just ask in natural language:
> who else is on IPC?
> → Claude calls `ipc_list_sessions`: `backend (you)`, `frontend`, `tester`
> tell frontend the sessions endpoint is ready at POST /api/sessions, payload {name}
> → Claude calls `ipc_send(name="frontend", message=…)`
The frontend session receives, as ordinary input:
```
[IPC MESSAGE from backend — reply via ipc_send] The sessions endpoint is ready at POST /api/sessions, payload {name}
```
Messages are **fire-and-forget**. There are no delivery receipts and no reply channel — a reply is just another `ipc_send` in the opposite direction. The sender tag is added automatically.
### Tools reference
| Tool | Args | Behavior |
|---|---|---|
| `ipc_register` | `name` (≤ 48 chars) | Joins IPC. Claims the name (evicting dead sessions holding it), styles the tmux tab title. One name per tmux session. |
| `ipc_list_sessions` | — | Lists live registered sessions; prunes dead ones from the registry as a side effect. |
| `ipc_send` | `name`, `message` | Delivers the message into the target session's input box and submits it. Refuses if the sender isn't registered, or if the target is you or dead. |
| `ipc_unregister` | — | Leaves IPC. |
## How delivery works (and why it's careful)
`ipc_send` is not a blind `send-keys`. Before injecting, the server:
1. **Finds the caller's identity by TTY**, not env vars — it maps the MCP process's TTY to a tmux pane (`tmux list-panes -a`), so identity survives stale/inherited `TMUX_PANE` values.
2. **Checks the target is alive** (`tmux has-session`) and prunes it from the registry if not.
3. **Makes the input box visible** — if the target is in an expanded view (Ctrl-O etc.), it sends Escape and waits for the UI to re-render.
4. **Waits for the human** — it captures the target pane *with ANSI colors*, strips Claude Code's dim-grey placeholder text (color 246), and reads what's actually typed in the input box. If a real message is mid-composition (3+ letter word heuristic; stray keystrokes don't count), it polls for up to 120 s until the user submits, then piggybacks. This is what stops agents from clobbering your half-typed prompt.
5. **Injects with bracketed paste** (`ESC[200~ … ESC[201~`), so long multi-line messages arrive as one input, then presses Enter.
Full details are in [docs/how-it-works.md](docs/how-it-works.md). Patterns for running multi-agent teams over this (single-writer git, contract-first pings, hold/go discipline) are in [docs/agent-team-patterns.md](docs/agent-team-patterns.md).
## Security model and limitations
**Trust model: one human, one machine.** There is no authentication. Any process that can write to your tmux server can already type into your terminals; this tool just makes that ergonomic for Claude sessions. Consequences:
- **Received messages are prompts.** An IPC message is submitted as user input, with user-input authority, to the receiving session. Only run IPC among sessions you'd trust with each other's keyboards. Don't wire untrusted content straight into `ipc_send`.
- **tmux only.** Sessions outside tmux can't register (the server needs a pane to type into). Terminal-tab users: `ipclaude` handles the wrapping.
- **Same machine only.** The registry is a local file and delivery is local tmux. No network transport.
- **Delivery is best-effort.** If the target is stuck in a permission dialog the Escape nudge can't clear, or the 120 s piggyback window expires while a human is still typing, injection can interleave awkwardly. In practice this is rare; the mitigations above cover the common cases.
- **Heuristics track Claude Code's TUI.** Placeholder detection keys off Claude Code's dim-grey (256-color 246) styling and the `>` prompt. A future Claude Code redesign could require updating those constants in `server.py`.
## Configuration
| Env var | Default | Meaning |
|---|---|---|
| `CLAUDE_IPC_DIR` | `~/.claude/ipc` | Where `registry.json` lives (both server and launcher honor it). |
| `IPCLAUDE_BIN_DIR` | `~/.local/bin` | Where `install.sh` symlinks the launcher. |
## Uninstall
```bash
claude mcp remove --scope user ipc
rm ~/.local/bin/ipclaude
rm -rf ~/.claude/ipc # runtime state (registry only)
```
## License
MIT — see [LICENSE](LICENSE).