Rooms for Claude Code agent teams: a lead, workers, decision cards on a phone page, and a record in plain markdown.
A room is one project. It has:
~/rooms), which outlive every session.The record is the memory, not any conversation. A lead can be a long-lived session you talk to, or a short one-shot turn started for a batch of events. Both read the same files and act the same way.
Rooms is deliberately loose. There is no default team, no fixed set of roles and no required process: you say what the work needs, in your own words, and the lead staffs it. A handful of rules are enforced by the rooms command (a feature's PRD is agreed before building, an item says what done means before it starts and reports a result when it ends, and only your decisions reach you); everything else is a convention you can change. Your team explains which is which.
It needs nothing beyond Claude Code: workers are claude --bg sessions.
You need Node 24 or newer, git, and Claude Code, on macOS or Linux.
git clone https://github.com/mvexel/claude-rooms ~/dev/claude-rooms
~/dev/claude-rooms/cli/rooms.ts install # links `rooms` into ~/.local/bin
rooms init # creates ~/rooms, the records repo
Make sure ~/.local/bin is on your PATH. Run rooms install in a terminal, not from inside Claude Code: it asks before adding ~/rooms to sandbox.filesystem.allowWrite in ~/.claude/settings.json (it keeps a .bak; --yes skips the question). Without that entry, sessions in Claude Code's Bash sandbox can't write the records, and they get a note saying so.
Then install the plugin, so every session has the lead and worker skills and the hooks that deliver events. At a Claude Code prompt:
/plugin marketplace add mvexel/claude-rooms
/plugin install rooms@mvexel-rooms
Or from a shell: claude plugin marketplace add mvexel/claude-rooms and claude plugin install rooms@mvexel-rooms --scope user. To use your clone instead, add ~/dev/claude-rooms as the marketplace. Sessions that are already open pick the plugin up with /reload-plugins. The plugin adds its instructions as context when you submit a prompt and never changes the system prompt, so it also works on managed machines and Team or Enterprise accounts with Claude Code's default plugin guard.
Two one-time steps that save a confusing first hour:
claude in the repo once and accept the prompt; its worktrees are then trusted too. Do the same in ~/rooms, where one-shot lead turns run.commit.gpgsign), signing usually fails inside the sandbox and in unattended runs. Turn it off for the records: git -C ~/rooms config commit.gpgsign false. Workers that hit it in your repo will say so in their Result.Pick a small, real task in a repo you trust, and start the page in a terminal:
rooms serve # the page on http://127.0.0.1:8787/, plus the sweep that delivers events
Open that address. (If you skip this, the lead starts a page itself; one you start in a terminal outlives the lead's session. The page has more, including the phone.)
claude in your repo and say what you want and, if you like, how: “Start a room to add a --json flag to the export command. One builder is enough; have someone review it before merging.” The session loads the lead skill and runs rooms new. Only one room per repo can be open on a machine.~/rooms/<room>/BRIEF.md: goal, scope, the team, and where the code is. Read it; it is yours to edit.P01 a in chat; it is recorded before the lead reads it.rooms new --feature, for something you build for users). The lead drafts PRD.md: purpose, goals and non-goals, numbered testable requirements, definition of done, 120 lines at most. Building stays locked until you agree it on a card; research items may run before. Fixes, chores and research skip this.rooms spawn gives each item its own worktree and branch (<repo>-<room>-t01 on rooms/<room>/t01), a scratch folder, and a background session on the item's tier. The worker logs as it goes, writes a Result, and runs rooms done, which tells the lead.rooms close <room> "<one-line outcome>". Follow-up work gets a new room. rooms prune <room> lists the finished worktrees and branches you can remove.Along the way, rooms status shows every room and rooms chat <room> opens the room's lead in your terminal.
What the lead runs. These are ordinary commands; you can run them yourself to see the moving parts, or to steer a room by hand:
rooms new hello "Hello room"
rooms add hello "Add a greeting script" --acc "hello.sh prints 'hello, rooms' and exits 0" \
--in README.md --cap 0.5 --owner scribe --tier light
rooms spawn hello T01 --repo . # a worker on the light tier, in its own worktree
rooms status hello # T01 doing … then done, with a Result in items/T01-*.md
A room started from a plain terminal has no lead session: when T01 is done, a one-shot lead turn reads its Result and acts. To have a lead you can talk to, open claude in the repo and say “lead the room for this repo”; it runs rooms lead and takes over.
There is no default team, and nothing about its shape is imposed. A room has a lead and whatever workers the work calls for: one builder, five, or none. Rooms gives you four words to describe a team. They are conventions the lead and workers read, not settings the command checks, and the names are yours.
| Word | What it is | Where it lives |
|---|---|---|
| Role | a name for who does an item: builder, tester, writer, critic, data-wrangler, anything. It becomes the worker's name in logs and in the session list. | owner on the item (rooms add --owner builder) |
| Charter | one line per member: what it owns and what it doesn't. Prose for the lead and workers; nothing parses it. | ## Team in BRIEF.md |
| Tier | a name for a model and effort level. Three ship (light, standard, review); retune them or add your own. | tiers in config.json; tier on the item |
| Item | one piece of work: acceptance, inputs, dependencies, a time cap. | items/Tnn-*.md |
| The work | A team that fits |
|---|---|
| A bug fix | one builder on standard; the lead reviews the Result itself |
| A feature | a researcher (light) maps the code; two builders (standard) take independent parts; a reviewer (review) checks each against the PRD |
| A docs pass | one writer on review, whose pull request you merge |
| A question | two researchers on --research items, writing notes only, no worktrees |
| A large mechanical change | several workers on light, one per directory, each item with a --locks name so they don't collide |
A charter, as it appears in BRIEF.md:
## Team
- builder, standard: T02-T04, the export code and its tests; doesn't touch the CLI parser.
- reviewer, review: T05, judges the branch against FR1-FR6; owns no code.
Say what you want when you start (“no reviewer, keep each item under two hours, use opus for the parser”), or later in chat, or edit BRIEF.md. The lead reads the record at the start of every turn. Its skill has defaults for what you leave open (items of about six hours at most, a reviewer on a stronger tier than the author, deep models only when you ask); your words win over them.
The rooms command refuses a few things; ask the lead to work within them rather than around them (--force exists for when you mean it):
Done: and a Next: that names a record or says none (why)) with its acceptance ticked before it is done;Everything else is convention: how many workers, what they are called, which tiers exist, whether there is a review step, how big items are, how branches merge. Change it by saying so.
Only decisions that are yours reach you. The lead chooses the kind; the command routes it.
| Kind | Example | Reaches you as |
|---|---|---|
| Hard to undo | a public name, an API field, merging to main, a deploy, spending | a card with options and the team's pick |
| Reversible but worth knowing | scope or order of work | “going ahead unless you stop it”, with a time; it applies itself if you don't |
| Premise | “the export runs on the existing job queue” | a yes / no card |
| Something only you can do | create an API token, approve an account | a task with exact steps and a Done button |
| Free-text answer | a name, a date, what you saw when you tried it | a text box |
| Engineering choice | which index type | nothing: the team decides and writes it down |
On any card you can ask a question instead of answering; the card waits until the lead replies. In chat, D05 b answers card D05 with option b.
rooms serve [--port 8787] [--host 127.0.0.1] serves the page and runs the sweep: every 15 seconds it delivers events, applies due defaults, and commits the record. The page is one inbox across all rooms, most blocking first; one tap answers, with a few seconds to undo, and a receipt says when the lead has read it.
It listens on loopback, and refuses any other address unless ROOMS_USERS is set. With ROOMS_USERS unset, a loopback server accepts every write. ROOMS_READONLY=1 serves the page with no sweep and no writes.
Over Tailscale: set ROOMS_USERS to your Tailscale login (comma-separate several; only they can answer), keep the server on loopback, and let Tailscale front it:
ROOMS_USERS=you@example.com rooms serve &
tailscale serve --bg --set-path /rooms http://127.0.0.1:8787
On Linux, as a systemd user service:
cp ~/dev/claude-rooms/deploy/claude-rooms.service ~/.config/systemd/user/
systemctl --user enable --now claude-rooms
Set ROOMS_USERS in the unit before you expose it. If node comes from a version manager, make the unit's PATH reach it (journalctl --user -u claude-rooms shows node: No such file or directory if not). After updating the plugin or the clone, systemctl --user restart claude-rooms. On a Mac, run rooms serve in a terminal tab you keep open.
Dockerfile and compose.production.yml run rooms serve in a small Debian image, built on the host. The image has no node, claude or rooms: your home directory is bind-mounted at the same path and supplies them from ~/.local/node/bin and ~/.local/bin. Set ROOMS_USERS (required) and ROOMS_HOME (your home directory) in the environment; ROOMS_PORT (default 8788), ROOMS_UID/ROOMS_GID, ROOMS_MEM_LIMIT and ROOMS_CPUS are optional. Lead and worker sessions are children of the container, so restart it only between turns, and never run it alongside the systemd unit on the same ~/rooms.
A room runs on one machine: its lead and worker sessions, the records in ~/rooms, and rooms serve. That can be your laptop (rooms pause while it sleeps; events wait and are delivered on wake) or an always-on host, where rooms keep moving while you are away.
Never run the same ~/rooms on two machines at once: event inboxes and the session list are local, so two hosts would each start their own lead turns. A git remote (git -C ~/rooms remote add origin <private repo>) is a background mirror for backup and reading elsewhere, not a sync; nothing waits on a push.
| From another machine, to | Do |
|---|---|
| Answer cards, pause, wake a lead | open the page over your tailnet |
| Talk to a room's lead | ssh host -t rooms chat <room> |
| Work in the rooms | ssh host -t 'cd ~/rooms && claude' |
| Chat with a lead from claude.ai/code or the app | start it on the host with claude --remote-control lead-<room> in ~/rooms, then rooms lead <room> inside it |
Taps, finished items, needs and due defaults become events in the room's inbox. If the lead session is open with the plugin, it takes them between tool calls (no extra turn) or as one prompt when idle. If no lead is running, a one-shot lead turn (claude -p in ~/rooms) handles them and exits. A running session is never resumed from outside, because that would fork it.
Records and runtime state are plain files by default. Every read and write goes through one Backend interface (src/backend.ts); CLAUDE_ROOMS_BACKEND=<module path> loads another implementation, for example one that keeps records in a service. Locks, git, worker scratch, sessions and sandbox settings stay local.
Every record is a markdown file: key: value lines between --- markers, then ## Section bodies. Edit them freely; the commands keep the rest in step.
~/rooms/<room>/
BRIEF.md charter and wiring
PRD.md feature rooms only
now-head.md the lead's current position
items/T01-*.md work items
decisions/D01-*.md decisions (D), premises (P), asks and tasks (A)
refinements/RF01-*.md changes to an agreed PRD
notes/ long-form notes, one writer per file
| Field | Meaning |
|---|---|
title | the room's name for people |
lead_session | the Claude Code session id of the lead; events go to it |
sessions | earlier or extra lead sessions |
repos, code | product repos, and the integration branch and its worktree |
paused | a time when you paused it; empty when running |
closed | a time and one-line outcome; set by rooms close |
Sections: Goal, Premises, Scope, Team (one charter per member, see Your team), Where things are.
phase (one line on where the room is), since (your last message), waiting and resume (an outside wait: who or what, and when it picks up again). The body is a short note to you.
| Field | Meaning |
|---|---|
state | todo, doing, blocked, review, done or dropped |
owner, tier | the role, and the model tier it runs on |
depends_on, locks | items that must be done first; shared resources it holds |
kind: research | may run before the PRD is agreed (4 hours at most, three open at a time) |
cap_h | the time cap in hours; status flags 1× and 1.5× overruns |
agent | the worker's session id, set by rooms spawn |
Sections: Acceptance (- [ ] lines), Inputs, Log, Result (Done:, Artifacts:, Unverified:, Next:), and Needs when a worker is blocked.
| Field | Meaning |
|---|---|
type | decision, premise, ask, todo, prd or refinement |
door | one (hard to undo) or two (reversible) |
decider | human for you, or a team role |
status | open, provisional (going ahead by default), final or superseded |
default, due | the option that applies, and when, if nobody stops it |
pick, pick_reason | the team's recommendation |
blocks, urgent | items it holds up (sets its place on the page); a live risk |
Sections: Options (a) … lines), Steps (tasks), Why it matters, Questions, Answer.
The PRD's status is draft, review or agreed; building is locked until it is agreed. An agreed PRD is never edited: a change is a refinement (status draft, review, agreed or superseded; touches lists the requirement ids it changes). The binding set is the PRD plus its agreed refinements, in order; rooms brief <room> prints it.
| Command | Does |
|---|---|
rooms new <room> "Title" [--repo path] [--feature] | a room for a repo (default: the current one); --feature adds a PRD and locks building |
rooms add <room> "title" --acc … --in … [--dep T01] [--cap 2] [--owner role] [--tier name] [--locks name] [--research] | a work item |
rooms ask <room> "Question?" -o … -o … [--door one | --default b] [--premise | --todo --steps … | --text] | a card, routed by kind |
rooms spawn <room> T03 [--repo path] [--tier name] [--model m] | start a worker on an item |
rooms spawn <room> --lead | start a background lead session |
rooms lead [<room>], rooms whoami | make this session the lead (no room: the open room for this repo); which room this session leads |
rooms log | need | done <room> T03 … | a worker's log line, a blocking need, or finishing |
rooms answer <room> D05 b [--quote …] | --reply "…" | record your answer from chat, or reply to your question |
rooms withdraw <room> D05 "reason" | take a card off the page; the record keeps why |
rooms prd <room> review, rooms rf <room> new | RFnn review | the PRD gate and refinements |
rooms set <room> T03 key=value … | change a field on a record |
rooms status [room] [--yield] | the room's state; --yield exits 1 if stopping now would leave it stuck |
rooms tell <room> "…" [--item T03] | message the lead, or a worker |
rooms chat <room> | open the room's lead in this terminal |
rooms pause | resume | close <room> | stop new work, restart it, or finish the room |
rooms prune <room> [--yes] | list, then remove, worktrees and branches of finished items |
rooms serve, rooms sweep | the page server; one sweep by hand |
rooms help lists every flag. Set ROOMS_AS=<name> so log lines carry it. The plugin also adds /room and a status-line entry to Claude Code.
~/.config/claude-rooms/config.json. Every key is optional; these are the defaults:
{
"tiers": {
"light": { "model": "claude-haiku-5-5", "effort": "medium" },
"standard": { "model": "claude-sonnet-5-5", "effort": "medium" },
"review": { "model": "claude-opus-5-5", "effort": "high" }
},
"worker": { "permissionMode": "auto", "tier": "standard" },
"lead": { "permissionMode": "auto", "allowedTools": ["Bash(rooms *)", "Read", "Edit", "Write"] },
"provisionalMinutes": 60,
"commitDebounceMs": 60000
}
A tier is a name for a model and an effort level, and the names are yours. The three that ship are a starting point: light for mechanical work, standard for ordinary implementation, review for judging someone else's work. Your tiers are merged over them, so you can retune one or add new ones:
{
"tiers": {
"standard": { "model": "claude-opus-5-5", "effort": "medium" },
"deep": { "model": "claude-opus-5-5", "effort": "max" }
},
"worker": { "tier": "light" }
}
An item names its tier (rooms add --tier deep); worker.tier is the one used when it doesn't, and rooms spawn --tier or --model override both for one worker. lead.model sets the model of one-shot lead turns. The lead uses an expensive tier only when you ask for it.
allowedTools if you want it to touch less.| Variable | Default | Sets |
|---|---|---|
CLAUDE_ROOMS_HOME | ~/rooms | where records live |
CLAUDE_ROOMS_STATE | $CLAUDE_ROOMS_HOME/.state | inboxes, leases, receipts and worker scratch (kept out of git) |
CLAUDE_ROOMS_CONFIG | ~/.config/claude-rooms/config.json | the config file |
ROOMS_USERS | unset | Tailscale logins allowed to answer on the page |
ROOMS_HUMAN | unset | an extra name accepted as the human decider |
ROOMS_AS | unset | the name your log lines carry |
ROOMS_READONLY | unset | 1: the page only, no sweep, no writes |
CLAUDE_ROOMS_BACKEND | unset (local files) | a storage backend module |
CLAUDE_ROOMS_CLAUDE | claude | the Claude Code executable used to start sessions |
CLAUDE_ROOMS_CLI | rooms | the rooms executable the plugin hooks call |
The plugin's status entry shows the current room's moving and waiting counts, items in review, your cards and the next ready step. To use it as your whole Claude Code status line, merge this into your settings; an existing status-line script can call rooms statusline with the same stdin JSON instead:
{ "statusLine": { "type": "command", "command": "rooms statusline", "refreshInterval": 5 } }
Every room has one status: needs you, stuck, moving, waiting outside, paused, idle or done. rooms status <room> gives the reason and, under “For the lead”, what to fix.
| You see | It means | Do |
|---|---|---|
rooms spawn fails: “Workspace not trusted” | background sessions start only in folders Claude Code trusts, and the worker's worktree is a new folder | open claude in the repo (or, for --lead, in ~/rooms) once and accept the prompt, then spawn again |
A command in a session fails with “Operation not permitted” under ~/rooms | the Bash sandbox can't write the records | run rooms install in a terminal and say yes to the sandbox entry |
| Commits fail: “gpg failed to sign the data” | commit signing can't reach your key from the sandbox or an unattended run | git -C ~/rooms config commit.gpgsign false; for a product repo, decide whether workers may commit unsigned |
| Stuck: items are ready but nobody is working on them | the lead ended its turn without starting work | tap Wake the lead on the page, or rooms tell <room> "dispatch the ready items" |
| Stuck: Tnn finished without reporting back | a worker's turn ended with its item still doing | the lead has been told; if nothing happens, read the item's Log and rooms tell <room> "…" --item Tnn |
| Stuck: updates for the lead can't reach it | the lead session is open without the plugin | install the plugin (above) and /reload-plugins in that session |
| Tnn doing, but its worker is idle | the worker stopped, often on a permission prompt | claude attach <id> to see it; set a permission mode that doesn't prompt |
| Sessions show “liveness unknown” | inside the sandbox the session list hides process ids | run rooms status in a terminal; unknown is never treated as ended |
| A command refuses: “the PRD is draft” | building is locked until you agree the PRD | only research items run before; the lead asks with rooms prd <room> review |
| Mirror push failing | the background push to your remote errored | check credentials on the host; records are safe in the local repo |
| Nothing on the page updates | rooms serve isn't running | start it again (The page) |
Source · public domain (Unlicense)