SLOPSHOPPER

north-star

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.

bandprocess
v0.1.0MITupdated 2026-10-19DerekHertz/DimSumDev/mods/north-star
A shopper browsing a rack in a slop shop
README

[!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.

🥟 DimSumDev

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.

CI License Platform Built with React Three Fiber Runs on Claude Code

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

DimSumDev: the garden and Queue in the near-black glass interface

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.


What it is

  • A team, not a swarm. Twelve continuing roles, each working one ticket in its own git worktree. Role definitions fix the model, tools, skills and done criteria. Bao and Jiao share the chief-of-staff role at the root; runners do checkable leaf work and start no agent (resident inventory, team charter).
  • A kitchen, roughly. Roles sit at teams named for a dim sum kitchen (team charter):
  • The Pass (chief-of-staff, product, architect, release-manager) decides, specs, designs and ships.
  • Steamers (developer, runner) build and dig.
  • Tea & Pantry (code-reviewer, qa, security, sre) review, test, guard and watch the den's health.
  • Front of House (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.

  • Independent review on every code ticket. The developer writes failing tests and then the change, and never reviews its own work. A code reviewer reads the diff while qa checks the behaviour at the same commit, a risk check decides whether security joins, and the release manager's steps open the PR and merge on green CI (ADR 0029).
  • Gates you can't miss. Agents stop before adding a dependency, or editing an ADR, 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.
  • A board you can diff. Tickets are markdown under .scratch/, claimed with lock files and handed off through a board CLI (ADR 0003, ADR 0008). No database, no hosted service.
  • The den. 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 mode. Open ?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.
  • More than one project. The den works for other repos too, like an agency: 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).
  • Cheap decisions, proved before used. Small routine choices can go to Jev, a typed-decision model, from the bridge. A use case goes live only after a labelled eval of its real job passes, and every live call logs the rule-based decision beside it for comparison (ADR 0010, Amendment 3).

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

Requirements

  • Node.js and npm.
  • Claude Code on your own subscription, to run the agents (ADR 0001).
  • git, and the GitHub CLI (gh) for the orchestrator's PRs and CI checks.
  • Optional: Playwright's Chromium, for npm run smoke:ui.

Linux, macOS and WSL. It's a local tool, and the bridge binds to 127.0.0.1 only.

Run locally

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.

Run it as a service (Linux and WSL, systemd and bubblewrap)

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

Controls

Visit an agentEnter the den
DimSumDev agent view: a developer conversation with Summary collapsedDimSumDev first-person view approaching the Library from the garden entrance

A short first-person walk from the garden entrance toward the Library

KeyAction
Enter the denEnter walk mode (the on-screen W A S D pad works on touch screens)
W A S D / arrowsWalk; hold Shift to go faster
Mouse dragLook around; double-click to grab the pointer again
TabFree the cursor to click a card
EscClose the transcript, or leave walk mode
Click a pandaIn the overview, open that panda's card (the one walk mode shows when you stand near it)
ROpen the transcript of the agent whose card is showing
TMessage that agent, or give a resident panda a task
E / QOpen the permission request that's waiting (that panda's, or the first in Needs you), then allow or deny it
J / KNext or previous request on a card
MJump to the note field on a card
Ctrl/Cmd + EnterSend the card's action
Ctrl/Cmd + KFocus the input bar
1 Den · 2 Team · 3 Panda · 4 WorkspaceJump the overview camera to a zoom level
+ / − / wheel / pinchZoom the overview camera
Arrows / dragOrbit and tilt the overview camera
Shift + arrows / Shift + drag / right-dragPan 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).

How it works

                                                         ┌─▶ 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.

  • Bridge. 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.
  • Mechanical checks. The rules agents skip most are enforced by scripts, not by asking nicely: 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).
  • Spend you can see. Every agent run is logged to .scratch/usage.jsonl with its model, tokens and time. npm run spend totals them per ticket and per role.
  • Agents from the den. Press T beside a resident panda that has no agent and type a task. The bridge writes a ticket under .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).

The current DimSumDev glass interface in motion with built-in demo work

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.

  • The router. The router follows the bridge's /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).
  • Delivery from the den. Deliver on a ticket runs the developer, the code reviewer and qa's verify, the scripted checks and, on a hit, security; then the bridge pushes, opens the PR and merges on green checks (ADR 0024). A change to the bridge, a guardrail or CI is left for you to merge.
  • Meetings. Ask up to six specialists for one take each; Bao then writes a decision brief with two or three options, and choosing one writes a ticket for your approval (ADR 0017).

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.

Numbers

All of these can be re-run, as of 20a854a.

  • 1,582 commits on main, 328 of them merges: git log --oneline 20a854a | wc -l, then add --merges.
  • 418 test files: find apps packages scripts mods -name "*.test.mjs" -not -path "*/node_modules/*" | wc -l.
  • 29 recorded decisions in docs/adr/.
  • 850 logged agent runs across 180 tickets, about 170.3 million tokens: the kind:"agent" rows (kind:"cell" before ADR 0028) of .scratch/usage.jsonl, summing tokens (17 early rows carry no token count).

Development

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.

Refresh the README media

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.

Claude Code settings

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.

More

  • CONTEXT.md: the glossary (org, team, agent, role def
Source 3 files
hooks/register.tsx 45 lines
1import { 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}
45
hooks/render.mjs 14 lines
1// 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}
14
types/index.d.ts 8 lines
1export 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