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.

newbandprocess
v0.1.0MITupdated 2026-10-09DerekHertz/DimSumDen/mods/north-star
A shopper browsing a rack in a slop shop
README

[!WARNING] Work in progress. Dim Sum Den 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.

🥟 Dim Sum Den

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 perch on Bao, a big plush panda, in a 3D den that shows 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

The den in Demo mode: Bao at the center, agents at their stations

The den in Demo mode (?demo=den), captured from the running app.


What it is

  • A team, not a swarm. Nine roles (product, architect, orchestrator, developer, scout, qa, security, designer, herald), each a Claude Code session running one ticket in its own git worktree. A role is a markdown file in .claude/agents/ that fixes its model, tools, skills and done criteria.
  • A kitchen, roughly. Roles sit at stations named for a dim sum kitchen. The Pass (orchestrator, product, architect) decides and sequences. Steamers (developer, scout) build and dig. Tea & Pantry (qa, security) test and guard. Front of House (designer, herald) keeps things looking right and drafts the public posts.
  • A relay for every code ticket. qa writes failing tests, a developer makes them pass, qa verifies, a risk check decides whether security reviews, and the orchestrator opens the PR and merges on green CI (ADR 0002).
  • Gates you can't miss. Agents stop before pushing, adding a dependency, or editing an ADR or their own role files. You approve a ticket once; the orchestrator runs its whole relay and only comes back for a visual check, a design question, a ticket that failed twice, 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 scene of Bao and the agents, with a dashboard for the queue, gates and usage. 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.
  • Cheap decisions, logged before trusted. Routine relay choices (which model a developer needs, how deep qa verifies) go to Jev, a small typed-decision model, through scripts/jev.mjs. It runs in shadow mode: each pick is logged beside what the relay actually did, and any error or missing key falls back to the current rule (ADR 0010).

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/DimSumDen.git
cd DimSumDen
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 an agent as its own Claude Code session:

claude --agent orchestrator     # proposes tickets and runs the relay
claude --agent developer        # or any other role in .claude/agents/
npm run next-session            # prints the command to resume from the latest handoff

Controls

KeyAction
Walk buttonEnter 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
FOpen the transcript of the agent you're standing near
TMessage that agent
A / DApprove or deny the request it's waiting on
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
+ / − / wheel / pinchZoom the overview camera

How it works

 you ── approve ticket ──▶ orchestrator ──▶ qa specify ──▶ developer ──▶ qa verify ──▶ risk-check ──▶ PR ──▶ merge on green
                                │                                                         │
                                └──── board (.scratch/, locks, handoffs) ◀────────────────┘
                                                      │
                                      bridge (127.0.0.1:4317, /state, /events) ──▶ den UI
  • 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.

Not built yet: launching agents from the den itself, and runtimes other than Claude Code (ADR 0004 sketches the adapter).

Numbers

All of these can be re-run, as of 9d3e57c.

  • 917 commits on main, 128 of them merges: git log --oneline 9d3e57c | wc -l, then add --merges.
  • 231 test files: find apps packages scripts -name "*.test.mjs" -not -path "*/node_modules/*" | wc -l.
  • 20 recorded decisions in docs/adr/.
  • 573 logged agent runs across 122 tickets, about 32.6 million tokens: the kind:"cell" rows 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
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/organism-infra (the board CLI), apps/ci-cd (dev server and smoke checks), scripts/ (relay tooling). Everything else is Node ESM run with node --test.

More

  • CONTEXT.md: the glossary (organism, station, cell, genome, Bao, perch and the rest).
  • docs/adr/: every recorded decision and why it was made.
  • docs/agents/: how agents use the board, worktrees and process hygiene.

License

MIT

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; ORGANISM_ROOT, when set, points it at the main checkout.
11async function refresh($: any) {
12  try {
13    const root = await $.env.get('ORGANISM_ROOT')
14    const r = await $.process.run(['node', 'scripts/north-star.mjs', '--json'], root ? { env: { ORGANISM_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