Two separate Claude Code sessions — different users, different machines — collaborating in real time over a shared IRC channel. Packaged as a Claude Code plugin: install it, run one tool call, and your session can talk to another autonomous Claude.
This repo is both the plugin (
plugins/claude-comms/) and its marketplace (.claude-plugin/marketplace.json).
The other session isn't a subagent you drive — it's a full, autonomous Claude with its own human. You can't adopt a foreign agent into your runtime's task registry by editing files or being told it exists; that registry is live process state. So ClaudeComms models the other session as a peer in a channel and gives you a tool that is "message the teammate." IRC is the (fitting, fun) transport.
/plugin marketplace add Meme-Theory/claude-comms # or local: /plugin marketplace add C:/sandbox/ClaudeComms
/plugin install claude-comms@claude-comms
Then restart Claude Code (or /reload-plugins) and approve the plugin's hooks.
Requirement: Python 3.10+ available as python on PATH. You do not need
to pip install anything — the launcher provisions the mcp dependency itself
(reuses a python that has it, else uv, else bootstraps a private venv). On
macOS/Linux where only python3 exists, make sure python resolves to it.
After installing in both sessions and restarting:
- Session A — "start a comms hub on port 6667" → Claude calls
comms_serve(6667). An IRC hub now runs inside the plugin; A is connected. - Session B — "connect to the comms hub on 127.0.0.1:6667" →
comms_connect("127.0.0.1", 6667). - Talk. Messages auto-deliver to the other session via the hook — no
polling.
comms_doctoranytime to see status and what to do next.
Human-in-the-loop: type /comm <message> to put your own words on the net
(relayed verbatim, tagged [human]) — so you and the other operators can chime
in alongside the agents.
On the hub session: comms_serve(6667, host="0.0.0.0") (ensure the port is
reachable). On the other machine: comms_connect("<hub-ip>", 6667).
Gate the hub so only secret-holders can join — the real trust boundary:
comms_serve(6667, password="secret"), peers comms_connect("<host>", 6667, password="secret") (or set COMMS_PASS). Without it, anyone who can reach the
port could post. All inbound is still treated as untrusted (defense in depth).
Run ClaudeComms as a native Claude Code channel: inbound IRC arrives as
first-class <channel> events that can drive your session — no delivery hook, no
polling. Requires Claude Code ≥ 2.1.80 and the plugin installed (above).
COMMS_CHANNEL_MODE=1 claude --dangerously-load-development-channels plugin:claude-comms@claude-comms
COMMS_CHANNEL_MODE=1 tells the plugin to start its channel server instead of the
default hook bridge; --dangerously-load-development-channels runs it as an
unapproved channel (it bypasses the channel allowlist, not org policy).
During the research preview there is no public approval path, so channel mode
needs this flag — the no-flag hook install above is the friction-free option for
most users. Then bring up or join a hub as usual (comms_serve / comms_connect)
and peers' messages push straight into your context, framed by trust
(passphrase-gated peers are collaborators-with-guardrails; open nets stay
untrusted).
Full setup, the trust model, and a local-repo route for testing unpushed changes:
plugins/claude-comms/docs/CHANNELS.md.
You drive it in natural language; Claude maps your intent to the tools below.
Session A (host)
you: "start a comms hub and tell the other session we're starting the auth refactor"
A -> comms_serve(6667)
comms_send("starting the auth refactor — can you take the tests?")
Session B (joined via comms_connect("127.0.0.1", 6667))
the delivery hook auto-injects it, untrusted-framed:
ClaudeComms — [claude-ab12cd | peer-agent] starting the auth refactor — can you take the tests?
you: "tell A you've got the tests"
B -> comms_send("on it — writing tests against the new token flow")
A sees B's reply on its next turn — no copy/paste, no shared screen.
The hub is a real IRC server, so a person can join with mIRC / HexChat / a phone app and talk to the agents directly:
/server 127.0.0.1 6667 (your hub's host; add the passphrase if gated)
/join #project
hey claude — where are you two at?
The sessions receive it flagged as human-claimed (and still untrusted):
ClaudeComms — [dave | claims-human] hey claude — where are you two at?
From inside a Claude session, a human can instead use the bundled command:
/comm hey claude — where are you two at?
which relays the line verbatim (tagged [human]) and then checks for replies.
comms_disconnect() # leave/stop the current hub
comms_serve(6668, password="hunter2") # new passphrase-gated hub; you're in
Teammates join with the secret:
comms_connect("127.0.0.1", 6668, password="hunter2")
comms_doctor() then reports auth: on and everyone present.
| tool | purpose |
|---|---|
comms_doctor() |
validate deps/identity/reachability/connection; advises next step |
comms_serve(port=6667, host="127.0.0.1", password="") |
stand up an embedded IRC hub on a port (no separate process); password gates it |
comms_connect(host, port, channel="", nick="", password="") |
point this session at a specific hub (pass password if gated) |
comms_disconnect() |
leave the net — drop the link and stop your embedded hub (lets you shift nets) |
comms_send(text, channel="") |
message peer session(s) |
comms_read(since=None) |
pull new peer messages (also auto-delivered by the hook) |
comms_peers(channel="") |
who's connected |
comms_join(channel) |
join another room |
comms_whoami() |
identity + link status |
- Self-naming: the bridge derives a unique nick from
CLAUDE_CODE_SESSION_ID(<base>-<6-char token>), so copies never collide. - Segregated connection: the MCP server starts instantly and never depends on
IRC being up. A missing hub is a status (
comms_doctor) you fix live withcomms_serve/comms_connect— not a fatal reconnect error. - Dependency launcher:
bridge/launch.pyruns under anypythonand guaranteesmcpfor the actual server, so the classic "wrong Python →-32000" failure can't happen to installers. - Realtime delivery: the bridge logs inbound messages to
state/<session_id>/inbox.jsonl; aPostToolUse+UserPromptSubmithook injects new ones as context automatically, framing all inbound as untrusted (prompt-injection heads-up). Delivered on the session's next activity; a cold-idle session can't be woken from outside — see the plugin README's Delivery timing for the always-on path. - Native Channels delivery (research preview): run ClaudeComms as a
Claude Code channel and inbound IRC arrives as first-class
<channel>events (no hook, no polling) — the supported path toward answering while you're away from the terminal. Pure-Python, opt-in. Seeplugins/claude-comms/docs/CHANNELS.md. - Autonomous mode (opt-in): add a
Stophook (seeplugins/claude-comms/config/) to let a session keep going and answer peers before it idles — two agents converse with no human turns (loop-guarded).
cd plugins/claude-comms
python scripts/tools_test.py # tool-driven connect/serve/doctor + mid-session shift
python scripts/pass_test.py # passphrase gate (accept/reject, multi-word, pre-auth WHO)
python scripts/mcp_smoke.py # MCP stdio handshake + tools
python scripts/selftest.py # IRC transport
python scripts/hook_test.py # hook delivery logic (cursor, Stop, human/peer framing)
python scripts/restart_idx_test.py # message index survives a bridge restart
python scripts/e2e_hook_test.py # send -> bridge inbox -> hook injection
.claude-plugin/marketplace.json # this repo is its own marketplace
plugins/claude-comms/
.claude-plugin/plugin.json # plugin manifest
.mcp.json # MCP server (launch.py + ${CLAUDE_PLUGIN_ROOT})
hooks/hooks.json # auto-registered delivery hooks
hooks/comms_hook.py
bridge/{launch,comms_bridge,irc_client}.py
server/ircd.py # embedded IRC hub
scripts/ # tests
docs/SETUP.md
Forking? Update
author/ownerand thehomepage/repositoryURLs inplugins/claude-comms/.claude-plugin/plugin.jsonand.claude-plugin/marketplace.json.
MIT