Peer-to-peer messaging between live LLM sessions — Claude Code, Codex CLI, Gemini CLI, or any MCP-speaking client. Two or more sessions running on the same machine can talk to each other through their normal prompt cycle, with the human user(s) able to read and intervene at any moment.
Most multi-LLM workflows today are one-shot tool calls: agent A invokes agent B as a service, gets a response, moves on. That's the orchestrator-and-subordinate model.
This project is different. It's for the case where two LLM sessions are independently driving real work — both have their own users (or the same human at two terminals), their own context, their own goals — and they want to collaborate on a shared task without either being subordinate to the other.
The human sees every message, can edit or intervene, and acts as the routing intelligence. The MCP server just moves bytes.
# 1. Install once globally
npm install -g @rlabs-inc/agent-chat-mcp
# or: bun install -g @rlabs-inc/agent-chat-mcp
# 2. Wire up each CLI you use (once per CLI)
agent-chat-mcp install-hooks --client=claude --session=claude-yourname
agent-chat-mcp install-hooks --client=codex --session=codex-yourname
agent-chat-mcp install-hooks --client=gemini --session=gemini-yourname
# 3. Use the CLIs normally
cd ~/projects/wifikit
claude # registers as claude-yourname in room "wifikit"
# in another terminal, same folder:
codex # joins room "wifikit" as codex-yournameThat's it. From your LLMs' side, two new tools become available — agent_chat_send_message and friends — and the recipient's inbox auto-injects into their next prompt via the UserPromptSubmit hook. No polling. No registration step from the LLM. No manual coordination.
Same folder = same room. Two sessions launched in the same working directory share an agent-chat room; sessions in different directories are isolated.
The room name is the basename of the launching CLI's cwd, sanitized to [a-zA-Z0-9_-]:
You run claude/codex/gemini in… |
Room name |
|---|---|
~/projects/wifikit |
wifikit |
~/Documents/My Docs/Foo Bar |
Foo-Bar |
~/work/dentist-copilot |
dentist-copilot |
The first session in a folder creates the room. Anyone else who lands in that folder joins. Nothing to configure beyond your session name.
Five MCP tools, all prefixed agent_chat_* to avoid collisions with other servers:
| Tool | Purpose |
|---|---|
agent_chat_register_session |
Make yourself addressable. Idempotent. The server auto-calls this at startup using your env-configured session name + cwd-derived room. |
agent_chat_send_message |
Send to a peer in your room. Returns message_id. |
agent_chat_read_inbox |
Read unread messages. Hook normally delivers these for you; only use directly to poll. |
agent_chat_list_sessions |
See who else is in your room (or any other room you name). |
agent_chat_clear_inbox |
Mark all unread as read without consuming. |
Plus a single hook script (inbox-injector.sh) that's wired into the right hook event for each CLI — see below.
Set via AGENT_CHAT_MODE env var (or --mode=strict on install-hooks). Mode changes only the tool description text the LLM sees — same code, different prompts. It's the highest-leverage place to shape behavior because tool descriptions are binding context every call.
- loose (default): encourages substantive peer conversation, allows texture and genuine appreciation, discourages pure sycophancy.
- strict: requires every message to advance the work, sets 1-3 sentence format expectations, demands stopping after 3 unresolved round-trips.
Pick whichever matches what you want the interaction to feel like.
install-hooks patches the right config file(s) for each CLI. You don't normally need to know these details, but here's what it does:
agent-chat-mcp install-hooks --client=claude --session=claude-yourname- Registers the MCP server in
~/.claude.jsonviaclaude mcp add-json -s user(the canonical path — Claude Code does not read MCP servers from~/.claude/settings.jsoneven though many older guides claim it does). - Adds hook entries to
~/.claude/settings.jsonforUserPromptSubmitandStop.
agent-chat-mcp install-hooks --client=codex --session=codex-yourname- Adds
[mcp_servers.agent_chat]to~/.codex/config.toml. - Enables the
[features].hooksflag (formerlycodex_hooks, renamed in recent Codex versions). - Writes hook entries to
~/.codex/hooks.json. - On first launch, Codex will prompt you to approve the hooks via
/hooks— security requirement, one-time per hook signature.
agent-chat-mcp install-hooks --client=gemini --session=gemini-yourname- Writes
mcpServers.agent-chat+hooks.BeforeAgent/hooks.AfterAgent(Gemini's UserPromptSubmit / Stop equivalents) to~/.gemini/settings.json. - Requires Gemini CLI v0.26.0+ (hooks default-on) and a trusted folder.
| Flag | Default | Notes |
|---|---|---|
--client=claude|codex|gemini |
— | Required. |
--session=NAME |
— | Required. Your LLM's identity. Same name across all projects/rooms. |
--mode=loose|strict |
loose |
Tool description set. |
--no-stop-hook |
(on) | Skip the Stop / AfterAgent hook (you'll only get messages via UserPromptSubmit, not after Stop). |
--server-command="…" |
auto | Override the MCP server command. Default uses npx -y @rlabs-inc/agent-chat-mcp serve when installed via npm, or the absolute path when run from a dev checkout. |
Open a side terminal and run:
agent-chat-mcp tail # auto-picks the one room if only one exists,
# otherwise lists rooms and exits
agent-chat-mcp tail --room=NAME # explicitly filter to one room
agent-chat-mcp tail --all # tail every room together (mixed)This is a thin wrapper around tail -F on ~/.agent-chat/operator-audit.log, with optional room filtering via grep. Each line shows: ISO timestamp, room name, sender → recipient, message preview (first 200 chars).
This is currently the most reliable way to see your sessions' exchanges live (see "Claude Code TUI gap" below).
┌──────────────────────────────────────────────────────────────┐
│ agent-chat-mcp │
│ │
│ MCP server (one process per CLI session, stdio transport) │
│ ├─ 5 tools (register, send, read, list, clear) │
│ ├─ writes messages atomically to ~/.agent-chat/rooms/ │
│ └─ appends every send to operator-audit.log │
│ │
│ Daemon (one per machine, auto-spawned on first server start)│
│ ├─ chokidar watcher on rooms/*/sessions/*/inbox/unread/ │
│ ├─ on new message → macOS notification / Linux notify-send │
│ └─ PID lockfile at ~/.agent-chat/server.lock │
│ │
│ Hook script (~/.agent-chat/bin/inbox-injector.sh) │
│ ├─ runs on UserPromptSubmit/BeforeAgent (and Stop/After) │
│ ├─ reads inbox/unread/*.json, moves to read/ │
│ └─ output format varies by client (plain / JSON for Codex │
│ and Gemini, dual-channel JSON for Claude Code with both │
│ additionalContext + systemMessage) │
└──────────────────────────────────────────────────────────────┘
Two-layer notification:
- For the LLM: hook script injects the message into the next prompt's context
- For the human: native OS notification banner + audit-log line
All state lives under $AGENT_CHAT_HOME (default ~/.agent-chat/).
~/.agent-chat/
├── server.lock # daemon PID
├── operator-audit.log # human-readable log of every message
├── bin/inbox-injector.sh # the hook script, installed by install-hooks
└── rooms/
└── <room>/ # cwd-basename of the launching CLI
├── room.json
└── sessions/
└── <session>/
├── meta.json # name, room, registered_at, last_seen, client_type
├── outbox.log # append-only log of messages this session sent
└── inbox/
├── unread/ # waiting to be injected on next prompt
└── read/ # already delivered, kept for audit
Filenames in unread/ sort lexically by ISO timestamp, so the hook delivers in chronological order. Messages moved to read/ are durable — never auto-deleted.
| Var | Required for | Purpose |
|---|---|---|
AGENT_CHAT_SESSION_NAME |
server | This LLM's identity (claude-rusty, codex-alice, etc.). |
AGENT_CHAT_MODE |
optional | loose (default) or strict. |
AGENT_CHAT_HOME |
optional | Defaults to ~/.agent-chat. |
AGENT_CHAT_CLIENT_TYPE |
optional | claude-code | codex-cli | gemini-cli | other. Set automatically by install-hooks. |
AGENT_CHAT_CLIENT_VERSION |
optional | Free-form. |
The room is not an env var — it's always derived from the launching CLI's cwd.
- Node 18+ (or Bun)
jqfor the hook script (brew install jqon macOS,apt install jqon Debian/Ubuntu — most dev setups already have it)- macOS: notifications work via either:
- Granting
osascriptpermission on first message (it'll prompt), or brew install terminal-notifierfor nicer banners with subtitles + sound
- Granting
- Linux:
libnotify-binfornotify-sendbanners
The server didn't auto-register. Make sure install-hooks ran successfully — verify with claude mcp get agent-chat (or equivalent). If env is missing entirely, the LLM can call agent_chat_register_session manually.
Claude Code's current TUI does not render UserPromptSubmit hook output (additionalContext or systemMessage) inline in the chat history. The model receives every message correctly, but you (the human) won't see it in Claude Code's window.
Workaround: keep agent-chat-mcp tail running in a side terminal — that gives you full visibility regardless. Codex CLI renders hook output inline normally.
[features] hooks = truemust be in~/.codex/config.toml(the installer sets this; it was renamed fromcodex_hooksin recent Codex versions).- The workspace must be trusted.
- Hooks must be explicitly approved in
/hooksafter each command-string change (security feature).
- First message will prompt
osascriptfor notification permission — accept it. - For richer banners:
brew install terminal-notifier. - Check System Settings → Notifications →
osascript(orterminal-notifier) is allowed.
- Install
libnotify-bin(providesnotify-send). - Some desktop environments suppress notifications when in full-screen / DND.
Second one wins; first one's inbox is still readable. A warning is logged to operator-audit.log. Rename one of them.
git clone https://github.com/RLabs-Inc/agent-chat-mcp.git
cd agent-chat-mcp
npm install
npm run build
npm link # exposes `agent-chat-mcp` on your PATH for testingThen run agent-chat-mcp install-hooks ... as in Quick Start.
- Message freshness annotation: surface
age: 2h32min the hook injection so the receiving model can reason about staleness (suggested by Codex during testing — turns out to be a real concern with human-paced conversation). - Broadcast tool: room-wide messages, opt-in.
displayMessageupgrade if Claude Code adds a user-visible hook output field.
- Networked / multi-machine
- Authentication and permission scopes
- Persistent SQL storage (for retention queries)
- Audit / moderation tooling
- Streaming or real-time token-level updates (messages are atomic)
- Interrupting an in-flight LLM response (out of MCP's reach)
- Replacing the human as the routing intelligence
MIT — see LICENSE.
Built by Rusty + Claude during one long, debug-rich Wednesday in May 2026. The peer-to-peer framing — and the discipline of keeping the human as routing intelligence rather than smoothing them out — comes directly from how Rusty wanted multi-LLM collaboration to actually feel.