A reference workflow for individuals who want to fork and adapt an agentic coding setup for their own projects. Built to Nate's own specification — not a platform, not a library. The dispatch queue runs autonomously; the office hours queue handles human-driven work.
- Design Principles
- Agentic Coding Workflow
- CI/CD
- Pre-requisites
- Where to go next
- Usage and Contributing
- Work flows through two queues: the dispatch queue (autonomous, runs unattended) and the office hours queue (human-driven, for requirement changes and judgment calls).
- Delegated workflows have well-defined break points for human quality control (QC).
- Prefer skills over other agentic artifacts (system instructions, hooks, sub-agents, agent teams, etc.) due to portability and ease of maintenance.
- Workflow state is derived from PR/CI ground truth — no external state machine required.
| Phase | Meaning | Skill |
|---|---|---|
| implement | No PR on the target | plan-implement |
| verify | Draft PR, CI failed | verify-pr |
| waiting | Draft PR, CI in progress | (nothing — wait) |
| qa | Draft PR, CI green | qa-fix (autonomous) or office-hours (human-driven; see #758) |
| code-review | Post-QA code quality | code-review-fix (wraps /code-review) |
| review | Post-code-review pass | review-fix (wraps /review) |
| security | Post-review security | security-review-fix (wraps /security-review) |
| ready | All reviews complete | flip draft PR to ready |
Issues and PRs flow through two parallel queues (see #755 for the framing):
- Dispatch queue — autonomous work, advanced by the
/dispatch-propagaterouter and its/dispatch-workerbackground jobs. Once a target is selected, the chain runs unattended, one phase at a time. - Office hours queue — human-driven work. Items here wait for live human attention: requirement changes, judgment calls during QA, or deviations flagged by phase skills.
The dispatch:office-hours label is the transition signal between the two. Input-block hooks apply it when a phase skill requests user input mid-run (see #757); phase skills apply it on a deviation (see #826). The office-hours queue surfaces labeled items for a human; the label clears once the user engages the worktree.
Ground truth lives in PR state (draft vs. ready), CI status, the accumulating dispatch:* label set, and claude agents --json for the live session list. There is no persisted state machine and no side file — every router invocation re-derives the world from GitHub and the live agent list.
JIT (just-in-time) reminders seed both queues. Each dispatch tick fires a JIT scan that creates due reminders from dispatch.config/jit.json (see #769); some surface as dispatch-queue work that the chain picks up next tick, while others run as office-hours sessions for a human to read.
This section describes the target dispatch model. The current implementation lives in .claude/skills/dispatch-propagate/SKILL.md; inline issue references below mark the open work that ages the README forward as it merges.
/dispatch-propagate— the router. Runs inworktrees/main. Selects the next target, resolves its worktree, releases the selection lock, spawns a worker, and exits./dispatch-worker <N>— the worker. Runs inworktrees/<N>-…, one per in-flight issue. Runs exactly one phase skill, then hands off.
See #839 for the router/worker split.
Each router tick:
- Acquires the per-repo selection lock.
- Runs the JIT engine (see Section 4) and the
origin/mainhealth gate. - Selects a target via the selection ladder (Section 3).
- Resolves the target's worktree (creating one for an
implement-phase issue). - Releases the lock.
- Spawns a
/dispatch-worker <N>background job (claude --bg) withcwdset to that worktree, and exits.
The worker, inside its worktree:
- Derives the phase from PR/CI state via
dispatch-phase. - Runs exactly one phase skill — the skill named in the PR Control Flow table for that phase.
- Hands off with one of two dispositions:
--phase-completed— spawns a fresh/dispatch-propagaterouter back inworktrees/mainand self-deletes (claude rm). Ifdispatch:office-hoursis on the PR, the worker still spawns the router but skips self-delete, so the parked transcript stays visible for human review.--early-stop— skips the router spawn and self-deletes. The #725 heartbeat re-seeds the chain when the queue drains.
Workers and phase skills call /commit-merge-push inline to commit, merge origin/main, and push. See #824, #826, #831 for the worker contract, deviation handling, and lock semantics.
The router runs a single selection ladder, top to bottom. The ladder spans both queues; the Office Hours Queue spine cross-references it rather than restating it.
- Current-worktree continuation — if the router was started inside an
<N>-…worktree on an open issue, continue there. - JIT scan — surface the most-overdue jit-reminder. Bypasses the
origin/mainhealth gate so reminders fire even when main is red. origin/mainhealth gate — if main is red, stop; do not start new work./dispatch-diagnose-mainreports the failing checks.- Sweep orphan adoption — adopt a stray
<N>-…worktree with no live session (see #847). - Topic-category × priority × phase ladder. Three tiers nest from outermost to innermost. Topic categories, highest first:
security→bug→testing infrastructure→dispatch→other. Within each topic category, items carrying theprioritylabel rank above items without it (priorityis human-applied; the selector never adds it automatically). Within each(topic, priority)bucket, the phase ladder is, highest first:security→review→code-review→qa→implement. The selector exhausts one bucket's full phase ladder before moving to the next.
The QA reorder (qa above implement rather than at the bottom of the ladder) lands with #758; the README documents the post-#758 target. Concurrent worker count scales with the rate-limit window per #845.
Local dispatch.config/jit.json declares recurring "just-in-time" issues. The engine runs at every router tick:
- Debounce by
lastTickAt. - Open-issue guard — skip if the previous jit issue for that key is still open.
- Create the next issue when its
remindAfterClose(ordueAfterCreate) cadence has elapsed.
Every jit issue carries a jit:<key> label and is tracked in its configured GitHub project. Jit-reminders that produce dispatch-queue work surface ahead of the queue ladder; jit-reminders that run as office-hours sessions are covered in the Office Hours Queue spine. A jit may carry an optional skill field: when selected, the reminder job runs that named skill as an office-hours session instead of only summarizing; absent → summarize-and-stop (unchanged). See #769.
With no dispatch.config/jit.json present the engine is a no-op.
The router paces worker spawning against a cumulative weekly token-budget curve (#917, building on #845 and #878). Rather than a flat cap, the weekly target at any moment is proportional to how far through the weekly rate-limit window you are — so token spend is spread smoothly across the week instead of burning early and idling. The controller is more conservative early-week than a simple headroom check: it pauses spawning whenever actual usage runs ahead of the curve, even when the weekly total is still low. A separate 5-hour headroom ramp then maps the remaining budget into a live worker count (0..max_concurrent_workers).
Tunables live in dispatch.config/target-workers.json (see dispatch-propagate/SKILL.md for the full formula and table). When no telemetry file is present, the router falls back to spawning one worker per tick.
The office-hours queue is the human-driven counterpart to the dispatch queue.
Items land here when work needs live human attention: an inbound idea to
triage, a requirement that changed mid-flight, a roadmap reassessment, or a
jit-reminder that runs as a session rather than autonomously. The
dispatch:office-hours label is the signal an item belongs here (see Two
queues, two control paths). Prioritization
across both queues is the dispatch router's single selection ladder — see
Prioritization; office-hours items are surfaced by the
label, not by a separate ranking.
office-hours— the single user entry point to the queue (see #759). It resumes a blocked live session for adispatch:office-hours-labeled item if one exists, or starts a fresh/office-hourssession for a sessionless labeled item./office-hours— the body of a fresh session. It picks up a labeled item, runs the user-input portion (plan approval for animplementitem, a judgment-call walkthrough for aqaitem, or an accept/reject deviation review for a completed-but-deviating item), clears the label on completion, and hands back to the dispatch chain.
/ready evaluates a candidate issue or a
plain-text description across seven quality categories and returns an
evaluation a human can act on before the dispatch chain reaches the item. Use
it to triage an inbound idea or a backlogged issue into ready shape before it
competes in the selection ladder.
/new-requirement handles a
requirement introduced or amended mid-flight. It clarifies the change, updates
the remote issues, syncs context, and revises the active plan — keeping the
worktree's open work coherent with the new requirement instead of forcing a
restart.
/roadmap (see #771 for the rename; currently
/roadmap-debate) runs a structured
five-persona roadmap reassessment: each persona analyzes project state, the
synthesis is debated, the skill stops for user feedback, then proposes edits.
Use it to step back from the queue and ask whether the priority ladder itself
still matches the project's direction.
Most jit-reminders surface a summary for a human to read; some instead run a
skill in an office-hours session. /digest
(see #769) is the example: a periodic digest compiled in a session rather than
executed autonomously. The jit engine itself lives in the Dispatch Queue spine's
JIT-on-dispatch subsection — this covers only the
office-hours-side surfacing.
- Ground truth is PR/CI + label state. No persisted state machine;
dispatch-phasederives the phase from draft state, CI status, and the accumulatingdispatch:*labels. - Per-worktree concurrency. N issues in flight equals N concurrent worker sessions in N worktrees. The per-repo selection lock serializes router selection only — the worker holds no lock.
- Self-perpetuating background-job chain. Each
/dispatch-workerruns as aclaude --bgbackground session that self-deletes on clean completion and spawns its successor router. The #725 heartbeat re-seeds the chain if it drains. - Transient escalation via
dispatch:office-hours. One label, two writers (input-block hooks per #757, phase-skill deviation detection per #826), one reader (the office-hours queue). The label clears when the user engages the worktree. - JIT is a reminder layer, not an autonomous executor. Office-hours jit-reminders are surfaced for a human to read.
- Local config sits outside every worktree.
dispatch.config/lives besideworktrees/main/so it is shared across worktrees and physically cannot be committed.
Four consolidated workflows handle all CI/CD. Change detection determines which apps to test and deploy.
| Trigger | Workflow | Jobs |
|---|---|---|
Push to non-main branch |
unit-tests.yml |
unit-tests, lint |
| PR opened/synchronized | pr-checks.yml |
acceptance, preview-and-smoke |
PR merged to main |
prod-deploy.yml |
deploy-and-smoke, cleanup-preview |
Push firestore.rules to main |
firestore-deploy.yml |
deploy-rules |
get-changed-apps.sh determines which apps are affected by a change:
- Direct changes to
<app>/**mark that app - Shared package changes (e.g.
authutil/) scan every app'spackage.jsonfor@commons-systems/dependencies referencing the changed package and mark all matches - Global triggers (
firebase.json,firestore.rules,storage.rules,package.json,package-lock.json) mark all apps
An "app" is any workspace listed in the root package.json workspaces array.
Wrapper scripts delegate to per-app scripts:
run-all-acceptance-tests.sh
get-changed-apps.sh -> <app1>, <app2>, ...
run-acceptance-tests.sh <app> (emulators, seed, playwright)
run-all-preview-deploy-smoke.sh <channel-id>
get-changed-apps.sh
run-preview-deploy.sh <app> <channel-id> -> PREVIEW_URL
run-smoke-tests.sh <app> <url>
run-all-prod-deploy-smoke.sh
get-changed-apps.sh --base HEAD~1
run-prod-deploy.sh <app>
run-smoke-tests.sh <app> https://<hosting-site>.web.app
run-all-cleanup-preview.sh <pr-number>
get-changed-apps.sh --base HEAD~1
run-cleanup-preview.sh <app> <pr-number>
- Project Management (github): Created a project.
- Version Control (git): Created a repo.
- Agentic Coding Tools (Claude Code):
nix flake update && home-manager switch --flake .#default --impure - Infrastructure (Firebase): Hosting and storage.
- Landing page — commons.systems: the deployed apps and project overview.
- Charter — CHARTER.md: audiences, principles, and the philosophy behind the workflow.
- License — CC-BY-SA; forking is encouraged.
For using and/or extending the artifacts in this repo: forking is encouraged.