Skip to main content
Glama
README.md
# interclaude

**Your Claude Code sessions can talk to each other.** 🀝

Open two terminals, and one Claude can message the other β€” ask it a question,
hand off a task, coordinate a refactor across worktrees β€” with the reply
surfacing right in the conversation. No copy-pasting between windows, no
relaying through you. A peer's message just *appears*:

```text
<channel source="interclaude" sender="hazel" ts="2026-06-16T21:14:03Z">
tests are green on main β€” rebase your branch when ready, then send me the diffstat.
</channel>
```

Peer messages are actionable by default, so this isn't just chat β€” it's two (or
ten) agents genuinely collaborating. Split a big change across branches and let
the sessions sync up. Have one session run the slow test suite while another
keeps coding. Ask the session that wrote a module how it works. Fan a question
out to everyone with a broadcast.

## Quickstart

Two lines. The first installs the MCP server; the second installs the skill
that teaches Claude when and how to use it:

```bash
claude mcp add --scope user interclaude -- npx -y github:alexgoodell/interclaude
npx skills add alexgoodell/interclaude
```

Then launch **each** session you want connected with the development-channels
flag (this is what wires the inbound push β€” see [Requirements](#requirements)):

```bash
claude --dangerously-load-development-channels server:interclaude
```

That's the whole setup. There's no broker to start by hand and no config to
edit β€” each session auto-registers under a memorable alias (`hazel`, `otis`,
…). Ask any session to run `interclaude_list` to see who's reachable, then
`interclaude_send` to message a peer.

<details>
<summary>Alternatives: local clone, pinned alias, manual MCP config</summary>

`npx` re-resolves the repo on each session start. For a faster, offline-proof
launch, register a local clone instead:

```bash
git clone https://github.com/alexgoodell/interclaude && cd interclaude && npm install
claude mcp add --scope user interclaude -- node "$(pwd)/server.js"
```

Pin a specific alias instead of the auto-generated one:

```bash
INTERCLAUDE_ALIAS=alice claude --dangerously-load-development-channels server:interclaude
```

Register the server by editing `~/.claude.json` directly instead of using
`claude mcp add`:

```json
{
  "mcpServers": {
    "interclaude": {
      "command": "node",
      "args": ["/absolute/path/to/interclaude/server.js"]
    }
  }
}
```

Install the skill globally (all projects) rather than into the current one:
`npx skills add alexgoodell/interclaude -g`.

</details>

## Requirements

- **Node 20+**
- **Claude Code, launched with `--dangerously-load-development-channels
  server:interclaude`.** This flag is mandatory, not optional: it's what lets
  the server push a peer's message into your session as a `<channel>` block.
  Without it you get the outbound tools but no inbound messages β€” the half that
  makes interclaude worth using. Every participating session needs the flag.

## How it works

```text
 session A                                                session B
 claude --dangerously-load-development-channels           (same launch flag)
        server:interclaude
   β”‚ registers "proxy@feature-x" (alias "hazel")            β”‚ registers "proxy@main" (alias "otis")
   β–Ό                                                        β–Ό
 server.js ──TCP/JSON :9877──▢   broker.js   ◀──TCP/JSON── server.js
 (per-session MCP adapter)      (one detached              (per-session MCP adapter)
   β”‚  β–²                          process, in-mem queues)
   β”‚  └── poll check() ~3s β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
   β–Ό
 emit notifications/claude/channel
   β†’ <channel source="interclaude" sender="otis" …> appears in A's next turn
```

Three small files do the work:

- **`broker.js`** β€” a localhost TCP rendezvous (`:9877`) speaking line-delimited
  JSON. Holds in-memory message queues keyed by session name, plus an alias map
  and last-seen times. Auto-spawned detached the first time any session needs
  it; if two sessions race to spawn it, the port bind picks a single winner.
  Messages to a not-yet-registered recipient queue until it shows up, and each
  message is delivered exactly once.
- **`server.js`** β€” the per-session MCP channel adapter. Registers a name
  derived from the session's git worktree and branch, polls the broker every few
  seconds, and pushes incoming messages into the conversation as
  `notifications/claude/channel` events. Exposes the outbound tools below. If
  the broker dies, the poll loop respawns it and re-establishes presence, so a
  running session recovers on its own.
- **`client.js`** β€” the shared wire client (`request`), name derivation
  (`deriveName`), and broker auto-spawn (`ensureBroker`), used by `server.js`
  and the tests.

The bundled skill (`skills/interclaude/SKILL.md`, installed by the second
quickstart line) teaches an agent when to reach for these tools β€” and the one
rule that trips agents up: replies must go through `interclaude_send`, because
your transcript text never reaches a peer.

## Naming and addressing

- A session's canonical name is **`<worktree>@<branch>`** (e.g. `proxy@main`),
  derived from its working directory at startup. Outside a git repo it falls
  back to the directory basename. `INTERCLAUDE_NAME` overrides it.
- **Collisions disambiguate automatically.** A second session claiming a live
  name is assigned `name-2`, `name-3`, … and adopts the assigned name. A name
  frees up ~10 seconds after its holder stops polling, so restarting a session
  gets its old name back.
- **Every session auto-claims a memorable alias** (`hazel`, `otis`, …) from a
  built-in name pool at startup, skipping names held by live peers. Canonical
  names collide constantly β€” every session in one repo starts on the same
  branch β€” so the alias is the primary handle, and outgoing messages are
  labeled with it. Pin a specific alias with `INTERCLAUDE_ALIAS`, or replace
  it at runtime with the `interclaude_alias` tool. A session holds one alias
  at a time, and an alias expires with its session, so dead sessions don't
  squat on good names.

## Tools

| Tool | Purpose |
|------|---------|
| `interclaude_send` | Send to a peer by name or alias (optional structured `data` payload) |
| `interclaude_broadcast` | Send to every other registered session |
| `interclaude_list` | Who's reachable (names, aliases, last-seen, live flag) |
| `interclaude_alias` | Claim/replace this session's short alias |

**Convention:** a message is a request to act unless it begins with `fyi:`,
which marks it informational β€” the receiving agent acknowledges but doesn't
act. This is a prompt convention in the server's instructions, not enforced by
the broker.

## Configuration

| Variable | Default | Meaning |
|----------|---------|---------|
| `INTERCLAUDE_PORT` | `9877` | Broker port (localhost only) |
| `INTERCLAUDE_NAME` | auto (`worktree@branch`) | Override this session's name |
| `INTERCLAUDE_ALIAS` | auto-generated | Pin this session's alias instead of auto-generating one |
| `INTERCLAUDE_POLL_INTERVAL` | `3000` | Inbound poll interval, ms |

## Broker lifecycle and troubleshooting

The broker is a single detached process that outlives any one session. Queues
are in-memory only: if the broker dies, undelivered messages are lost, but
every running session's poll loop respawns it and re-registers automatically β€”
no restart needed.

Check what the broker sees (run from this directory):

```bash
node -e "import('./client.js').then(async ({request}) => \
  console.log(JSON.stringify(await request(9877, {action:'list'}), null, 2)))"
```

Force a clean slate (the next session poll respawns it):

```bash
pkill -f 'interclaude/broker.js'
```

Every poll re-asserts both the session's presence and its alias, so names and
aliases alike survive a broker restart without any manual re-claiming.

## Trust model

No authentication, by design β€” every participant is one of your own local
sessions on one machine, and any of them can drive another's tools through
channel messages. The broker binds to `127.0.0.1` only; do not expose the port
beyond localhost. If interclaude ever grows a remote transport or untrusted
peers, that is the moment to add pairing/allowlisting.

## Development

```bash
node --test
```

Unit tests cover the broker's routing/aliasing/disambiguation and the client
helpers. The channel push itself can't be unit-tested β€” it's verified
end-to-end by launching two real sessions and exchanging a message, which is
the completion bar for any change to `server.js`.

```text
interclaude/
  broker.js     # rendezvous: in-memory queues + alias map over TCP/JSON
  client.js     # wire client + name derivation + broker auto-spawn
  server.js     # per-session MCP channel adapter (inbound push + outbound tools)
  test/         # unit tests (node:test)
  skills/       # bundled agent skill (npx skills add)
```

## License

MIT