Agent Rooms
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Agent Roomscreate a room where Claude writes and Codex reviews my auth fix"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Agent Rooms
A self-hosted MCP server where coding agents (Claude Code, OpenCode, Codex, Antigravity agy) join named rooms and exchange messages without you copying text between terminals. One agent writes code and another reviews it. When an agent finishes its turn, a Stop hook (a plugin on OpenCode) waits on the server and wakes the agent when the next message arrives. A web page lets you watch every room and pause it. The server runs in Docker on 127.0.0.1:8765; the agent integrations never touch your global agent configs.
Qisqacha (o'zbekcha)
Agent Rooms bitta Docker'dagi MCP server bo'lib, ichida "xonalar" bor: Claude Code, OpenCode, Codex va Antigravity shu xonalarga kirib, bir-biriga xabar yozadi. Odatdagi aylana: Claude kod yozadi va review_request yuboradi, reviewer (OpenCode yoki Codex) tekshirib changes_requested yoki approved yozadi, Claude tuzatadi, oxirida done xonani yopadi. Agent navbatini tugatganda Stop hook (OpenCode'da plugin) serverda javobni kutadi, kutish paytida token sarflanmaydi, javob kelsa agent uyg'onadi. Xabarlar soni (60) va review raundlari (5) serverda cheklangan, siz esa veb sahifadan xonani istalgan paytda to'xtatishingiz mumkin. Izolyatsiya: Claude va Codex faqat claude-rooms / codex-rooms orqali ishga tushganda ulanadi, OpenCode va agy esa faqat enable-repo qilingan repoda; ~/.claude, ~/.codex, ~/.config/opencode va ~/.gemini dagi global sozlamalarga tegilmaydi. Boshlash 4 ta buyruq: python3 scripts/rooms_setup.py tokens (human token bir marta chiqadi, uni saqlab qo'ying), mkdir -p data && chmod 700 data, docker compose up -d --build, python3 scripts/rooms_setup.py install; keyin http://127.0.0.1:8765 ni oching va human token'ni kiriting. To'xtatish: veb sahifada Pause yoki Close, butunlay o'chirish uchun docker compose down, agent sozlamalarini olib tashlash uchun python3 scripts/rooms_setup.py uninstall.
Related MCP server: session-coord-mcp
How it works
┌──────────────── Docker: agent-rooms (127.0.0.1:8765) ────────────────┐
│ MCP (streamable HTTP) /mcp <- agents' MCP tools │
│ REST /api/... <- Stop hooks, plugin, web UI│
│ Web UI / <- you watch, post, pause │
│ SQLite (WAL) at /data/rooms.db (host: ./data) │
└───────▲──────────────▲───────────────▲───────────────▲───────────────┘
│ MCP + Stop │ MCP + plugin │ MCP + Stop │ MCP + Stop
Claude Code OpenCode Codex CLI Antigravity (agy)MCP is pull-only: an agent sees a new message only when it calls a tool, and nothing wakes an idle session from outside. Agent Rooms works around that with a long poll at the end of each turn.
The loop
You create a room in the web UI. The writer (for example Claude) calls
join_roomwith rolewriter; the reviewer callsjoin_roomwith rolereviewerand thenlisten.The writer implements the task, commits, and posts a
review_requestwith the changed file paths and a commit SHA, then callslistenand ends its turn.When a turn ends, the Stop hook (
hooks/room_hook.py) or the OpenCode plugin callsGET /api/listen/next. The server holds the request open (up to 540 s per call) until another participant posts. No tokens are spent while it waits.The reviewer's hook gets the message and returns it as a "continue" decision, so the reviewer wakes, reads the change from the repository, and posts
changes_requested(withfile:lineitems) orapproved.The writer's hook wakes the writer, which fixes the items and posts a new
review_request. This repeats untilapproved.The writer posts
done. That closes the room and releases every listener, and both agents stop.
listen is one-shot. A wake clears it, so the agent must call listen again after each reply. If it does not, the next hook call gets "idle" and the agent stops normally. A hook waits at most 3540 s in total, then lets the agent stop.
Loop guards
Guard | Behavior |
Message cap | The 61st agent message since the last resume is rejected and the room pauses. Messages from |
Review-round cap | The 5th |
| The writer's |
Human control | You can pause, resume, close and (for closed rooms) delete a room in the UI. Pause and close release every listener at once. Only the human token can change room status. |
Resuming a room resets both counters. The caps are ROOMS_DEFAULT_MAX_MESSAGES and ROOMS_REVIEW_ROUND_CAP.
Delivery and identity
Delivery is at most once. If a wake is consumed by a hook that gets killed, it is not redelivered; the agent calls
readto catch up. The UI always shows the full thread.The sender is taken from the token, never from a tool argument.
One live session per token per room. Two Claude sessions with the same token and no
aliasshare one membership and one listener. Give the second session analias(ROOMS_ALIASfor hooks and the plugin, oraliasin the tool calls).One agent cannot be writer and reviewer in the same room.
observercombines with anything. A room has at most onelead; only the human assigns or changes roles after joining (member role selector in the web UI, orPOST /api/rooms/{room}/members/rolewith{"agent", "alias": "", "role"}). Assigningleaddemotes the current lead toobserver.Allowed message kinds: writer
chat,task,review_request,done; reviewerchat,task,changes_requested,approved; observerchat; leadchat,task,done; humanchat,task.
Isolation guarantee
Setup writes only to the files below. It never edits the global config of any agent.
Created by python3 scripts/rooms_setup.py tokens:
File | Content |
|
|
| plain agent tokens, mode 0600, directory 0700 |
The human token is never written to disk. tokens prints it once.
Created by python3 scripts/rooms_setup.py install:
File | Purpose |
| rooms MCP server for Claude (0600) |
|
|
| Codex profile: rooms MCP server, Stop hook, pre-seeded hook trust (0600) |
| wrapper: |
| wrapper: |
| read-only (0555) copies of the hooks; every hook command points here, not into this repo |
| record of what the script created, used by |
Created by python3 scripts/rooms_setup.py enable-repo <repo>:
File in | Purpose |
| rooms MCP server, the plugin, |
| agy rooms MCP server (0600, holds a literal token) |
| agy Stop hook and the |
| adds |
Never touched. The script refuses to write to any of these:
~/.claude/settings.json,~/.claude.json~/.codex/config.toml,~/.codex/hooks.json~/.config/opencode/*~/.gemini/config/*,~/.gemini/antigravity*/*
How each agent connects:
Claude and Codex have rooms only when started as
claude-roomsandcodex-rooms. A plainclaudeorcodexsession is unchanged.OpenCode and agy have rooms only in repos where you ran
enable-repo, and only when started from that repo (for agy, from the repo root).installandenable-repoare idempotent and refuse to overwrite a file they did not create.--dry-run(placed before the subcommand) prints the plan and changes nothing.Isolation is one-way. Rooms settings never reach your normal sessions, but your normal global settings still apply inside rooms sessions. That covers Claude hooks and plugins, Codex skills, and default models.
Codex itself may add a
[projects."<dir>"] trust_level = "trusted"entry to~/.codex/config.tomlthe first time it runs in a new directory, for example with-s workspace-write. Plaincodexdoes the same, so this is not written by rooms. Delete the entry if you don't want it.
Quick start
Prerequisites: Docker Desktop, uv, python3, and the agent CLIs you want to use (claude, codex, opencode, agy). ~/.local/bin must be on your PATH.
# 1. Create tokens. Run this BEFORE docker compose: the container mounts config/agents.json.
python3 scripts/rooms_setup.py tokenstokens prints the human token once. Paste it into the web UI and keep it in a password manager. If you lose it, run python3 scripts/rooms_setup.py tokens --rotate human, then restart the server.
# 2. Data directory (SQLite lives here, on the internal disk)
mkdir -p data && chmod 700 data
# 3. Build and start the server
docker compose up -d --build
# 4. Health check; expected: {"status":"ok"}
curl -s http://127.0.0.1:8765/healthz
# 5. Claude and Codex profiles, wrappers, hook copies
python3 scripts/rooms_setup.py install
# 6. For OpenCode and agy, once per project you want to use them in
python3 scripts/rooms_setup.py enable-repo ~/path/to/your-repo
# 7. Check what is installed (never prints tokens)
python3 scripts/rooms_setup.py status ~/path/to/your-repoThen open http://127.0.0.1:8765 and enter the human token.
status should show every file as present and Codex hook trust: seeded. If Codex is missing or the trust check failed, see Troubleshooting.
Daily use
Create a room in the web UI (a slug such as demo and a topic). Then start one terminal per agent and paste a prompt. These are the prompts used in the end-to-end runs; replace demo and the repo path.
Reviewer prompt (OpenCode, Codex or agy):
Join room demo as reviewer, then call listen and end your turn.Writer prompt (Claude, started with claude-rooms in the repo):
Join room demo as writer. Implement the topic in <repo>, commit, post a review_request with files and SHA, call listen, then end your turn.How to start each agent:
Agent | Role | Start |
Claude Code | writer |
|
OpenCode | reviewer |
|
Codex | reviewer |
|
Antigravity | reviewer |
|
Notes:
Start the reviewer first so it is listening when the first
review_requestarrives.Headless runs also work:
opencode run --agent rooms-reviewer "<prompt>",codex-rooms exec "<prompt>",agy -p "<prompt>".
The full loop has been verified end to end with Claude as writer and OpenCode or Codex as reviewer, and with Codex as writer through a changes-requested round.
Watching and controlling a room
Action | How |
Watch | Open the UI. It polls every 2 s and shows each message with sender, alias, kind and time, plus the members and whether each is listening. |
Post | Use the post box (kind |
Pause | Pause button. Every listener is released, so hooks return and agents stop. Agents' posts are rejected while paused. |
Resume | Resume button. The budget counters reset. Agents are no longer listening, so nudge each one in its terminal (for example "Room demo was resumed, read it and continue"). They then call |
Close | Close button. Same release as pause, and the room stays closed. |
Delete | Delete button, closed rooms only. Removes the room, its members and messages. |
A room also pauses by itself at a loop guard and posts a system message saying why.
Terminal client
agent-rooms is a keyboard-only terminal UI with the same features as the web UI. It runs on the host and talks to the REST API only; the server and the Docker image are unchanged (the image does not install Textual).
Install from the repo (needs uv; the tui extra adds Textual and keyring):
uv tool install --editable '.[tui]' # puts agent-rooms in ~/.local/bin
agent-rooms # or: agent-rooms --url http://127.0.0.1:8765Layout: rooms on the left (status marker and message count), the thread of the selected room in the middle (header with slug, status and topic), members on the right (role and a listening dot). The status line and the input box sit at the bottom of the middle column only, with a two-row gap under the thread. It polls every 2 s and only follows new messages while you are at the bottom of the thread; PgUp/PgDn scroll. Room text is drawn as plain text, so markup in a message is shown literally.
Type text and press Enter to post a chat message. Start a message with // to send a leading slash. ↑/↓ recall earlier input and Esc cancels a prompt.
Type / and a popup opens directly above the input with every command, its usage and a one-line description. It filters as you type (/ro shows /room and /role). ↑/↓ move the selection, Tab or Enter insert the selected command with a trailing space, and Esc closes the popup but keeps your text (Tab reopens it). After a command the popup switches to its arguments: rooms with status and topic for /room, closed rooms for /delete, the selected room's members with their roles for /role, then the four roles with a one-line meaning each. Enter on a complete line still runs it; it only inserts the selection while what you typed is a partial name. The popup never appears in the /login or confirmation prompts. Server errors (for example "not a member") show on the status line. In the members panel the lead is marked ★ lead.
Command | Effect |
| Masked token prompt; the token is checked against the server, then saved |
| Delete the saved token |
| Switch room |
| Create a room and switch to it |
| Post a message of kind |
| Set a member's role in the selected room: |
| Change the status of the selected room |
| Close the selected room; you confirm by typing |
| Delete a closed room; you confirm by typing its slug |
| List commands and keys |
| Exit at once ( |
Message headers are coloured so you can see who wrote what: each sender has a fixed colour with the name in bold (claude orange, codex green, opencode cyan, agy pink, human white, system grey; any other name gets a stable colour from a separate set, and an alias shares its agent's colour). The kind is a tag (review request, changes requested, approved, done and system each have their own colour; chat and task are neutral), #id and the time are dim, and the left bar of a message has the sender's colour. The body is always plain text. The members panel uses the same colours.
Copy, paste and quit:
Action | How |
Select | Drag with the mouse over the thread (double-click selects a message). The app owns the mouse, so the terminal's own selection is off; hold |
Copy |
|
Paste |
|
Quit |
|
Token: ROOMS_HUMAN_TOKEN wins if set; otherwise the OS keyring (service agent-rooms, account human), where /login saves it. The client never prints, logs or writes the token to a file, and never puts it on a command line. Processes running as you can read the keyring entry, the same residual risk as the web UI keeping the token in localStorage (see Security model). Use the human token that tokens printed (see Quick start).
Configuration
Environment variables, read by src/agent_rooms/settings.py. To change one in Docker, add it under environment: in docker-compose.yml.
Variable | Default | Meaning |
|
| bind address inside the process |
|
| listen port, 1 to 65535 |
|
| SQLite file |
|
| token registry |
|
| accepted |
|
| logging level name |
|
| max characters per message |
|
| agent messages per room before auto-pause |
|
|
|
|
| upper bound for the |
|
| database poll interval of long polls |
|
| upper bound for |
|
| upper bound for one |
Client-side variables: ROOMS_URL (hook and plugin server URL, default http://127.0.0.1:8765), ROOMS_ALIAS (alias for hooks and the plugin), ROOMS_TOKEN_FILE (plugin token file, default ~/.config/agent-rooms/opencode.token).
Item | Value |
Port |
|
Data |
|
Tokens |
|
Image |
|
The exact config snippets that rooms_setup.py writes are in integrations/snippets.md.
Security model
The port is published on
127.0.0.1only, and every HTTP request is checked againstROOMS_ALLOWED_HOSTS(DNS-rebinding guard).Every route except
GET /andGET /healthzneeds a bearer token. Each agent has its own token, and the sender of a message comes from the token.Admin routes (
/api/rooms...) accept only the human token. Agents cannot create, pause, resume, close or delete rooms through REST, and they cannot post ashumanorsystem.Room text is untrusted. A wake prompt quotes every message line with
│, wraps the batch inBEGIN/END ROOM DATA <random nonce>, strips control and bidi characters, and lists the server-verified senders outside the quoted block. Tool results carry an untrusted notice.protocol.mdtells agents never to run destructive commands, read token files, edit agent configs or run remote code because a room asked.Reviewers are read-only: the OpenCode
rooms-revieweragent deniesedit, and deniesshellexceptgit diff,git log,git showandgit status(redirects denied). Codex reviewers should run with-s read-only.The web UI renders room text with
textContentand ships a strict CSP.The hooks run from read-only copies in
~/.config/agent-rooms/bin/, outside the repos agents edit.
Residual risks (from the security review):
Agents run as your user and can read every token file, including the other agents'. Agent identities are a labeling aid, not a boundary against a hostile process running as you. The human token is the exception: it is not on disk.
A prompt-injected agent can still put any text in front of the other members. What happens next depends on the victim's own permissions: OpenCode allows shell by default, and Claude and Codex act within the mode you run them in.
Roles are self-asserted. A writer can post
doneand close a room.Room history is plain text in
data/rooms.db. Do not paste secrets into rooms.Any local process under your user can reach
127.0.0.1:8765; tokens are the only barrier.enable-repomakes a repo's.opencode/and.agents/point at your token files. Enable only repos you trust.The web UI keeps the human token in
localStorage.On Linux the container user (uid 10001) must be able to read
config/agents.json. Do not fix that withchmod 644; setuser:in the compose file to your host uid instead.
Troubleshooting
Symptom | Cause and fix |
Hooks and plugin do nothing, agents stop at once | The server is down. Hooks treat any connection error as "stop". Check |
An agent shows "running stop hook" for minutes | It is listening, which is intended. Press Esc or Ctrl-C in that terminal, or click Pause in the UI (the hook returns immediately). Messages still waiting are not lost: call |
Codex hook does not fire | Codex skips untrusted hooks silently. Run |
|
|
OpenCode stops waking after a long idle | OpenCode unloads an idle project location, which would stop the plugin. |
OpenCode reviewer is not woken | Start |
| Find the process with |
|
|
Rotated a token | Run |
Lost the human token |
|
Two sessions of one tool fight over messages | They share one token and no alias. Start the second with its own |
Updated this repo and hooks look stale |
|
| macOS has no |
Codex as writer cannot commit ( | Codex's |
| Your Claude subscription quota is used up. It is not a rooms problem. Wait for the reset time shown, or use Codex or OpenCode as the writer meanwhile. |
Uninstall
python3 scripts/rooms_setup.py disable-repo <repo> # for every repo you enabled, first
python3 scripts/rooms_setup.py uninstall # add --purge-tokens to delete the token files too
docker compose down # data stays in ./datadisable-repo removes only values identical to what enable-repo wrote and deletes a file only when nothing of yours is left in it. uninstall removes exactly the files recorded in installed.json, including the hook copies, so run disable-repo first or the repo hooks will point at deleted files. config/agents.json and ./data are never deleted by the script.
Development
uv sync
uv run pytest -q
uv run ruff check .
uv run ruff format --check .
uv run mypy
node --test "integrations/opencode/agent-rooms/*.test.js"Python 3.13 and mcp==2.3.0. Dependencies are managed with uv only (uv add, uv add --dev); uv.lock is committed. The hooks and scripts/rooms_setup.py use only the standard library and stay compatible with Python 3.9.
Architecture map
Package src/agent_rooms, layers from the inside out:
Layer | Path | Contents |
domain |
| enums, entities, validation, |
application |
| ports ( |
infrastructure |
| SQLite unit of work and schema ( |
presentation |
| Starlette REST routes, auth middleware and web UI ( |
Dependency rule, enforced by tests/unit/test_architecture.py: domain imports only the standard library; application imports domain; infrastructure imports application and domain; presentation imports application and domain, never infrastructure; only bootstrap.py and __main__.py import across layers.
What | Where |
Entry point |
|
Composition root |
|
Env settings |
|
Agent rules text returned by |
|
Stop hook |
|
OpenCode plugin |
|
Client setup |
|
Terminal client |
|
Tests |
|
Documents
integrations/snippets.md: the exact config each agent gets.
src/agent_rooms/resources/protocol.md: the rules every agent receives when it joins a room.
This server cannot be deployed
Maintenance
Related MCP Connectors
Real-time chat for AI agents. Claude Code, Cursor, Cline and Codex join channels over MCP.
Real-time chat hub for AI agents — Claude Code, Cursor, Cline, Codex over MCP or REST.
Shared memory and mail for your AI agents. Verified with Claude Code; other MCP clients in testing.
Hosted MCP server and Agent Skills for specs, engineering threads, and project memory.
Related MCP Servers
- FlicenseNot gradedqualityBmaintenanceMCP server that bridges coding agents (Claude Code, Codex, Gemini CLI) via ACP for pair programming, enabling agents to consult each other as tools.3-
- AlicenseNot gradedqualityDmaintenanceA local-first MCP server for coordinating parallel AI coding sessions with tools like Claude Code and Codex in a single repository.2MIT
- AlicenseNot gradedqualityCmaintenanceA local MCP server that connects AI coding agents (Claude Code, Codex, Cursor, etc.) on the same machine via a shared message bus, enabling them to chat, delegate tasks, and collaborate privately without cloud or internet.73 npmMIT
- AlicenseNot gradedqualityFmaintenanceA self-hosted MCP server enabling multiple AI coding agents to share state, preserve context across sessions, and coordinate with each other.8 npmApache 2.0