A thin band above the prompt that counts down to den v1: bar, done/total and the next critical-path ticket, read from the board.

[!WARNING] Work in progress. DimSumDev is built for one person's workflow (mine), and the agents in it rebuild it every day. Expect breaking changes: keys that move, panels that get redrawn, features that come and go without notice. If it's close to what you want, fork it and bend it into what you need.
A local control room where a small team of AI agents builds software, and you sit at the Pass.
Agents work tickets through a strict relay on a plain-file board, stop at gates only you can open, and show up as animal residents in a 3D bamboo garden, so you can see who is doing what at a glance.
Run locally · Controls · How it works · Numbers · Development · Glossary · Decisions
npm ci && npm run ui # then open http://127.0.0.1:4317/?demo=den

Screenshots and GIFs show the current app with built-in demo/sample work (?demo=den). Counts and events are illustrative, not production metrics; steering actions are disabled.
chief-of-staff, product, architect, release-manager) decides, specs, designs and ships.developer, runner) build and dig.code-reviewer, qa, security, sre) review, test, guard and watch the den's health.designer, technical-writer) keeps things looking right, keeps the docs true and drafts public posts for you to publish.The former orchestrator, herald and scout responsibilities now belong to the chief of staff, technical writer and runner.
CLAUDE.md or their own role files, and a change to the bridge, a guardrail or CI is yours to merge. You approve a ticket once; the relay runs end to end and only comes back for a visual verdict, an open scope question, a ticket that failed twice, an environment problem, usage at 90%, or a red merge..scratch/, claimed with lock files and handed off through a board CLI (ADR 0003, ADR 0008). No database, no hosted service.apps/ui is a React Three Fiber garden with four team pavilions around a central meeting plaza, animal residents, and Bao and Jiao as its panda executives (layout and roster). Beside it, your desk holds the queue, what needs you, meetings and the library, with tabs for Work, Pandas and Library and a Talk to Bao button. Zoom from the whole den to a team, a panda or its workspace, or press Enter the den to walk around; walk up to an agent to read its transcript, message it, or approve or deny what it's waiting on.?demo=den or press Watch the demo to replay a recorded session into the den. It makes no bridge calls and reads no tokens, and every action that would steer a real agent is greyed out.npm run den:project -- add <slug> <path> onboards one with its own board and queue, and you pick the active project in the den (ADR 0021).The garden and Team show 24 continuing role and runtime profiles: 22 specialists at team desks, plus Bao and Jiao at separate desks in the jade executive pavilion. Each keeps its owned desk while away. The office has independently reserved visitor places for each executive, with a center aisle for meeting travel. Debugger, Librarian, Painter, Cub, scout and herald are retired from the active roster. Historical records keep their original authorship, and ended retired-role conversations stay readable without offering a restart. Codex dispatch remains unavailable (placement, office ownership and routes, inventory and runtime availability, conversation controls, roster tests).
Idle specialists may reserve a destination for tea, pond watching, rest, mahjong or a cross-team friend visit. At most three outings run at once, including travel and return. Residents walk at 3.6 scene units/sec and return to work at 4.8, including physical meeting journeys. Local leisure routes are capped at 60 scene units and a planned 20 seconds one way on a clear route; obstructions can delay travel. Settled activities last 45–90 seconds, followed by a 2–4 minute cooldown after returning home. If no safe place is available, the resident stays home. Friends face each other; mahjong uses real paired residents and ends when a partner leaves (behavior and timing, behavior tests).
Work starts immediately during a break. The resident shows Working · returning to desk, returns safely and settles at its own desk; a blocked return holds the last safe position. Meetings can use separate guest bodies while busy originals keep working. Closing a meeting sheet does not end its participants' ownership. Reduced motion skips travel animation only over revalidated routes, and hiding the tab pauses ambient scheduling (work and meeting integration tests, blocked-return and motion tests).
Bao and Jiao stay at their desks outside meeting ownership and take no ambient leisure outings. Both have independent physical routes to and from the meeting area; Bao uses the existing meeting chair lifecycle and returns to his reserved desk when the meeting ends. Jiao's routes add no meeting authority or Codex dispatch (office behavior and routes, meeting ownership, runtime availability).
Floating blue Codex cloud and orange Claude star ornaments follow the resident's runtime. Decorative marks hide behind structures or when crowded; Team also names the runtime in text (ornaments, Team profiles).
git, and the GitHub CLI (gh) for the orchestrator's PRs and CI checks.npm run smoke:ui.Linux, macOS and WSL. It's a local tool, and the bridge binds to 127.0.0.1 only.
git clone https://github.com/DerekHertz/DimSumDev.git
cd DimSumDev
npm ci
npm run ui # builds the UI, then starts the bridge on port 4317
Open <http://127.0.0.1:4317/>. With no agents running, add ?demo=den to watch a recorded session.
The resource launcher requires systemd 254 or newer. It disables systemd's argument expansion so shell variables and literal dollar signs reach the agent unchanged. Sandbox CI runs on Ubuntu 24.04, installs bubblewrap with an executable-specific AppArmor user-namespace allowance, and requires the launcher's enforcement probe to pass. Ubuntu 26.04's AppArmor policy can prevent nested bubblewrap sandboxes from starting.
npm run den:install # once: packages, first release, the service; the den then answers on 4317
npm run den:update # after merging a change to `main`: new release, links switched, service restarted
The service runs from release folders under ~/.local/state/den/ (a copy of main, its UI built in a sandbox), not from the checkout, so editing the checkout does not change the running den (ADR 0026). den:install also installs the router (den-router.service), a small process beside the bridge that starts approved tickets in the order you set (ADR 0027); a change to the router takes effect on the next den:install. Both commands print each step and refuse before changing anything on a host without a systemd user manager or bubblewrap. npm run bridge is for development: run it with PORT=4400 npm run bridge, on another port than the service.
The den's credential proxy (ADR 0031) logs in as its own Claude accounts, not as your interactive claude: each refresh token is single use, so sharing one login would make the two race, and the den never reads or writes ~/.claude/.credentials.json. Add an account once per account:
npm run den:account -- add <name> # opens claude with CLAUDE_CONFIG_DIR set to ~/.local/state/den/accounts/<name>; type /login, then /exit
npm run den:account -- list # the accounts, the active one starred
The bridge reports the active account and you switch by hand (POST /accounts/active with { "name": ... }; the den has no button for it yet); it never switches by itself when an account hits a usage limit. A running den picks a new account up within a minute.
The bridge also holds one Profile, lean or full (default full; ADR 0032). GET /state reports it, and you flip it by hand with POST /profile and { "name": "lean" } (token required; the den has no button for it yet). The next run uses the new values with no restart, and nothing flips it by itself. Both profiles run 2 workers for now and share the same resource-group limits.
Run an agent as its own Claude Code session:
claude --agent orchestrator # plans tickets and runs a relay from the terminal
claude --agent developer # or any other role in .claude/agents/
npm run next-session # prints the command to resume from the latest handoff
| Visit an agent | Enter the den |
|---|---|
![]() | ![]() |

| Key | Action |
|---|---|
| Enter the den | Enter walk mode (the on-screen W A S D pad works on touch screens) |
| W A S D / arrows | Walk; hold Shift to go faster |
| Mouse drag | Look around; double-click to grab the pointer again |
| Tab | Free the cursor to click a card |
| Esc | Close the transcript, or leave walk mode |
| Click a panda | In the overview, open that panda's card (the one walk mode shows when you stand near it) |
| R | Open the transcript of the agent whose card is showing |
| T | Message that agent, or give a resident panda a task |
| E / Q | Open the permission request that's waiting (that panda's, or the first in Needs you), then allow or deny it |
| J / K | Next or previous request on a card |
| M | Jump to the note field on a card |
| Ctrl/Cmd + Enter | Send the card's action |
| Ctrl/Cmd + K | Focus the input bar |
| 1 Den · 2 Team · 3 Panda · 4 Workspace | Jump the overview camera to a zoom level |
| + / − / wheel / pinch | Zoom the overview camera |
| Arrows / drag | Orbit and tilt the overview camera |
| Shift + arrows / Shift + drag / right-drag | Pan the overview camera |
The Den camera button restores the original target, tilt and zoom. First-person walking moves at 5 scene units/sec, or 9 while holding Shift; residents retain their own pacing. Floating panels use translucent glass with rainbow edges and near-black dark-mode surfaces, with solid surfaces when backdrop filtering is unsupported, reduced transparency is requested, or forced colors are active. Collapsed sidebar icon bubbles gently bob and wobble; they settle during hover, keyboard focus or pressing, and stay still with reduced motion (material, layout and motion).
Beside the camera controls, Roof visibility offers Full roofs and Cutaway. Full roofs is the first-use default; the choice is saved in this browser, with Full roofs as the fallback when storage is invalid or unavailable. Cutaway retains the rear roof section and frame to reveal interiors (controls, preference and tests, roof construction).
Frame rate beside those controls offers 30, 60, and 120 FPS targets, saved in this browser. The default is 30 FPS, including when storage is unavailable or invalid. Higher targets depend on display refresh rate and available rendering time. Badge visibility uses grouped obstacle bounds to avoid scanning the whole garden for each panda. The garden prepares full-roof and cutaway shaders before reporting ready; resident and meeting route searches share a 2 ms frame budget rather than blocking a departure frame.
In Team, selecting a resident opens its profile with role, runtime, real work state and any scene note. Use the keyboard-operable Talk, View work and active-tool inspection buttons; unavailable actions show a reason. Codex Talk/tasks remain disabled until runtime support is enabled (Team actions, runtime availability).
Queue counts active tickets, including work in review, and excludes resolved, closed and parked tickets. Those off-relay tickets also stop blocking active work; missing dependencies remain visibly waiting. Pending user requests remain available through a counted review action and an expandable request list (snapshot, Queue model, drawer).
Run activity groups routine scene labels into an expandable list. Each row selects its exact run; selected runs and Needs you, Blocked and Failed markers remain visible over the scene (activity model, controls).
┌─▶ code-reviewer ─┐
you ── approve ticket ──▶ router / Deliver ──▶ developer ┤ ├─▶ risk-check ──▶ PR ──▶ merge on green
│ └─▶ qa verify ─────┘ (security on a hit)
│ │
└──── board (.scratch/, locks, handoffs) ◀────────────────┘
│
bridge (127.0.0.1:4317, /state, /events) ──▶ den UI
The relay is sized to risk: a ticket on a risk path (the wall, CI, hooks, the service, auth, dependencies) has qa write failing tests before the developer starts, and its verify is light; anything else goes straight to the developer and gets a full verify. One fix round carries both reviews' findings.
apps/bridge reads the board, streams changes over server-sent events at /events, and serves /state and /metrics (ADR 0011). What you do in the den (approvals, messages) goes back as requests that the orchestrator acts on.npm run risk-check, npm run check:bom, npm run session-check, and a release gate that won't let an agent drop a ticket without a handoff (ADR 0009)..scratch/usage.jsonl with its model, tokens and time. npm run spend totals them per ticket and per role..scratch/den/ and starts that role's Claude Code agent in its own git worktree. Its tool calls, permission requests, final reply and cost show on the panda's card, and you can message it while it works. A permission request also shows in the Needs you card and in the transcript, with Deny and Allow (ADR 0016).
Current screenshots and GIFs use the demo's recorded and staged sample work, not production metrics. The first-person clip is a short walk from the entrance toward the courtyard. Steering actions are disabled. Download the earlier MP4 recording with captions and an original soundtrack; this video has not been refreshed for the current interface.
/events and starts deliveries for ready-for-agent tickets from the top of a work order file, two at a time when their files don't overlap. It looks again every few minutes at a delivery paused on your merge (guardrail) or on checks (ci-pending): once you have merged the pull request on GitHub the ticket resolves with no click, and the router never merges a change that is yours to merge. It also resolves an in-review ticket whose branch was merged outside a delivery. It starts nothing at 90% usage, and sends every stop to Needs you and to your phone (ADR 0027).In progress: Codex as a second runtime, with Jiao as a second executive beside Bao, and task trees in place of the fixed relay (ADR 0004, ADR 0029). The bridge already records each run's runtime; agents still run on Claude Code only.
All of these can be re-run, as of 20a854a.
main, 328 of them merges: git log --oneline 20a854a | wc -l, then add --merges.find apps packages scripts mods -name "*.test.mjs" -not -path "*/node_modules/*" | wc -l.docs/adr/.kind:"agent" rows (kind:"cell" before ADR 0028) of .scratch/usage.jsonl, summing tokens (17 early rows carry no token count).npm test # node:test across apps, packages and scripts (runs check:bom first)
npm run ui:dev # Vite dev server for the UI alone
npm run bridge # the bridge alone (development; the service is `den:install`)
npm run smoke # HTTP smoke check
npm run smoke:ui # browser smoke check (Playwright + Chromium)
npm run risk-check # decides whether a branch needs a security review
npm run board -- status <feature>/<NN-slug>
Layout: apps/ui (React + React Three Fiber, built by Vite, the only build step), apps/bridge (localhost server), apps/router (the router), apps/board (the board CLI), apps/ci-cd (dev server and smoke checks), packages/ (shared libraries), mods/ (Claude Code mods), scripts/ (relay tooling). Everything else is Node ESM run with node --test.
With the existing Playwright Chromium and ffmpeg / ffprobe installed:
PW_GPU=1 DISPLAY=:0 node scripts/capture-readme.mjs # WSL GPU; omit these variables for headless capture
node scripts/render-readme-media.mjs
The capture runs an isolated Vite server with local responses for bridge reads. It uses the demo fixture, opens a panda's conversation, and walks through the garden without launching agents. Screenshots and GIFs go to docs/images/; the 18-second, 1920 × 1080 H.264/AAC video goes to docs/videos/dimsumdev-linkedin.mp4. Temporary footage stays under the system temp directory. The video includes captions for muted playback and an original synthesized soundtrack.
Once you trust this folder in Claude Code, .claude/settings.json runs repo scripts as hooks: scripts/session-start.mjs on start, scripts/hooks/context-budget.mjs before each tool call, and scripts/notify.mjs on notifications and stop, plus scripts/statusline.mjs for the status line. To show plan usage, scripts/usage-claude.mjs reads your Claude Code login (~/.claude/.credentials.json, or the macOS Keychain) and sends it only to api.anthropic.com. The shared allow list holds read-only commands; put push and PR permissions in your own .claude/settings.local.json. Read changes to scripts/ and .claude/ in an outside pull request before you open its branch in Claude Code.
The board (.scratch/) is not part of this repo; without one, the board is simply empty.
CONTEXT.md: the glossary (org, team, agent, role defhooks/register.tsx 45 lines1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4import type { Progress } from '../types'
5import { renderBand } from './render.mjs'
6
7const progress = atom({ plugin: 'north-star', key: 'progress' } as const, null)
8
9// The band's numbers come from scripts/north-star.mjs (board files only, no network). The mod
10// shells out to it in the session's directory; DEN_ROOT (or the old ORGANISM_ROOT), when set, points it at the main checkout.
11async function refresh($: any) {
12 try {
13 const root = (await $.env.get('DEN_ROOT')) || (await $.env.get('ORGANISM_ROOT'))
14 const r = await $.process.run(['node', 'scripts/north-star.mjs', '--json'], root ? { env: { DEN_ROOT: root } } : undefined)
15 if (r.exitCode === 0) await update($, progress, () => JSON.parse(r.stdout) as Progress)
16 } catch {
17 // keep the last reading
18 }
19}
20
21export const register: Register = on => {
22 on('session.start', async ($, e, next) => {
23 await refresh($)
24 return next(e)
25 })
26
27 on('turn.complete', async ($, e, next) => {
28 await refresh($)
29 return next(e)
30 })
31
32 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
33 const p = await read($, progress)
34 const text = p ? renderBand(p) : ''
35 if (!text) return next(e)
36
37 const { Box, Text } = $.ui.resolve(e)
38 return (
39 <Box>
40 <Text dimColor>{text}</Text>
41 </Box>
42 )
43 })
44}
45hooks/render.mjs 14 lines1// organism-infra/209: the band text for den v1 progress. Pure and dependency-free: a mod runs with
2// no Node. Input is what scripts/north-star.mjs returns: { done, total, remaining, next }.
3// ████████░░░░ v1 8/12 · next: 143
4const WIDTH = 12;
5
6export function renderBand(progress) {
7 const { done, total, next } = progress ?? {};
8 if (!total) return "";
9 const fill = done >= total ? WIDTH : Math.min(WIDTH - 1, Math.round((done / total) * WIDTH));
10 const bar = "█".repeat(fill) + "░".repeat(WIDTH - fill);
11 const num = next ? /(?:^|\/)0*(\d+)/.exec(next)?.[1] : null;
12 return `${bar} v1 ${done}/${total}${num ? ` · next: ${num}` : ""}`;
13}
14types/index.d.ts 8 lines1export type Progress = { done: number; total: number; remaining: number; next: string | null }
2
3declare module 'claude-code' {
4 interface PluginState {
5 'north-star': { progress: Progress | null }
6 }
7}
8