claude-rooms

Rooms for Claude Code agent teams: a lead, workers, decision cards on a phone page, and a record in plain markdown.

The idea

A room is one project. It has:

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.

Install

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:

Your first room

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.)

  1. Ask for it. Open 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.
  2. The brief. The lead fills in ~/rooms/<room>/BRIEF.md: goal, scope, the team, and where the code is. Read it; it is yours to edit.
  3. Premises. What the lead assumed becomes premise cards. Tap yes or no on the page, or type P01 a in chat; it is recorded before the lead reads it.
  4. The PRD, for feature rooms only (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.
  5. The plan. The lead adds one item per piece of work: what done means (acceptance lines), what to read (inputs), what it waits for, a time cap, a role and a tier. Items with no dependency between them run in parallel.
  6. Workers. 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.
  7. Review and merge. The lead reads the Result, reviews or has it reviewed, and merges the branch into the room's integration branch. Merging to your main branch, deploying and other hard-to-undo steps come to you as cards.
  8. Close. When the work is done, the lead runs 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.

Your team

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.

WordWhat it isWhere it lives
Rolea 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)
Charterone line per member: what it owns and what it doesn't. Prose for the lead and workers; nothing parses it.## Team in BRIEF.md
Tiera 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
Itemone piece of work: acceptance, inputs, dependencies, a time cap.items/Tnn-*.md

Some teams people use

The workA team that fits
A bug fixone builder on standard; the lead reviews the Result itself
A featurea researcher (light) maps the code; two builders (standard) take independent parts; a reviewer (review) checks each against the PRD
A docs passone writer on review, whose pull request you merge
A questiontwo researchers on --research items, writing notes only, no worktrees
A large mechanical changeseveral 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.

Steering it

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.

What is enforced, and what isn't

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):

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.

Who gets asked what

Only decisions that are yours reach you. The lead chooses the kind; the command routes it.

KindExampleReaches you as
Hard to undoa public name, an API field, merging to main, a deploy, spendinga card with options and the team's pick
Reversible but worth knowingscope 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 docreate an API token, approve an accounta task with exact steps and a Done button
Free-text answera name, a date, what you saw when you tried ita text box
Engineering choicewhich index typenothing: 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.

The page

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.

On your phone

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

As a service

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.

In a container

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.

Where it runs

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, toDo
Answer cards, pause, wake a leadopen the page over your tailnet
Talk to a room's leadssh host -t rooms chat <room>
Work in the roomsssh host -t 'cd ~/rooms && claude'
Chat with a lead from claude.ai/code or the appstart it on the host with claude --remote-control lead-<room> in ~/rooms, then rooms lead <room> inside it

How the lead hears things

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.

Storage

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.

Records

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

BRIEF.md

FieldMeaning
titlethe room's name for people
lead_sessionthe Claude Code session id of the lead; events go to it
sessionsearlier or extra lead sessions
repos, codeproduct repos, and the integration branch and its worktree
pauseda time when you paused it; empty when running
closeda time and one-line outcome; set by rooms close

Sections: Goal, Premises, Scope, Team (one charter per member, see Your team), Where things are.

now-head.md

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.

Items

FieldMeaning
statetodo, doing, blocked, review, done or dropped
owner, tierthe role, and the model tier it runs on
depends_on, locksitems that must be done first; shared resources it holds
kind: researchmay run before the PRD is agreed (4 hours at most, three open at a time)
cap_hthe time cap in hours; status flags 1× and 1.5× overruns
agentthe 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.

Decisions

FieldMeaning
typedecision, premise, ask, todo, prd or refinement
doorone (hard to undo) or two (reversible)
deciderhuman for you, or a team role
statusopen, provisional (going ahead by default), final or superseded
default, duethe option that applies, and when, if nobody stops it
pick, pick_reasonthe team's recommendation
blocks, urgentitems it holds up (sets its place on the page); a live risk

Sections: Options (a) … lines), Steps (tasks), Why it matters, Questions, Answer.

PRD and refinements

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.

Commands

CommandDoes
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> --leadstart a background lead session
rooms lead [<room>], rooms whoamimake 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 reviewthe 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 sweepthe 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.

Configuration

~/.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
}

Tiers

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.

Other keys

Environment

VariableDefaultSets
CLAUDE_ROOMS_HOME~/roomswhere records live
CLAUDE_ROOMS_STATE$CLAUDE_ROOMS_HOME/.stateinboxes, leases, receipts and worker scratch (kept out of git)
CLAUDE_ROOMS_CONFIG~/.config/claude-rooms/config.jsonthe config file
ROOMS_USERSunsetTailscale logins allowed to answer on the page
ROOMS_HUMANunsetan extra name accepted as the human decider
ROOMS_ASunsetthe name your log lines carry
ROOMS_READONLYunset1: the page only, no sweep, no writes
CLAUDE_ROOMS_BACKENDunset (local files)a storage backend module
CLAUDE_ROOMS_CLAUDEclaudethe Claude Code executable used to start sessions
CLAUDE_ROOMS_CLIroomsthe rooms executable the plugin hooks call

Status line

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 } }

Troubleshooting

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 seeIt meansDo
rooms spawn fails: “Workspace not trusted”background sessions start only in folders Claude Code trusts, and the worker's worktree is a new folderopen 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 ~/roomsthe Bash sandbox can't write the recordsrun 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 rungit -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 themthe lead ended its turn without starting worktap Wake the lead on the page, or rooms tell <room> "dispatch the ready items"
Stuck: Tnn finished without reporting backa worker's turn ended with its item still doingthe 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 itthe lead session is open without the plugininstall the plugin (above) and /reload-plugins in that session
Tnn doing, but its worker is idlethe worker stopped, often on a permission promptclaude 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 idsrun 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 PRDonly research items run before; the lead asks with rooms prd <room> review
Mirror push failingthe background push to your remote erroredcheck credentials on the host; records are safe in the local repo
Nothing on the page updatesrooms serve isn't runningstart it again (The page)

Source · public domain (Unlicense)