compounding-loop 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +152 -128
- package/dist/cli/draft.js +69 -0
- package/dist/cli/draft.js.map +1 -0
- package/dist/cli/eval.js +186 -0
- package/dist/cli/eval.js.map +1 -0
- package/dist/cli/hook.js +92 -0
- package/dist/cli/hook.js.map +1 -0
- package/dist/cli/index.js +336 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init.js +41 -4
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/lanesInit.js +8 -2
- package/dist/cli/lanesInit.js.map +1 -1
- package/dist/cli/pace.js +41 -0
- package/dist/cli/pace.js.map +1 -0
- package/dist/cli/pause.js +3 -3
- package/dist/cli/pause.js.map +1 -1
- package/dist/cli/recipe.js +89 -0
- package/dist/cli/recipe.js.map +1 -0
- package/dist/cli/recipeDraft.js +64 -0
- package/dist/cli/recipeDraft.js.map +1 -0
- package/dist/cli/runs.js +49 -11
- package/dist/cli/runs.js.map +1 -1
- package/dist/cli/schedule.js +177 -0
- package/dist/cli/schedule.js.map +1 -0
- package/dist/core/brief.js +35 -12
- package/dist/core/brief.js.map +1 -1
- package/dist/core/draftBrief.js +68 -0
- package/dist/core/draftBrief.js.map +1 -0
- package/dist/core/eval.js +123 -0
- package/dist/core/eval.js.map +1 -0
- package/dist/core/hooksInit.js +54 -0
- package/dist/core/hooksInit.js.map +1 -0
- package/dist/core/laneRound.js +28 -7
- package/dist/core/laneRound.js.map +1 -1
- package/dist/core/lanes.js +3 -0
- package/dist/core/lanes.js.map +1 -1
- package/dist/core/lanesInit.js +19 -0
- package/dist/core/lanesInit.js.map +1 -1
- package/dist/core/parse/index.js +8 -0
- package/dist/core/parse/index.js.map +1 -1
- package/dist/core/parse/outcomes.js +51 -0
- package/dist/core/parse/outcomes.js.map +1 -0
- package/dist/core/parse/policy.js +56 -0
- package/dist/core/parse/policy.js.map +1 -0
- package/dist/core/parse/runs.js +1 -0
- package/dist/core/parse/runs.js.map +1 -1
- package/dist/core/parse/schedule.js +101 -0
- package/dist/core/parse/schedule.js.map +1 -0
- package/dist/core/parse/steer.js +68 -0
- package/dist/core/parse/steer.js.map +1 -0
- package/dist/core/recipeDraft.js +37 -0
- package/dist/core/recipeDraft.js.map +1 -0
- package/dist/core/recipes.js +185 -0
- package/dist/core/recipes.js.map +1 -0
- package/dist/core/repo.js +16 -1
- package/dist/core/repo.js.map +1 -1
- package/dist/core/triggers.js +137 -0
- package/dist/core/triggers.js.map +1 -0
- package/dist/server/actionBody.js +54 -0
- package/dist/server/actionBody.js.map +1 -0
- package/dist/server/checkLabels.js +7 -2
- package/dist/server/checkLabels.js.map +1 -1
- package/dist/server/claude.js +18 -0
- package/dist/server/claude.js.map +1 -0
- package/dist/server/controls.js +28 -1
- package/dist/server/controls.js.map +1 -1
- package/dist/server/data.js +8 -3
- package/dist/server/data.js.map +1 -1
- package/dist/server/draftInputs.js +33 -0
- package/dist/server/draftInputs.js.map +1 -0
- package/dist/server/health.js +3 -3
- package/dist/server/health.js.map +1 -1
- package/dist/server/index.js +55 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/laneChecks.js +1 -1
- package/dist/server/laneChecks.js.map +1 -1
- package/dist/server/laneSchedule.js +15 -0
- package/dist/server/laneSchedule.js.map +1 -0
- package/dist/server/live/build.js +25 -10
- package/dist/server/live/build.js.map +1 -1
- package/dist/server/live/index.js +4 -2
- package/dist/server/live/index.js.map +1 -1
- package/dist/server/live/urls.js +3 -1
- package/dist/server/live/urls.js.map +1 -1
- package/dist/server/loops.js +145 -20
- package/dist/server/loops.js.map +1 -1
- package/dist/server/memory.js +115 -0
- package/dist/server/memory.js.map +1 -0
- package/dist/server/memory.json +398 -0
- package/dist/server/paceNote.js +13 -0
- package/dist/server/paceNote.js.map +1 -0
- package/dist/server/roundState.js +26 -2
- package/dist/server/roundState.js.map +1 -1
- package/dist/server/runs.js +72 -9
- package/dist/server/runs.js.map +1 -1
- package/dist/server/shipped.js +35 -0
- package/dist/server/shipped.js.map +1 -0
- package/dist/server/static-export.js +8 -2
- package/dist/server/static-export.js.map +1 -1
- package/dist/server/this-repo.json +2367 -858
- package/dist/web/assets/index-C5q9d5-n.css +2 -0
- package/dist/web/assets/index-DDEAcJFt.js +123 -0
- package/dist/web/index.html +2 -2
- package/method/COMPOUNDING_LOOP.md +33 -6
- package/method/operating-card.md +1 -1
- package/method/retro.md +4 -3
- package/package.json +4 -4
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/commands/loop-init.md +1 -0
- package/plugin/commands/retro.md +3 -2
- package/plugin/hooks/hooks.json +62 -0
- package/runners/github-actions/lanes.yml +2 -2
- package/runners/routine/README.md +4 -0
- package/runners/routine/control-room-prompt.md +4 -14
- package/runners/routine/lane-prompt.md +4 -10
- package/templates/lanes/README.md +5 -1
- package/templates/lanes/claude-section.md +1 -0
- package/templates/lanes/control-run.md +37 -0
- package/templates/lanes/lane-run.md +33 -0
- package/templates/lanes/schedule.json +4 -0
- package/templates/lanes/steer.md +3 -0
- package/templates/recipe.md +39 -0
- package/templates/recipes/cli-command-json.md +47 -0
- package/templates/recipes/static-calculator-site.md +50 -0
- package/templates/static-site/CLAUDE.md +1 -1
- package/dist/web/assets/index-BOPytDNU.js +0 -53
- package/dist/web/assets/index-BSHEI7fa.css +0 -2
package/dist/web/index.html
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<link rel="icon" type="image/svg+xml" href="./favicon.svg" />
|
|
7
7
|
<title>Compounding Loop</title>
|
|
8
|
-
<script type="module" crossorigin src="./assets/index-
|
|
9
|
-
<link rel="stylesheet" crossorigin href="./assets/index-
|
|
8
|
+
<script type="module" crossorigin src="./assets/index-DDEAcJFt.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="./assets/index-C5q9d5-n.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
<div id="root"></div>
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
> Copy this section into your always-loaded rules file. Keep it under ~60 lines.
|
|
37
37
|
> The § numbers point to Part B of `COMPOUNDING_LOOP.md`.
|
|
38
38
|
|
|
39
|
-
**Objective:** the most verified-correct results per minute of the
|
|
39
|
+
**Objective:** the most verified-correct results per minute of the owner's attention, from the smallest context that still passes. A wrong result that looks right is the most expensive one. Tokens are spent where they buy correctness (checks, review), not on re-learning what a recipe, a trigger or a check already knows.
|
|
40
40
|
|
|
41
41
|
**Start of every session**
|
|
42
42
|
1. Read `.ai/index.md`. Open a knowledge entry only when it's relevant to what you're doing.
|
|
@@ -79,11 +79,11 @@ Ranked by real cost, most expensive first:
|
|
|
79
79
|
1. **Wrong results that look right.** You find out late, and everything built on top has to be redone.
|
|
80
80
|
2. **Human attention.** Explaining, re-explaining, watching, reviewing.
|
|
81
81
|
3. **Waiting time.**
|
|
82
|
-
4. **Tokens
|
|
82
|
+
4. **Tokens.** Once cheap, now a real limit: a smaller context lets a smaller or local model do the job, and a subscription's five-hour limit is shared by every run. One loop spent about two thirds of its scheduled fires on idle runs; that is the same waste as re-reading what is already known.
|
|
83
83
|
|
|
84
|
-
So the goal is **the most verified-correct results per minute of human attention.**
|
|
84
|
+
So the goal is **the most verified-correct results per minute of human attention, from the smallest context that still passes.** Not "zero context": one recipe plus the checks that prove it.
|
|
85
85
|
|
|
86
|
-
Trim context when it hurts quality (noise, stale notes, contradictions)
|
|
86
|
+
Tokens are spent where they buy correctness (checks, review), not on re-learning what a recipe, a trigger or a check already knows. Trim context when it hurts quality (noise, stale notes, contradictions) and when a recipe or trigger can carry the lesson instead. Do not compress a note into keywords: that saves a few hundred tokens and throws away the *why*, which is the part that prevents repeated mistakes. Only a measured result counts as a claim that something got cheaper (§13).
|
|
87
87
|
|
|
88
88
|
### 2. Two ideas everything follows from
|
|
89
89
|
|
|
@@ -95,6 +95,7 @@ Trim context when it hurts quality (noise, stale notes, contradictions), not to
|
|
|
95
95
|
|---|---|---|
|
|
96
96
|
| Memory | Load every rule every session | A short index, plus triggers that fire at the moment of relevance (§7, §8) |
|
|
97
97
|
| Context | One long window that slowly degrades | Fresh windows with handoffs; subagents for reading (§9, §10) |
|
|
98
|
+
| Experience | Start every project from zero, or paste a whole old repo | One recipe for this kind of project: steps, traps and the check that proves each step (§7) |
|
|
98
99
|
| Compute | The strongest model for everything | The cheapest tier that passes the checks (§10) |
|
|
99
100
|
| Rules | Prose rules re-read forever | Automated checks that cost nothing until broken (§12) |
|
|
100
101
|
| Human attention | Watching the agent work | Four touchpoints (§14) |
|
|
@@ -160,13 +161,14 @@ This is the biggest multiplier in the whole method. An agent that can check its
|
|
|
160
161
|
|
|
161
162
|
### 7. Memory
|
|
162
163
|
|
|
163
|
-
|
|
164
|
+
Six layers, each loaded differently:
|
|
164
165
|
|
|
165
166
|
| Layer | Holds | Loaded |
|
|
166
167
|
|---|---|---|
|
|
167
168
|
| Core | Operating Card + project core | Always. Small and unchanging, so it stays cached |
|
|
168
169
|
| Index | One line per knowledge entry | Always |
|
|
169
170
|
| Knowledge | Gotchas, facts, decisions, lessons, open questions | Only when relevant or triggered |
|
|
171
|
+
| Recipes | Procedures that worked: steps, traps, checks (below) | One, when the task matches (`loop recipe match`); the SessionStart hook names the best one |
|
|
170
172
|
| Task state | `.ai/task.md` | Every window of the current task; deleted afterwards |
|
|
171
173
|
| Source of truth | Code, git history, test output, docs | Read when needed; never copied |
|
|
172
174
|
|
|
@@ -178,6 +180,12 @@ Five layers, each loaded differently:
|
|
|
178
180
|
K-0017 gotcha ✔ MV3 service worker loses in-memory state after ~30s idle · files: src/background/**
|
|
179
181
|
```
|
|
180
182
|
|
|
183
|
+
**Recipes are the layer for procedures.** A knowledge entry says "this breaks"; a recipe says "to build this kind of thing, do these steps, and this check proves each one". It is one Markdown file (`templates/recipe.md`) with front matter (`id`, `title`, `kind`, `stack` as `name@major`, `tags`, `status`, `source`, `uses`, `passes`, `updated`) and five sections in order: When to use, Steps (each names its proof), Traps (each pointing at a knowledge id or trigger), Checks, Start from. **Hard cap 6 KB**: a recipe is the small exact context, not a manual.
|
|
184
|
+
- *Stored* in `.ai/recipes/` (project) and `~/.ai/recipes/` (yours, across projects). Nothing personal: no absolute paths, emails or secrets.
|
|
185
|
+
- *Induced only from work whose checks passed*, as small appended or edited items with evidence, never a full rewrite. Rewriting the whole file loses what earlier runs learned (ACE, Zhang et al. 2025, "context collapse"). `loop recipe draft` follows this delta rule.
|
|
186
|
+
- *Used* by `loop recipe match` (stack overlap at the major version, plus tag and keyword overlap) and `loop recipe show <id> --context`, which prints exactly what a model receives.
|
|
187
|
+
- *Judged* by `uses` and `passes`; a recipe that fails more than it passes is fixed or deleted, like a trigger.
|
|
188
|
+
|
|
181
189
|
**Cross-project knowledge is keyed by library and major version, not by project type.** At task start, read the dependency manifest (`package.json`, `pyproject.toml`, `Cargo.toml`, `go.mod`, …) and load the index lines from `~/.ai/libs/<library>@<major>.md` for each dependency. This has three advantages:
|
|
182
190
|
- No guessing about what "kind" of project this is.
|
|
183
191
|
- It combines on its own: a Next + Supabase + Tailwind project simply gets all three sets of notes.
|
|
@@ -196,7 +204,17 @@ Triggers watch events the tool already sees, run outside the model, and cost not
|
|
|
196
204
|
| **Command** | Before a matching command runs | `npm run build` → "run `npx prisma generate` first" (or just run it automatically) |
|
|
197
205
|
| **Topic** | The task text matches keywords | "persist auth" → a one-line hint pointing to K-0017 |
|
|
198
206
|
|
|
199
|
-
- **With hooks** (for example Claude Code):
|
|
207
|
+
- **With hooks** (for example Claude Code): `loop hook <event>` reads the hook JSON from stdin, matches it against `.ai/triggers.yaml` and prints `{"hookSpecificOutput":{"hookEventName":"<event>","additionalContext":"…"}}`, or nothing. Wire it through the plugin (`plugin/hooks/hooks.json`) or a project `.claude/settings.json` (`loop init --hooks`). Match built-in tools only (`Bash|Read|Edit|Write|Glob|Grep`); context from MCP and Agent calls may be dropped.
|
|
208
|
+
|
|
209
|
+
| Hook event | Kinds it checks |
|
|
210
|
+
|---|---|
|
|
211
|
+
| `SessionStart` | The best matching recipe, as one line |
|
|
212
|
+
| `UserPromptSubmit` | Topic |
|
|
213
|
+
| `PreToolUse` (Bash) | Command |
|
|
214
|
+
| `PostToolUse` | File. It fires only after a tool *succeeds* |
|
|
215
|
+
| `PostToolUseFailure` | Error and file. A failed Bash call fires this, not `PostToolUse` |
|
|
216
|
+
|
|
217
|
+
A hook never calls a model or the network, exits 0 on any error (a broken hook must not break a run), injects at most 2,000 characters, and injects each trigger once per session. Each fire is logged locally to `.ai/.trigger-log.jsonl`.
|
|
200
218
|
- **Without hooks:** manual trigger mode. Card rules 7 and 8 make the agent search the index after errors and before edits. It's less automatic but has the same effect, and works in any tool.
|
|
201
219
|
- **Precision beats coverage.** A trigger that fires often and doesn't help is noise. Track `fired` and `helped`, and fix or delete triggers that help less than about half the time.
|
|
202
220
|
|
|
@@ -312,6 +330,7 @@ The AI writes prompts for its next window, its subagents and its reviewers. It's
|
|
|
312
330
|
**Retro.** About 2 minutes, when the checks pass. The AI drafts it and the human approves.
|
|
313
331
|
1. List what was learned. For each item: does it apply beyond this task, or only to it? Is it confirmed or a guess? At what moment would it have helped (which file, command or error)?
|
|
314
332
|
2. Move general, confirmed items down the promotion ladder as far as they'll go.
|
|
333
|
+
**Recipe step:** is this a *procedure* someone will repeat (a kind of project or feature)? Then draft a recipe (`loop recipe draft`, §7), check it with `loop recipe check`, and keep it only if it is short and every step has a proof. A repeated *procedure* becomes a recipe; a repeated *mistake* still goes note → trigger → check.
|
|
315
334
|
3. Put library-level lessons in `~/.ai/libs/<library>@<major>.md`.
|
|
316
335
|
4. Delete `.ai/task.md`. Knowledge that only mattered for this task goes with it, on purpose.
|
|
317
336
|
5. Add a line to `.ai/log.md` (§13) and update `routing.md`.
|
|
@@ -320,6 +339,7 @@ The AI writes prompts for its next window, its subagents and its reviewers. It's
|
|
|
320
339
|
|
|
321
340
|
```
|
|
322
341
|
guess → confirmed note → note with a trigger → automated check
|
|
342
|
+
↘ repeated procedure → recipe
|
|
323
343
|
```
|
|
324
344
|
|
|
325
345
|
The last step is the goal: **the best memory is one the model never has to read.** "Never use `require()` here" becomes a lint rule. "Run prisma generate before build" becomes part of the build script. Then the prose note is deleted. A check costs nothing until it's broken, and it can't be ignored.
|
|
@@ -346,6 +366,8 @@ What to watch:
|
|
|
346
366
|
- **First-try pass rate per tier.** This feeds the routing table.
|
|
347
367
|
- **Trigger precision:** times helped ÷ times fired.
|
|
348
368
|
- **Windows until the checks pass**, and cost.
|
|
369
|
+
- **Tokens per verified unit:** tokens spent divided by units whose checks passed. A cheaper run that fails its checks is not cheaper.
|
|
370
|
+
- **The eval** (`loop eval`) is the one place a context claim is tested: it rebuilds known tasks cold, with a recipe, and with triggers, then reports pass rate, tokens and time per arm and model. Read the headline only when a results file exists; no results, no claim. The owner runs it; unattended lanes use the fake runner.
|
|
349
371
|
|
|
350
372
|
Prefer numbers from tools (check results, git, billing) over the AI's own account of how things went. Change one thing at a time and watch the numbers. Every threshold in this file (50–60%, 20 tasks, 90%, 5 windows) is a starting guess for your log to correct.
|
|
351
373
|
|
|
@@ -429,6 +451,11 @@ A claim protects a lane, not a file: ownership of paths still decides what a lan
|
|
|
429
451
|
|
|
430
452
|
Every run, successful or not, appends one line to `.ai/lanes/<lane>/runs.jsonl` (`loop runs log`); `loop runs` compares those lines and the commits with each lane's scheduled slots, so a slot with neither is shown as "no run recorded", which on a subscription plan usually means the usage limit. A run that cannot pull because its clone started from an old copy fetches `origin main`, works on a branch from it and pushes with `git push origin HEAD:main`; origin `main` is never reset or force-pushed.
|
|
431
453
|
|
|
454
|
+
**Thin prompts, schedule, steering.** The routine prompt should be a few lines: name the lane, do the pull (with the stale-clone recovery) and say "follow `.ai/lane-run.md`" (the control room: `.ai/control-run.md`). The procedure lives in the repo, so changing how every lane runs is a commit, not an edit in five routine settings. `loop init --lanes` writes both files from the kit templates. Two more files let you leave the loop on around the clock:
|
|
455
|
+
|
|
456
|
+
- `.ai/schedule.json` (`{"version":1,"lanes":{"<lane>":{"notBefore":ISO|null,"sleeping":bool,"paused":bool,"reason":string|null}}}`): the first thing a run checks, before any install. A lane that is paused, sleeping or before its `notBefore` replies "Not due" and stops in seconds. A missing file or entry means due. A lane that shipped nothing backs itself off (1 idle run: 3 h, 2 in a row: 6 h, 3 or more: sleeping); the control room wakes it when it has a steer entry, a message or a new round. Only the owner pauses a lane.
|
|
457
|
+
- `.ai/lanes/<lane>/steer.md`: notes from the owner, newest first, each a heading `## YYYY-MM-DD HH:MM UTC · <author> · next-run|standing`. `next-run` entries come before the lane's own unit order, `standing` entries apply until removed. The lane acknowledges each with one line under it (`→ done in run N` or `→ not done: <why>`); that is the only edit it makes to the file.
|
|
458
|
+
|
|
432
459
|
**Failure modes seen when this repo was built by five lanes**
|
|
433
460
|
|
|
434
461
|
- **Stale sandbox clones.** A cloud run can start from an old clone and report that someone force-pushed or that a file vanished. Check `git log` on a fresh pull before believing a run's account of history.
|
package/method/operating-card.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> The full method is in `.ai/method.md`. The § numbers point to Part B there.
|
|
4
4
|
|
|
5
|
-
**Objective:** the most verified-correct results per minute of the owner's attention
|
|
5
|
+
**Objective:** the most verified-correct results per minute of the owner's attention, from the smallest context that still passes. A wrong result that looks right is the most expensive one. Tokens are spent where they buy correctness (checks, review), not on re-learning what a recipe, a trigger or a check already knows.
|
|
6
6
|
|
|
7
7
|
**Start of every session**
|
|
8
8
|
1. Read `.ai/index.md`. Open a knowledge entry only when it's relevant to what you're doing.
|
package/method/retro.md
CHANGED
|
@@ -5,9 +5,10 @@ Run it when the checks pass, before deleting `.ai/task.md`. About two minutes.
|
|
|
5
5
|
1. List what was learned. For each item: does it apply beyond this task? Is it confirmed or a guess? At what moment would it have helped (file, command or error)?
|
|
6
6
|
2. Move general, confirmed items down the promotion ladder as far as they go: `guess → confirmed note → note with a trigger → automated check`. The best memory is one the model never has to read: turn a rule into a lint, test or script and delete the prose.
|
|
7
7
|
3. Write each kept lesson as a knowledge entry (`.ai/knowledge/K-NNNN.md`, see `method/example/K-0001.md`) with triggers, and add one line to `.ai/index.md`.
|
|
8
|
-
4.
|
|
9
|
-
5.
|
|
10
|
-
6.
|
|
8
|
+
4. Is this a procedure someone will repeat? Then draft a recipe (`loop recipe draft`; see method §7 and `templates/recipe.md`). A repeated *procedure* becomes a recipe; a repeated *mistake* still goes note → trigger → check. For an existing recipe, add or change items; never rewrite the file.
|
|
9
|
+
5. Flag entries this task showed to be wrong or stale. Never delete a CONFIRMED entry without new evidence.
|
|
10
|
+
6. Add one line to `.ai/log.md`: date, task, checks result, runs used, what was learned.
|
|
11
|
+
7. Delete `.ai/task.md`.
|
|
11
12
|
|
|
12
13
|
## In a lane
|
|
13
14
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "compounding-loop",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Write the brief, check back tomorrow: unattended, verified Claude Code build loops with a local dashboard.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -66,13 +66,13 @@
|
|
|
66
66
|
"tsx": "^4.23.15",
|
|
67
67
|
"typescript": "^7.0.2",
|
|
68
68
|
"vite": "^8.3.3",
|
|
69
|
-
"vitest": "^5.0.3"
|
|
70
|
-
"yaml": "^2.9.1"
|
|
69
|
+
"vitest": "^5.0.3"
|
|
71
70
|
},
|
|
72
71
|
"dependencies": {
|
|
73
72
|
"@clack/prompts": "^1.8.1",
|
|
74
73
|
"@hono/node-server": "^2.1.4",
|
|
75
74
|
"commander": "^15.0.0",
|
|
76
|
-
"hono": "^4.13.13"
|
|
75
|
+
"hono": "^4.13.13",
|
|
76
|
+
"yaml": "^2.9.1"
|
|
77
77
|
}
|
|
78
78
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "compounding-loop",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Run unattended, verified Claude Code build loops: set up a repo, hand off between windows, run retros and escape stuck loops.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": ["loop", "unattended", "verification", "handoff", "retro"]
|
|
@@ -8,6 +8,7 @@ Set this repository up for unattended, verified build loops.
|
|
|
8
8
|
2. Make sure these exist, creating only what is missing and never overwriting the user's files:
|
|
9
9
|
- `CLAUDE.md` containing the Operating Card (the "Operating Card (the Compounding Loop)" section).
|
|
10
10
|
- `.ai/method.md`, `.ai/index.md`, `.ai/knowledge/`, `.ai/log.md`.
|
|
11
|
+
- `.ai/triggers.yaml` (a starter) and, if the user agrees, the hooks in `.claude/settings.json` (`npx compounding-loop init --hooks`) so triggers fire at the moment they matter. The plugin already ships the same hooks; do not add both.
|
|
11
12
|
- `.ai/task.md` with `Run: 0 / 30`, a Contract with a "Done when" that a command can check, and empty Confirmed, Guesses, Tried and Decisions sections.
|
|
12
13
|
3. Ask the user for the one-sentence goal and the command that proves it works (for example `npm test`) if `IDEA.md` or the task contract does not already say.
|
|
13
14
|
4. Finish with the exact command the user should run next, and do not start the build yourself.
|
package/plugin/commands/retro.md
CHANGED
|
@@ -7,5 +7,6 @@ Run the retro (method section 12) now that the checks pass.
|
|
|
7
7
|
1. Run the checks once more and confirm they pass. If they do not, stop and say so; the task is not done.
|
|
8
8
|
2. Read `.ai/task.md`. List the lessons worth keeping beyond this task (Confirmed facts that will bite again, gotchas, decisions). For each, propose a knowledge entry with its triggers (files, error text, commands, topics) and evidence.
|
|
9
9
|
3. Flag existing entries in `.ai/index.md` that this task showed to be wrong or stale. Do not delete or rewrite a CONFIRMED entry without new evidence.
|
|
10
|
-
4.
|
|
11
|
-
5.
|
|
10
|
+
4. Is this a procedure someone will repeat (a kind of project or feature)? Then draft a recipe: run `loop recipe draft --print-prompt`, or `loop recipe draft` if the user agrees to a Claude call. The draft lands in `.ai/recipes/<id>.draft.md` and is scored by `loop recipe check`. For an existing recipe id the draft is a list of added or changed items, never a full rewrite. A repeated *procedure* becomes a recipe; a repeated *mistake* still goes note -> trigger -> check.
|
|
11
|
+
5. Add one line to `.ai/log.md`: date, task, checks result, number of windows, what was learned.
|
|
12
|
+
6. After the user approves the proposed entries and the recipe draft, write them, update `.ai/index.md`, and delete `.ai/task.md`.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"SessionStart": [
|
|
4
|
+
{
|
|
5
|
+
"hooks": [
|
|
6
|
+
{
|
|
7
|
+
"type": "command",
|
|
8
|
+
"command": "loop hook SessionStart",
|
|
9
|
+
"timeout": 5
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"UserPromptSubmit": [
|
|
15
|
+
{
|
|
16
|
+
"hooks": [
|
|
17
|
+
{
|
|
18
|
+
"type": "command",
|
|
19
|
+
"command": "loop hook UserPromptSubmit",
|
|
20
|
+
"timeout": 5
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"PreToolUse": [
|
|
26
|
+
{
|
|
27
|
+
"matcher": "Bash|Read|Edit|Write|Glob|Grep",
|
|
28
|
+
"hooks": [
|
|
29
|
+
{
|
|
30
|
+
"type": "command",
|
|
31
|
+
"command": "loop hook PreToolUse",
|
|
32
|
+
"timeout": 5
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
"PostToolUse": [
|
|
38
|
+
{
|
|
39
|
+
"matcher": "Bash|Read|Edit|Write|Glob|Grep",
|
|
40
|
+
"hooks": [
|
|
41
|
+
{
|
|
42
|
+
"type": "command",
|
|
43
|
+
"command": "loop hook PostToolUse",
|
|
44
|
+
"timeout": 5
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
],
|
|
49
|
+
"PostToolUseFailure": [
|
|
50
|
+
{
|
|
51
|
+
"matcher": "Bash|Read|Edit|Write|Glob|Grep",
|
|
52
|
+
"hooks": [
|
|
53
|
+
{
|
|
54
|
+
"type": "command",
|
|
55
|
+
"command": "loop hook PostToolUseFailure",
|
|
56
|
+
"timeout": 5
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -77,7 +77,7 @@ jobs:
|
|
|
77
77
|
# ("${{ matrix.lane }}" works for the lane). Short form shown:
|
|
78
78
|
prompt: |
|
|
79
79
|
You are one scheduled run of the ${{ matrix.lane }} lane of an unattended build loop.
|
|
80
|
-
Read CLAUDE.md and follow it exactly. Your task file is .ai/lanes/${{ matrix.lane }}/task.md.
|
|
80
|
+
Read CLAUDE.md and follow it exactly. Your task file is .ai/lanes/${{ matrix.lane }}/task.md. After the pull below, follow .ai/lane-run.md from step 1 with <lane> = ${{ matrix.lane }}: it has the cheap stop checks (a lane not due in .ai/schedule.json replies "Not due" and stops before the install), the steer step (.ai/lanes/${{ matrix.lane }}/steer.md) and the run log.
|
|
81
81
|
Every commit ends with the trailer line "Lane: ${{ matrix.lane }}". Push to main, never to a claude/ branch.
|
|
82
82
|
If .ai/PAUSED exists, the owner paused the loop: reply "Paused" and stop.
|
|
83
83
|
Stop if DONE.md or BLOCKED.md exists at the root or in .ai/lanes/${{ matrix.lane }}/.
|
|
@@ -126,7 +126,7 @@ jobs:
|
|
|
126
126
|
# Paste runners/routine/control-room-prompt.md here with {{name}} and {{lanes}} filled. Short form shown:
|
|
127
127
|
prompt: |
|
|
128
128
|
You are the scheduled control room run of an unattended build loop. Read CLAUDE.md and IDEA.md.
|
|
129
|
-
|
|
129
|
+
Follow .ai/control-run.md from step 1 (schedule backoff and wake, tests, leak check, lane supervision). In short: pull main, run the tests, run `npx compounding-loop check-lanes` over the last 4 hours, read every lane's task.md and outbox,
|
|
130
130
|
and write notes for lanes in `.ai/control-room.md`. Commit only `.ai/` notes and the smallest fixes, each ending with the trailer line "Lane: control". Push to main.
|
|
131
131
|
If .ai/PAUSED exists, the owner paused the loop: reply "Paused" and stop. Stop if DONE.md or BLOCKED.md exists at the root.
|
|
132
132
|
If `git pull --ff-only` fails because the clone started from a stale copy, run `git fetch origin main && git switch -C lane-run FETCH_HEAD` and push with `git push origin HEAD:main`.
|
|
@@ -29,6 +29,10 @@ With several lanes, create one routine per lane plus one control room routine.
|
|
|
29
29
|
|
|
30
30
|
`loop run --lane <name>` fills them for local runs. In the cloud, paste the filled text into the routine. In the cloud nothing writes the lock; it only matters when a local `loop run --lane` is active too. The lane prompt stands down on a fresh `.ai/lanes/<lane>/session.lock`, stops on root or lane `DONE.md`/`BLOCKED.md`, stands down when `.ai/lanes/<lane>/claim` names someone else and has not expired (see `loop claim`), and ends every commit with `Lane: <lane>`.
|
|
31
31
|
|
|
32
|
+
### Thin prompts
|
|
33
|
+
|
|
34
|
+
The lane and control room prompts are short on purpose: they do the pull (with the stale-clone recovery) and say "follow `.ai/lane-run.md`" (control room: `.ai/control-run.md`). Everything else (stop checks, `.ai/schedule.json` "Not due" skip, steering in `.ai/lanes/<lane>/steer.md`, the run log, idle backoff) lives in those two files, which come from `templates/lanes/` (copy them into `.ai/`; `loop init --lanes` will write them once core ships the hook). Change how all lanes run with one commit that ends `Lane: control`; the routine settings stay as they are. A repo that predates them needs the two files before the thin prompts work (copy `lane-run.md` and `control-run.md` from `templates/lanes/` to `.ai/`).
|
|
35
|
+
|
|
32
36
|
### Staggered cron for N lanes
|
|
33
37
|
|
|
34
38
|
Give lane `i` (counting from 0) the minute `7 + i * step` with `step = 12` for up to five lanes (more lanes: use a smaller step, but keep minutes distinct), and put the control room on its own minute every 3 hours. Runs last up to 40 minutes, so neighbouring lanes overlap; that is expected. Path ownership plus `git pull --rebase` before each push make it safe, and the stagger only spreads the pulls out. Avoid minute 0.
|
|
@@ -1,16 +1,6 @@
|
|
|
1
|
-
You are the scheduled "control room" run for this repository ({{name}}). The lanes ({{lanes}}) push to `main` on their own schedules; you do the supervision the owner would otherwise do by hand. Nobody is watching; the owner reads your final summary later.
|
|
1
|
+
You are the scheduled "control room" run for this repository ({{name}}). The lanes ({{lanes}}) push to `main` on their own schedules; you do the supervision the owner would otherwise do by hand. Nobody is watching; the owner reads your final summary later. Every commit you make ends with the trailer line `Lane: control`.
|
|
2
2
|
|
|
3
|
-
0. Run `date -u`. `git checkout main && git pull --ff-only`. If that pull fails because local `main` and `origin/main` have diverged or look unrelated, the clone started from a stale cached copy
|
|
4
|
-
1.
|
|
5
|
-
2. Run the install and test commands from CLAUDE.md. If the tests are red: find the commit that broke it (`git log`, `git bisect` only if quick). If the fix is small and obvious, make it, run the tests, commit with the trailer `Lane: control` and push. Otherwise add a note to `.ai/control-room.md` addressed to the owning lane with the failing command and the likely commit. Also check lane ownership: run `git fetch --unshallow` if the clone is shallow, then `npx compounding-loop check-lanes --range <commit from 4 hours ago>..HEAD` (or `loop check-lanes` if installed), and report each violation to the lane that caused it. If the command is unavailable or errors, say so in the summary and carry on.
|
|
6
|
-
3. Leak check: the published package must contain nothing from `.ai/`, the brief, CLAUDE.md or tests (`npm pack --dry-run`, or your project's equivalent). Grep the repository for secrets (API keys, tokens, private keys) and for the owner's email address and local paths. Fix any leak immediately with the smallest change, push, and say so in the summary.
|
|
7
|
-
4. For each lane read `.ai/lanes/<lane>/task.md` (Run counter, Handoff), its outbox, and whether its `DONE.md` or `BLOCKED.md` exists. Then:
|
|
8
|
-
- BLOCKED with "run budget reached": if the lane made real progress (commits that pushed tests or features) and its task file has no line "Budget extended by control room", raise the limit by 20, add that line with the date under Decisions, `git rm` the BLOCKED.md, commit and push. If already extended once, leave it for the owner.
|
|
9
|
-
- BLOCKED with a question: if it can be answered safely from the brief and CLAUDE.md (never anything that spends money, creates accounts, publishes, posts, changes repo visibility or settings, or adds secrets), write the answer under Decisions in that lane's task file as "Answer from control room (date): ...", `git rm` the BLOCKED.md, commit and push. Otherwise leave it and put the question in your summary.
|
|
10
|
-
- A lane waiting on a lane that is already DONE: tell the waiting lane in `.ai/control-room.md` to stop waiting, list the gap as a next improvement in its DONE.md, and finish.
|
|
11
|
-
- A message in any outbox that has waited more than 3 hours for a lane that has since run without answering: copy it into `.ai/control-room.md` addressed to that lane.
|
|
12
|
-
- Lanes with no commit in the last 4 hours and no DONE/BLOCKED: note "stalled" in the summary. Runs that end in about a second are usually a plan or rate limit, not a bug.
|
|
13
|
-
5. When every `.ai/lanes/*/DONE.md` exists, check every line of the brief's "Done when" for the whole product by running its commands. If all pass, write root `DONE.md`: what was built, test results, anything unsure, and the owner's launch checklist. Commit and push. If a line fails, add a note to `.ai/control-room.md` for the owning lane, `git rm` that lane's DONE.md so it resumes, commit and push.
|
|
14
|
-
6. Commit only `.ai/` notes and the smallest fixes, each with the trailer `Lane: control`. End with one line per lane: `lane · Run N/LIMIT · building|blocked|done|stalled · last commit time · note`, then one line for tests and the leak check, then any question for the owner.
|
|
3
|
+
0. Run `date -u`. Run `git checkout main && git pull --ff-only`. If that pull fails because local `main` and `origin/main` have diverged or look unrelated, the clone started from a stale cached copy; origin `main` is never rewritten, so do not report a rewrite and do not reset anything (check `git log origin/main` before blaming a force-push). Instead run `git fetch origin main && git switch -C lane-run FETCH_HEAD`, work on that branch, and wherever the procedure says pull or push, use `git pull --rebase origin main` and `git push origin HEAD:main`. Push only to `main` on origin. Never force-push. Never create or push a `claude/` branch. Verify claims on a fresh pull: a stale clone is not evidence.
|
|
4
|
+
1. Then follow `.ai/control-run.md` from step 1, exactly. It is the procedure (pause and DONE checks, `.ai/schedule.json` backoff and wake, tests, leak check, lane supervision, round check); CLAUDE.md and IDEA.md are the rules and the brief. If `.ai/control-run.md` is missing, you are on a stale clone (redo step 0) or the repo predates it (copy it from the package's `templates/lanes/`, or follow CLAUDE.md directly).
|
|
15
5
|
|
|
16
|
-
Never spend money, sign up for anything, create accounts, publish anything (no package publish, no posting, no repo visibility or settings changes, no releases), add API keys, secrets, ads, analytics or tracking, force-push, or touch anything outside this repository.
|
|
6
|
+
Never spend money, sign up for anything, create accounts, publish anything (no package publish, no posting, no repo visibility or settings changes, no releases, no tags, no version bump), add API keys, secrets, ads, analytics or tracking, force-push, or touch anything outside this repository.
|
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
You are one scheduled run of the **{{lane}}** lane of an unattended build loop for this repository ({{name}}).
|
|
1
|
+
You are one scheduled run of the **{{lane}}** lane of an unattended build loop for this repository ({{name}}). Other lanes push to the same `main` at other minutes of the hour. Nobody is watching; the owner reads the results later. Your lane is `{{lane}}`; every commit message you write ends with the trailer line `Lane: {{lane}}`.
|
|
2
2
|
|
|
3
|
-
0. Run `date -u` and note it as this run's start time. Run `git checkout main && git pull --ff-only`. If that pull fails because local `main` and `origin/main` have diverged or look unrelated, the clone started from a stale cached copy; origin `main` is never rewritten, so do not report a rewrite and do not reset anything (check `git log origin/main` before blaming a force-push). Instead run `git fetch origin main && git switch -C lane-run FETCH_HEAD`, work on that branch, and
|
|
4
|
-
1.
|
|
5
|
-
2. Run the install command from CLAUDE.md (`npm install` unless it says otherwise). If it fails, write `.ai/lanes/{{lane}}/BLOCKED.md` with the error and stop. If the tests need a browser, install it the way CLAUDE.md says (a blocked download or a Chrome that will not launch is covered under "Commands" there). If CLAUDE.md names no commands, derive them from `package.json` and record them under Decisions.
|
|
6
|
-
3. Read `.ai/control-room.md` and every other lane's `.ai/lanes/*/outbox.md` for messages to {{lane}} or all. Run the resume protocol (.ai/method.md section 9.2) on your task file and add 1 to its run counter. If the counter has reached its limit, follow "Run budget" in CLAUDE.md.
|
|
7
|
-
4. Run the test command from CLAUDE.md (`npm test` unless it says otherwise) on the fresh pull. If it fails, fixing it is your first unit ("main stays green" in CLAUDE.md), whichever lane owns the failure (commit that fix with the trailer `Lane: control`, which `loop check-lanes` allows to touch any path, and tell the owner in your outbox). Otherwise take the next unit: a message addressed to you that another lane is waiting on, else the next item in your task file's units list. Edit only the paths your lane owns, plus the smallest needed edits to shared files. Then run the tests.
|
|
8
|
-
5. Green: commit, have a reviewer subagent check the diff against IDEA.md and your lane contract ("find the three most likely ways this is wrong for a real user, and check each"), fix any blockers, run the tests again, pull, then push to `main`. Run the reviewer in the foreground if you can. If it runs in the background you are resumed when it reports; then finish this step and continue with step 6. Ending your turn ends the run unless a subagent is still running, so never end it to wait for anything else. Red: write the attempt and the reason under Tried in your task file, then discard the unit's uncommitted changes. The same failure twice means the stuck protocol (§9.3); its last step is `.ai/lanes/{{lane}}/BLOCKED.md`.
|
|
9
|
-
6. A pushed unit does not end the run. Go straight back to step 4 with the next unit and keep going until one of these is true: 40 minutes have passed since the start time (check `date -u` after each push), your context is about 60% full, your lane has no units left, or every remaining unit is blocked. Each unit still gets its own tests, review and push.
|
|
10
|
-
7. Run log: before your final commit, whether the run pushed or not (also when the tests failed or you wrote BLOCKED.md or DONE.md), append one line to `.ai/lanes/{{lane}}/runs.jsonl` with `loop runs log --lane {{lane}} --units <unit names> --passed N --failed N --pushed` (or `--no-pushed`, optionally `--note "why"`; use `npx compounding-loop runs log …` if `loop` is not installed, or write the JSON line `{"at":"<UTC ISO>","lane":"{{lane}}","units":[…],"tests":{"passed":N,"failed":N},"pushed":true|false,"note":"…"}` by hand) and commit it in that last commit, so it is pushed with it. Every stop after step 1, including a stop at the run budget or a BLOCKED.md, ends this way. Then run the handoff (.ai/method.md section 9.1) in your task file so a fresh run with zero memory can continue at full quality, and add a line to your outbox for any lane affected by what you shipped (new types, routes, files they are waiting for). If every done-when in your lane contract passes and the reviewer finds no blockers, follow "Lane finished" in CLAUDE.md instead: read the other lanes' outboxes first, run the retro, write `.ai/lanes/{{lane}}/DONE.md`. Commit, push to `main`, and stop.
|
|
3
|
+
0. Run `date -u` and note it as this run's start time. Run `git checkout main && git pull --ff-only`. If that pull fails because local `main` and `origin/main` have diverged or look unrelated, the clone started from a stale cached copy; origin `main` is never rewritten, so do not report a rewrite and do not reset anything (check `git log origin/main` before blaming a force-push). Instead run `git fetch origin main && git switch -C lane-run FETCH_HEAD`, work on that branch, and wherever the procedure says pull or push, use `git pull --rebase origin main` and `git push origin HEAD:main`. Push only to `main` on origin. Never force-push. Never create or push a `claude/` branch. If pushing to main is refused by permissions, push to a branch named `claude/{{lane}}-blocked` and stop.
|
|
4
|
+
1. Then follow `.ai/lane-run.md` from step 1, exactly, with `<lane>` = `{{lane}}`. It is the run procedure (stop checks: `.ai/PAUSED`, DONE/BLOCKED, claim, `.ai/schedule.json` "Not due"; steering in `.ai/lanes/{{lane}}/steer.md`; the run log in `.ai/lanes/{{lane}}/runs.jsonl`); CLAUDE.md (lane table, hard rules, Unattended mode) and IDEA.md are the rules and the brief. Its first step is a cheap stop check: if it says stop, stop at once without installing anything. If `.ai/lane-run.md` is missing, you are on a stale clone (redo step 0) or the repo predates it (copy it from the package's `templates/lanes/`, or follow CLAUDE.md directly).
|
|
11
5
|
|
|
12
|
-
Never spend money, sign up for anything, create accounts, publish anything (no package publish, no posting, no repo visibility or settings changes, no releases, no version bump), add API keys, secrets, ads, analytics or tracking,
|
|
6
|
+
Never spend money, sign up for anything, create accounts, publish anything (no package publish, no posting, no repo visibility or settings changes, no releases, no tags, no version bump), add API keys, secrets, ads, analytics or tracking, force-push, or touch anything outside this repository.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Lane templates
|
|
2
2
|
|
|
3
|
-
Files `loop init --lanes` copies into a repository that runs several lanes. A lane is one scheduled run that owns one part of the codebase (see `method/COMPOUNDING_LOOP.md`, chapter "Lanes").
|
|
3
|
+
Files `loop init --lanes` copies (the four run-model files `lane-run.md`, `control-run.md`, `schedule.json`, `steer.md` once core ships that hook; until then copy them by hand) into a repository that runs several lanes. A lane is one scheduled run that owns one part of the codebase (see `method/COMPOUNDING_LOOP.md`, chapter "Lanes").
|
|
4
4
|
|
|
5
5
|
| File | Written to | Once per |
|
|
6
6
|
|---|---|---|
|
|
@@ -9,6 +9,10 @@ Files `loop init --lanes` copies into a repository that runs several lanes. A la
|
|
|
9
9
|
| `control-room.md` | `.ai/control-room.md` | repo |
|
|
10
10
|
| `claude-section.md` | appended to `CLAUDE.md` and `AGENTS.md`; `loop init --lanes` wraps it in its own markers, so a second run replaces the block | repo |
|
|
11
11
|
| `ci-check-lanes.yml` | `.github/workflows/check-lanes.yml` | repo |
|
|
12
|
+
| `lane-run.md` | `.ai/lane-run.md` | repo: the run procedure every lane routine prompt points at (stop checks incl. "Not due", steering, run log, idle backoff) |
|
|
13
|
+
| `control-run.md` | `.ai/control-run.md` | repo: the control room's procedure (schedule backoff and wake, tests, leak check, supervision) |
|
|
14
|
+
| `schedule.json` | `.ai/schedule.json` | repo: `{"version":1,"lanes":{}}`; a missing lane entry means due |
|
|
15
|
+
| `steer.md` | `.ai/lanes/<lane>/steer.md` | lane |
|
|
12
16
|
|
|
13
17
|
## Placeholders
|
|
14
18
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
This repository is built by parallel **lanes**. Lanes in this repo: {{lanes}}. Each lane is a scheduled run that owns one part of the codebase. `.ai/lanes.json` says which paths each lane owns and which files are shared; read it first. Each lane's task file is `.ai/lanes/<lane>/task.md`.
|
|
4
4
|
|
|
5
|
+
- **Run procedure:** a lane run follows `.ai/lane-run.md` (the control room: `.ai/control-run.md`); the routine prompt only names the lane and does the pull. It holds the cheap stop checks (a lane that is paused, sleeping or before its `notBefore` in `.ai/schedule.json` replies "Not due" and stops before the install), the steering step (`.ai/lanes/<your lane>/steer.md`: `next-run` entries first, `standing` entries until removed, each acknowledged with one `→` line), the run log and the idle backoff. Only `Lane: control` commits change those two files.
|
|
5
6
|
- **Edit only the paths your lane owns.** Read anything. **Shared files** (`shared` in `.ai/lanes.json`) may be edited by any lane, with the smallest possible edit.
|
|
6
7
|
- **Commit trailer:** every commit message ends with a line `Lane: <your lane>` (the control room uses `Lane: control`). `loop check-lanes` fails a commit that touches another lane's paths.
|
|
7
8
|
- **Talking to other lanes:** write only to your own `.ai/lanes/<you>/outbox.md`, newest first, `YYYY-MM-DD HH:MM UTC · to <lane|all> · message`. At the start of each run read every other lane's outbox and `.ai/control-room.md` for messages to you or `all`, and act on them or say in your task file why not.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Control room run procedure
|
|
2
|
+
|
|
3
|
+
The control room routine's prompt says "follow `.ai/control-run.md`". The prompt itself does step 0 (pull, stale clone). You do the supervision the owner would otherwise do by hand. Be brief and cheap. Read CLAUDE.md (lane table, hard rules, Unattended mode) and IDEA.md (the current brief, with its "done when" list and any deadline). Every commit you make ends with the trailer line `Lane: control`.
|
|
4
|
+
|
|
5
|
+
1. If root `DONE.md` exists, give the summary (step 7) and stop. If `.ai/PAUSED` exists, reply "Paused" and stop.
|
|
6
|
+
2. **Schedule** (cheap, before the install). Read `.ai/schedule.json`, each lane's `runs.jsonl`, `steer.md`, outbox and `.ai/control-room.md`.
|
|
7
|
+
- Run `npx compounding-loop schedule update` if your installed version has it. Otherwise apply the rule by hand:
|
|
8
|
+
- backoff for idle lanes;
|
|
9
|
+
- **wake** a lane that is sleeping or waiting on `notBefore` (`sleeping: false`, `notBefore: null`) when it has a steer entry with no `→` line, or a message addressed to it newer than its last run.
|
|
10
|
+
- Never change `paused` (only the owner pauses a lane).
|
|
11
|
+
- If nothing else in this run needs work, commit any schedule change and skip to step 7. "Nothing else" means: no lane committed since your last run, and no lane has DONE.md that hasn't been checked yet.
|
|
12
|
+
3. Run the install and test commands from CLAUDE.md (`npm install` and `npm test` unless it says otherwise).
|
|
13
|
+
- If it is red, find the commit that broke it (`git log`, `git bisect` only if quick).
|
|
14
|
+
- If the fix is small and obvious, make it, run the tests, commit and push.
|
|
15
|
+
- Otherwise add a note to `.ai/control-room.md` addressed to the owning lane, with the failing command and the likely commit.
|
|
16
|
+
4. **Leak check:**
|
|
17
|
+
- If the project publishes a package, a dry-run pack (`npm pack --dry-run` or your equivalent) must list nothing from `.ai/`, the brief, `CLAUDE.md` or `tests/`.
|
|
18
|
+
- Grep the repo for secrets (API keys, tokens, private keys) and for the owner's email address. Fix any leak immediately (smallest change), push, and say so in the summary.
|
|
19
|
+
- Run `npx compounding-loop check-lanes` over the commits since your last run and tell each lane that crossed its paths in `.ai/control-room.md`.
|
|
20
|
+
5. For each lane, read `.ai/lanes/<lane>/task.md` (run counter, handoff), its outbox, `.ai/audit/<lane>.md` (open blockers in the current round's section), and whether `.ai/lanes/<lane>/DONE.md`, `BLOCKED.md` or an unexpired `claim` exists. Then:
|
|
21
|
+
- **BLOCKED with "run budget reached":** if the lane made real progress (commits that pushed tests or features) and its task file has no line "Budget extended by control room", raise the limit by 15, add that line with the date under Decisions, `git rm` the BLOCKED.md, commit, push. If it was already extended once, leave it for the owner.
|
|
22
|
+
- **BLOCKED with a question:** if it can be answered safely from IDEA.md and CLAUDE.md (never anything that spends money, creates accounts, publishes, posts, changes repo visibility or settings, or adds secrets), write the answer under Decisions in that lane's task file as "Answer from control room (date): ...", `git rm` the BLOCKED.md, commit, push. Otherwise leave it and put the question in your summary for the owner.
|
|
23
|
+
- **A message waiting in an outbox:** if it has waited more than 3 hours for a lane that has since run without answering, copy it into `.ai/control-room.md` addressed to that lane, and wake that lane. If the lane it waits on has DONE.md, tell the waiting lane to stop waiting and either work around it or list it as a next improvement.
|
|
24
|
+
- **Stalled lanes:** a lane that is due in schedule.json, has no runs.jsonl line in the last 4 hours, no DONE/BLOCKED and no unexpired claim is "stalled" in the summary (likely the plan's five-hour usage limit). A lane that isn't due is "sleeping" or "not due until <time>", not stalled. A lane with an unexpired claim is "claimed by <name>".
|
|
25
|
+
- **After the deadline** of the current round in IDEA.md: tell every lane without DONE.md, once, in `.ai/control-room.md`, to finish its open unit, start nothing new and write DONE.md. Wake those lanes.
|
|
26
|
+
6. **Round check:** when every lane in `.ai/lanes.json` has a `.ai/lanes/<lane>/DONE.md`, check every line of the current round's "done when" list in IDEA.md by running its commands. Run the fresh-install check the list names (for a package: pack it, install it in an empty temp dir, follow the README quick start word for word).
|
|
27
|
+
- If all pass, write root `DONE.md`, then commit and push. It contains:
|
|
28
|
+
- what was built and the test results;
|
|
29
|
+
- the minor audit findings left open (from `.ai/audit/*.md`) and anything unsure;
|
|
30
|
+
- the owner's release checklist: the owner bumps the version, finalises the changelog, runs the tests and publishes; the control room never publishes.
|
|
31
|
+
- If a line fails, add a note to `.ai/control-room.md` for the owning lane, `git rm` that lane's DONE.md so it resumes, wake that lane, commit and push.
|
|
32
|
+
7. Commit only `.ai/` notes, `.ai/schedule.json` and the smallest fixes. End with:
|
|
33
|
+
- one line per lane: `lane · Run N/LIMIT · building|sleeping|not due until T|blocked|done|stalled|claimed · last run · open blockers · note`;
|
|
34
|
+
- one line for tests and the leak check;
|
|
35
|
+
- any question for the owner.
|
|
36
|
+
|
|
37
|
+
Never spend money, sign up for anything, create accounts, publish anything (no npm publish, no posting, no repo visibility or settings changes, no releases, no tags, no version bump), add API keys, secrets, ads, analytics or tracking, force-push, or touch anything outside this repository.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Lane run procedure
|
|
2
|
+
|
|
3
|
+
Every lane routine's prompt names its lane `<lane>` and says "follow `.ai/lane-run.md`". The prompt itself does step 0 (pull, stale clone). This file is the rest. Change it with a commit that ends `Lane: control`; lane runs never edit it. Wherever the Operating Card or `.ai/method.md` says `.ai/task.md`, use `.ai/lanes/<lane>/task.md`. Every commit message you write ends with the trailer line `Lane: <lane>`.
|
|
4
|
+
|
|
5
|
+
1. **Cheap stop checks. Do these before anything else, with no `npm install`.** Reply with the word in quotes and stop, without committing:
|
|
6
|
+
- root `DONE.md` or `BLOCKED.md`, or `.ai/lanes/<lane>/DONE.md` or `BLOCKED.md` exists → "Nothing to do";
|
|
7
|
+
- `.ai/PAUSED` exists → "Paused";
|
|
8
|
+
- `.ai/lanes/<lane>/claim` names someone else and its expiry is in the future → "Nothing to do";
|
|
9
|
+
- in `.ai/schedule.json`, your lane's entry has `paused: true`, `sleeping: true`, or a `notBefore` later than now → "Not due". A missing file or a missing entry means due.
|
|
10
|
+
2. Run the install command from CLAUDE.md (`npm install` unless it says otherwise), and install the browser the way CLAUDE.md says if the tests need one. If the install fails, write `.ai/lanes/<lane>/BLOCKED.md` with the error and stop.
|
|
11
|
+
3. Read `.ai/control-room.md` and every other lane's `.ai/lanes/*/outbox.md` for messages to `<lane>` or all. Read `.ai/lanes/<lane>/steer.md` (if it exists): the entries with no `→` line under them are this run's steering. `next-run` entries are done before anything else in step 4. `standing` entries apply to everything you do until the owner removes them. Run the resume protocol (.ai/method.md section 9.2) on your task file and add 1 to its run counter. If the counter has reached its limit, follow "Run budget" in CLAUDE.md.
|
|
12
|
+
4. Run the test command from CLAUDE.md (`npm test` unless it says otherwise) on the fresh pull. If it fails, fixing it is your first unit ("main stays green" in CLAUDE.md). Otherwise take the next unit in this order:
|
|
13
|
+
1. an unacknowledged `next-run` steer entry;
|
|
14
|
+
2. a message addressed to you that another lane is waiting on;
|
|
15
|
+
3. the next item in your task file's units list.
|
|
16
|
+
|
|
17
|
+
A steer entry that conflicts with IDEA.md's hard rules or "Never" list is not done: acknowledge it with the reason. Edit only the paths your lane owns, plus the smallest needed edits to shared files. Then run the tests.
|
|
18
|
+
5. Green: commit, have a reviewer subagent check the diff against IDEA.md, your lane contract and any steer entry it answers. Fix any blockers, run the tests again, pull, then push to `main`. Run the reviewer in the foreground if you can. If it runs in the background you are resumed when it reports; then finish this step and continue with step 6. Ending your turn ends the run unless a subagent is still running, so never end it to wait for anything else. Red: write the attempt and the reason under Tried in your task file, then discard the unit's uncommitted changes.
|
|
19
|
+
6. A pushed unit does not end the run. Go straight back to step 4 with the next unit. Keep going until one of these is true:
|
|
20
|
+
- 40 minutes have passed since the start time (check `date -u` after each push);
|
|
21
|
+
- your context is about 60% full;
|
|
22
|
+
- your lane has no units left;
|
|
23
|
+
- every remaining unit is blocked.
|
|
24
|
+
|
|
25
|
+
This replaces the "when a unit is done" handoff trigger in .ai/method.md. Each unit still gets its own tests, review and push.
|
|
26
|
+
7. Acknowledge every steer entry you read in step 3, by adding exactly one line directly under it: `→ done in run N` or `→ not done: <why>`. Do this for a `standing` entry only once, the first time you read it. This is the only edit you make to `steer.md`. Then run the handoff (.ai/method.md section 9.1) in your task file, so that a fresh run with zero memory can continue at full quality. Add a line to your outbox for any lane affected by what you shipped (new types, routes, files they are waiting for). If every done-when in your lane contract passes, follow "Lane finished" in CLAUDE.md.
|
|
27
|
+
8. **Always, even when the run did nothing**, do two things before the last commit:
|
|
28
|
+
- Append one line to `.ai/lanes/<lane>/runs.jsonl`. Use `npx compounding-loop runs log --lane <lane> --units <names> --passed N --failed N --pushed|--no-pushed [--note …]`, or write this JSON shape by hand: `{"at":ISO,"lane":"<lane>","units":[…],"tests":{"passed":N,"failed":N},"pushed":bool,"note":"…"}`.
|
|
29
|
+
- If this run shipped no unit, apply the idle backoff to **your own entry only** in `.ai/schedule.json`. Use `npx compounding-loop schedule update` if your installed version has it. Otherwise apply the rule by hand: count your trailing idle runs in runs.jsonl. With 1, set `notBefore` to now + 3 h. With 2, set it to now + 6 h. With 3 or more, set `sleeping: true`. Set `reason` to a few words. If your lane's DONE.md now exists, leave schedule.json alone.
|
|
30
|
+
|
|
31
|
+
Then commit, push to `main`, and stop.
|
|
32
|
+
|
|
33
|
+
Never spend money, sign up for anything, create accounts, publish anything (no npm publish, no posting, no repo visibility or settings changes, no releases, no tags, no version bump, no `release.yml` dispatch), add API keys, secrets, ads, analytics or tracking, copy other projects' content without a compatible licence, force-push, edit `.ai/lane-run.md` or `.ai/control-run.md`, or touch anything outside this repository.
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
# Steering for the {{lane}} lane
|
|
2
|
+
|
|
3
|
+
Notes from the owner or a teammate, newest first. Entry format: `## YYYY-MM-DD HH:MM UTC · <author> · next-run|standing`, then the text. The lane adds one line under each entry it has read: `→ done in run N` or `→ not done: <why>` (see `.ai/lane-run.md` steps 3 and 7). Write entries with `loop steer`, the dashboard's Steer button, or by hand.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: R-your-slug
|
|
3
|
+
title: Short name of the kind of project or feature
|
|
4
|
+
kind: feature
|
|
5
|
+
stack:
|
|
6
|
+
- node@20
|
|
7
|
+
tags:
|
|
8
|
+
- one-word-tag
|
|
9
|
+
status: GUESS
|
|
10
|
+
source: owner/repo at commit abc1234
|
|
11
|
+
uses: 0
|
|
12
|
+
passes: 0
|
|
13
|
+
updated: 2026-01-01
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
<!-- Copy to .ai/recipes/R-<slug>.md. Keep it under 6 KB: a recipe is the small exact context, not a manual.
|
|
17
|
+
Check it with `loop recipe check`. Change an existing recipe by adding or editing items, never by regenerating the file.
|
|
18
|
+
Induce it only from work whose checks passed. No absolute paths, emails or secrets. -->
|
|
19
|
+
|
|
20
|
+
## When to use
|
|
21
|
+
|
|
22
|
+
One paragraph: the kind of task this fits, and when it does not.
|
|
23
|
+
|
|
24
|
+
## Steps
|
|
25
|
+
|
|
26
|
+
1. Do the first thing. Proof: `npm test`.
|
|
27
|
+
2. Do the next thing. Proof: `node --test`.
|
|
28
|
+
|
|
29
|
+
## Traps
|
|
30
|
+
|
|
31
|
+
- One line per trap, with what covers it (K-0001 or a trigger id).
|
|
32
|
+
|
|
33
|
+
## Checks
|
|
34
|
+
|
|
35
|
+
- `npm test`
|
|
36
|
+
|
|
37
|
+
## Start from
|
|
38
|
+
|
|
39
|
+
- The template or files to copy first.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: R-cli-command-json
|
|
3
|
+
title: Add a CLI subcommand with --json output and tests
|
|
4
|
+
kind: feature
|
|
5
|
+
stack:
|
|
6
|
+
- node@20
|
|
7
|
+
tags:
|
|
8
|
+
- cli
|
|
9
|
+
- json
|
|
10
|
+
- subcommand
|
|
11
|
+
- command
|
|
12
|
+
status: CONFIRMED
|
|
13
|
+
source: Minntooos/Compounding-Loop src/cli and tests/unit/cli, rounds 4 to 6
|
|
14
|
+
uses: 0
|
|
15
|
+
passes: 0
|
|
16
|
+
updated: 2026-10-14
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## When to use
|
|
20
|
+
|
|
21
|
+
A Node command-line tool needs one more subcommand that reports something, for people (a table) and for scripts (`--json`). Not for commands that only wrap another tool.
|
|
22
|
+
|
|
23
|
+
## Steps
|
|
24
|
+
|
|
25
|
+
1. Write the logic as a pure function that takes its inputs and returns a plain object (no printing, no `process.exit`). Proof: `npm test` runs its unit test.
|
|
26
|
+
2. Test it first: the normal result, an empty or missing input, and a bad argument that throws a message saying how to fix it. Use a temp folder, never the real repo. Proof: `npm test`.
|
|
27
|
+
3. Register the subcommand with a one-line description and a `--json` option. With `--json` print only `JSON.stringify(result, null, 2)` to stdout; without it print the human text. Proof: a test runs the command and parses stdout.
|
|
28
|
+
4. Make errors short and actionable: print them to stderr and set `process.exitCode = 1` instead of exiting mid-stream. Proof: a test asserts the exit code and the message.
|
|
29
|
+
5. Add the command to `--help` and the README usage list. Proof: `node bin/<tool>.js --help`.
|
|
30
|
+
6. Run the whole suite. Proof: `npm test`.
|
|
31
|
+
|
|
32
|
+
## Traps
|
|
33
|
+
|
|
34
|
+
- Any extra `console.log` breaks `--json` for scripts; log progress to stderr.
|
|
35
|
+
- Build paths with `node:path`, never a hard-coded `/` (Windows).
|
|
36
|
+
- Start child processes with `execFile` and an argument array, never a shell string.
|
|
37
|
+
- Tests that use real temp folders can hit `EBUSY` on Windows at cleanup; pass `maxRetries` to `rm` (K-0002).
|
|
38
|
+
- A JSON shape is a contract: add fields, never rename them.
|
|
39
|
+
|
|
40
|
+
## Checks
|
|
41
|
+
|
|
42
|
+
- `npm test`
|
|
43
|
+
- `node bin/<tool>.js <command> --json`
|
|
44
|
+
|
|
45
|
+
## Start from
|
|
46
|
+
|
|
47
|
+
- An existing subcommand in the same file (copy its option handling) and its test file.
|