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.
Files changed (128) hide show
  1. package/README.md +152 -128
  2. package/dist/cli/draft.js +69 -0
  3. package/dist/cli/draft.js.map +1 -0
  4. package/dist/cli/eval.js +186 -0
  5. package/dist/cli/eval.js.map +1 -0
  6. package/dist/cli/hook.js +92 -0
  7. package/dist/cli/hook.js.map +1 -0
  8. package/dist/cli/index.js +336 -1
  9. package/dist/cli/index.js.map +1 -1
  10. package/dist/cli/init.js +41 -4
  11. package/dist/cli/init.js.map +1 -1
  12. package/dist/cli/lanesInit.js +8 -2
  13. package/dist/cli/lanesInit.js.map +1 -1
  14. package/dist/cli/pace.js +41 -0
  15. package/dist/cli/pace.js.map +1 -0
  16. package/dist/cli/pause.js +3 -3
  17. package/dist/cli/pause.js.map +1 -1
  18. package/dist/cli/recipe.js +89 -0
  19. package/dist/cli/recipe.js.map +1 -0
  20. package/dist/cli/recipeDraft.js +64 -0
  21. package/dist/cli/recipeDraft.js.map +1 -0
  22. package/dist/cli/runs.js +49 -11
  23. package/dist/cli/runs.js.map +1 -1
  24. package/dist/cli/schedule.js +177 -0
  25. package/dist/cli/schedule.js.map +1 -0
  26. package/dist/core/brief.js +35 -12
  27. package/dist/core/brief.js.map +1 -1
  28. package/dist/core/draftBrief.js +68 -0
  29. package/dist/core/draftBrief.js.map +1 -0
  30. package/dist/core/eval.js +123 -0
  31. package/dist/core/eval.js.map +1 -0
  32. package/dist/core/hooksInit.js +54 -0
  33. package/dist/core/hooksInit.js.map +1 -0
  34. package/dist/core/laneRound.js +28 -7
  35. package/dist/core/laneRound.js.map +1 -1
  36. package/dist/core/lanes.js +3 -0
  37. package/dist/core/lanes.js.map +1 -1
  38. package/dist/core/lanesInit.js +19 -0
  39. package/dist/core/lanesInit.js.map +1 -1
  40. package/dist/core/parse/index.js +8 -0
  41. package/dist/core/parse/index.js.map +1 -1
  42. package/dist/core/parse/outcomes.js +51 -0
  43. package/dist/core/parse/outcomes.js.map +1 -0
  44. package/dist/core/parse/policy.js +56 -0
  45. package/dist/core/parse/policy.js.map +1 -0
  46. package/dist/core/parse/runs.js +1 -0
  47. package/dist/core/parse/runs.js.map +1 -1
  48. package/dist/core/parse/schedule.js +101 -0
  49. package/dist/core/parse/schedule.js.map +1 -0
  50. package/dist/core/parse/steer.js +68 -0
  51. package/dist/core/parse/steer.js.map +1 -0
  52. package/dist/core/recipeDraft.js +37 -0
  53. package/dist/core/recipeDraft.js.map +1 -0
  54. package/dist/core/recipes.js +185 -0
  55. package/dist/core/recipes.js.map +1 -0
  56. package/dist/core/repo.js +16 -1
  57. package/dist/core/repo.js.map +1 -1
  58. package/dist/core/triggers.js +137 -0
  59. package/dist/core/triggers.js.map +1 -0
  60. package/dist/server/actionBody.js +54 -0
  61. package/dist/server/actionBody.js.map +1 -0
  62. package/dist/server/checkLabels.js +7 -2
  63. package/dist/server/checkLabels.js.map +1 -1
  64. package/dist/server/claude.js +18 -0
  65. package/dist/server/claude.js.map +1 -0
  66. package/dist/server/controls.js +28 -1
  67. package/dist/server/controls.js.map +1 -1
  68. package/dist/server/data.js +8 -3
  69. package/dist/server/data.js.map +1 -1
  70. package/dist/server/draftInputs.js +33 -0
  71. package/dist/server/draftInputs.js.map +1 -0
  72. package/dist/server/health.js +3 -3
  73. package/dist/server/health.js.map +1 -1
  74. package/dist/server/index.js +55 -0
  75. package/dist/server/index.js.map +1 -1
  76. package/dist/server/laneChecks.js +1 -1
  77. package/dist/server/laneChecks.js.map +1 -1
  78. package/dist/server/laneSchedule.js +15 -0
  79. package/dist/server/laneSchedule.js.map +1 -0
  80. package/dist/server/live/build.js +25 -10
  81. package/dist/server/live/build.js.map +1 -1
  82. package/dist/server/live/index.js +4 -2
  83. package/dist/server/live/index.js.map +1 -1
  84. package/dist/server/live/urls.js +3 -1
  85. package/dist/server/live/urls.js.map +1 -1
  86. package/dist/server/loops.js +145 -20
  87. package/dist/server/loops.js.map +1 -1
  88. package/dist/server/memory.js +115 -0
  89. package/dist/server/memory.js.map +1 -0
  90. package/dist/server/memory.json +398 -0
  91. package/dist/server/paceNote.js +13 -0
  92. package/dist/server/paceNote.js.map +1 -0
  93. package/dist/server/roundState.js +26 -2
  94. package/dist/server/roundState.js.map +1 -1
  95. package/dist/server/runs.js +72 -9
  96. package/dist/server/runs.js.map +1 -1
  97. package/dist/server/shipped.js +35 -0
  98. package/dist/server/shipped.js.map +1 -0
  99. package/dist/server/static-export.js +8 -2
  100. package/dist/server/static-export.js.map +1 -1
  101. package/dist/server/this-repo.json +2367 -858
  102. package/dist/web/assets/index-C5q9d5-n.css +2 -0
  103. package/dist/web/assets/index-DDEAcJFt.js +123 -0
  104. package/dist/web/index.html +2 -2
  105. package/method/COMPOUNDING_LOOP.md +33 -6
  106. package/method/operating-card.md +1 -1
  107. package/method/retro.md +4 -3
  108. package/package.json +4 -4
  109. package/plugin/.claude-plugin/plugin.json +1 -1
  110. package/plugin/commands/loop-init.md +1 -0
  111. package/plugin/commands/retro.md +3 -2
  112. package/plugin/hooks/hooks.json +62 -0
  113. package/runners/github-actions/lanes.yml +2 -2
  114. package/runners/routine/README.md +4 -0
  115. package/runners/routine/control-room-prompt.md +4 -14
  116. package/runners/routine/lane-prompt.md +4 -10
  117. package/templates/lanes/README.md +5 -1
  118. package/templates/lanes/claude-section.md +1 -0
  119. package/templates/lanes/control-run.md +37 -0
  120. package/templates/lanes/lane-run.md +33 -0
  121. package/templates/lanes/schedule.json +4 -0
  122. package/templates/lanes/steer.md +3 -0
  123. package/templates/recipe.md +39 -0
  124. package/templates/recipes/cli-command-json.md +47 -0
  125. package/templates/recipes/static-calculator-site.md +50 -0
  126. package/templates/static-site/CLAUDE.md +1 -1
  127. package/dist/web/assets/index-BOPytDNU.js +0 -53
  128. package/dist/web/assets/index-BSHEI7fa.css +0 -2
package/README.md CHANGED
@@ -1,128 +1,152 @@
1
- # Compounding Loop
2
-
3
- **Write the brief, check back tomorrow.** Several unattended Claude Code lanes, one repo, each lane owning its own paths so they do not step on each other, every round verified by your own tests. Local dashboard included.
4
-
5
- ```sh
6
- npx compounding-loop init
7
- ```
8
-
9
- [![npm](https://img.shields.io/npm/v/compounding-loop)](https://www.npmjs.com/package/compounding-loop) [![CI](https://github.com/Minntooos/Compounding-Loop/actions/workflows/ci.yml/badge.svg)](https://github.com/Minntooos/Compounding-Loop/actions/workflows/ci.yml) [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
10
-
11
- **[Watch this repo build itself](https://minntooos.github.io/Compounding-Loop/)**: the live demo reads this repo's commits from GitHub in your browser, next to the real five-site data. Working with a teammate? [Five steps](docs/team.md).
12
-
13
- ![The Compounding Loop fleet screen: six loops, one with lanes](docs/assets/screens/fleet-light-laptop.png)
14
-
15
- ![A loop's detail screen in the dark theme](docs/assets/screens/loop-dark-laptop.png)
16
-
17
- **Lanes:** each lane is a scheduled run that owns a set of paths (`.ai/lanes.json`). `loop check-lanes` fails any commit that touches files outside its lane, so parallel runs do not collide. This repo is the proof: five lanes (core, kit, server, web, docs) built and documented it by pushing to one `main`. See [Lanes](docs/lanes.md).
18
-
19
- Most agent tools show a demo. This one ships with proof: the method in this repo built five live sites, 215 pages in all, with nobody watching, and made **verification** the product. A round only counts when the repo's own tests pass and a reviewer that did not write the code has checked the diff.
20
-
21
- ## Proof
22
-
23
- Five calculator sites, each built by scheduled Claude Code runs working from a written brief. Numbers come from the repo data in [`demo/five-sites.json`](demo/five-sites.json).
24
-
25
- | Site | Pages | Tests passing | Commits | Rounds | Elapsed* |
26
- |---|---:|---:|---:|---:|---:|
27
- | [powerstationruntime.netlify.app](https://powerstationruntime.netlify.app) | 55 | 278 | 112 | 5 / 5 | 22 h |
28
- | [clearpoolmath.netlify.app](https://clearpoolmath.netlify.app) | 40 | 543 | 83 | 5 / 5 | 22 h |
29
- | [yarnsums.netlify.app](https://yarnsums.netlify.app) | 40 | 419 | 109 | 5 / 5 | 19 h |
30
- | [quiltcalculators.netlify.app](https://quiltcalculators.netlify.app) | 43 | 452 | 77 | 5 / 5 | 18 h |
31
- | [mathtank.netlify.app](https://mathtank.netlify.app) | 37 | 433 | 83 | 5 / 5 | 18 h |
32
- | **Total** | **215** | **2,125** | **464** | | |
33
-
34
- \*Wall-clock time from the first to the last commit, rounded. It is not the time a human spent: that was writing five briefs.
35
-
36
- ## How it works
37
-
38
- ```mermaid
39
- flowchart LR
40
- B[Brief] --> T[task.md contract]
41
- T --> R[Scheduled run]
42
- R --> U[One unit of work]
43
- U --> C{npm test}
44
- C -- red --> X[Log under Tried, discard]
45
- C -- green --> V[Reviewer subagent]
46
- V --> P[Push]
47
- P --> K[Retro: knowledge]
48
- K --> R
49
- R -. stuck .-> Q[BLOCKED.md: your inbox]
50
- ```
51
-
52
- 1. **Brief and contract.** You write what "done" means as commands that can be checked.
53
- 2. **Scheduled runs.** A routine, a GitHub Action or `loop run` wakes up, reads its task file and does one unit at a time.
54
- 3. **Checks decide.** The agent never declares itself done; `npm test` does.
55
- 4. **Reviewer gate.** A fresh subagent attacks the diff before it is pushed.
56
- 5. **Compounding.** Every run writes what it learned into a small indexed knowledge base the next run reads first.
57
- 6. **Escalation.** When it is stuck it writes `BLOCKED.md`. You answer from the dashboard inbox or `loop answer`.
58
-
59
- ## Quick start
60
-
61
- ```sh
62
- npx compounding-loop new static-site my-site # a new loop repo from a template
63
- cd my-site
64
- npx compounding-loop run # one round now
65
- npx compounding-loop dashboard # the local dashboard
66
- ```
67
-
68
- Several lanes in one repo (needs an `IDEA.md` brief first): `npx compounding-loop init --lanes core,web,docs`, then `npx compounding-loop run --lane core`. Check ownership with `npx compounding-loop check-lanes`. Details: [Lanes](docs/lanes.md). Sharing a loop with a teammate: [Work on a loop with a teammate](docs/team.md).
69
-
70
- **Run the loop without tokens.** Pause every lane (`npx compounding-loop pause`, undo with `resume`), start the next round from a brief in a laned repo (`next-round --brief`), see which scheduled runs happened (`runs`) and check whether you can ship (`ship-check`). The dashboard has buttons for all of it (Pause all, Start next round, Release), each with a confirm step, plus the run history and a Ready to ship checklist: see [Dashboard](docs/dashboard.md#controls). Releases go through a GitHub Actions workflow with npm trusted publishing and a protected `release` environment: see [Release](docs/release.md).
71
-
72
- Already have a repo? `npx compounding-loop init` adds the method to it without overwriting anything. Details: [Getting started](docs/getting-started.md).
73
-
74
- ## Just loop the agent?
75
-
76
- An honest comparison with `while true; do claude -p "keep going"; done`:
77
-
78
- | | Plain loop | Compounding Loop |
79
- |---|---|---|
80
- | Knows when it is done | When the model says so | When your checks pass |
81
- | Reviews its own work | Same context, same blind spots | A fresh reviewer must find no blockers |
82
- | Survives context limits | Starts cold every time | Writes a handoff the next run resumes from |
83
- | Learns from mistakes | No | Notes and a retro, indexed |
84
- | Overlapping runs | Collide on the same files | Session lock |
85
- | Stuck for hours | Burns budget | Run budget, then `BLOCKED.md` |
86
- | You can see what happened | `git log` | Fleet dashboard and inbox |
87
-
88
- If your task is one afternoon of work and you are at the keyboard, a plain loop is fine. This pays off for work that runs for days.
89
-
90
- ## How it differs from other tools
91
-
92
- These tools solve a neighbouring problem: running several agents side by side while you watch. Compounding Loop is for work that runs unattended and has to prove itself. Descriptions come from each project's own page (checked 2026-10-08).
93
-
94
- | Tool | What it is for | Where Compounding Loop differs |
95
- |---|---|---|
96
- | [Claude Squad](https://github.com/smtg-ai/claude-squad) | A terminal app that manages several agents, each in its own tmux session and git worktree; an experimental `-y` flag auto-accepts prompts. | Squad isolates work by branch. We isolate by path ownership on one `main`, and a round only counts when your tests and a reviewer pass. |
97
- | [Parallel Code](https://github.com/johannesjo/parallel-code) | Runs several agents at once, each in its own git worktree; you merge the branch back yourself. | Interactive and merge-driven. We are scheduled, and the agent never marks itself done. |
98
- | [Agent Orchestrator](https://github.com/ComposioHQ/agent-orchestrator) | A local desktop workspace where an orchestrator delegates work to agents on separate branches and worktrees, shown on a Kanban board. | A desktop planner. We are a plain-file method (task file, notes, handoffs) you can read in git. |
99
- | [Vibe Kanban](https://github.com/BloopAI/vibe-kanban) | Plan issues on a kanban board, then run coding agents in workspaces with a branch and a terminal. Its page says it is sunsetting. | We have no board; the contract is a file of checkable commands. |
100
- | [Claude Code agent teams](https://code.claude.com/docs/en/agent-teams) | Experimental, off by default: a lead session coordinates teammates with a shared task list and messages. Needs an interactive session (not `claude -p`). | Teams are live and interactive. Lanes are scheduled, unattended and leave a git trail. They can be combined. |
101
-
102
- If you work at the keyboard and want to watch agents in parallel, those tools are a good fit.
103
-
104
- ## Docs
105
-
106
- [Getting started](docs/getting-started.md) · [Lanes](docs/lanes.md) · [Team](docs/team.md) · [Concepts](docs/concepts.md) · [Runners](docs/runners.md) · [Dashboard](docs/dashboard.md) · [Release](docs/release.md) · [Templates](docs/templates.md) · [FAQ](docs/faq.md) · [Troubleshooting](docs/troubleshooting.md) · [Contributing](CONTRIBUTING.md)
107
-
108
- ## FAQ
109
-
110
- **Isn't this cron + `claude -p`?** Partly, and the table above shows what is added: your checks decide when a round is done, a fresh reviewer attacks the diff, each run writes a handoff and notes the next run reads, a run budget stops runaway loops, and lanes keep parallel runs out of each other's files.
111
-
112
- **What does it cost (plan usage vs API)?** The tool is free and MIT licensed. Runs use whatever your Claude Code login uses: plan usage on a subscription, or API billing if you authenticate with a key. The `Run: N / LIMIT` budget caps how many runs happen. More lanes means more runs, so cost scales with lanes times runs.
113
-
114
- **Why not agent teams?** Agent teams are experimental, off by default and need an interactive session ([docs](https://code.claude.com/docs/en/agent-teams)). Lanes are for scheduled runs nobody is watching. Use teams when you are at the keyboard; use lanes when you are away.
115
-
116
- **What happens when it goes wrong?** A red round is discarded and logged under Tried. The same failure twice triggers the stuck protocol, then a `BLOCKED.md` appears in your dashboard inbox. When the run budget is reached the lane stops. Everything is a git commit, so it is reversible. See [Troubleshooting](docs/troubleshooting.md).
117
-
118
- **Is my code sent anywhere?** Only what Claude Code and git already send. Compounding Loop has no telemetry and no call home, the dashboard binds to loopback, and tokens come from your environment or `gh` and are never stored. See [SECURITY.md](SECURITY.md).
119
-
120
- **What won't it do?** It will not publish, spend money, create accounts or change repo settings on its own, and it stops and asks (`BLOCKED.md`) rather than guess.
121
-
122
- **Why does the live demo sometimes say "Snapshot"?** The demo page reads this repo from GitHub's public API without a login, which allows 60 requests an hour per IP address (a page load uses about three). When that runs out, or you are offline, it shows the bundled snapshot and the header chip says so; it switches back to live on the next refresh once GitHub allows requests again. Nothing is sent anywhere else.
123
-
124
- **Does it need a GitHub login?** Only to create a private repo with `loop new`; `--dry-run` and every test work offline.
125
-
126
- ## License
127
-
128
- MIT. See [LICENSE](LICENSE).
1
+ # Compounding Loop
2
+
3
+ **Write the brief, check back tomorrow.** Several unattended Claude Code lanes, one repo, each lane owning its own paths so they do not step on each other, every round verified by your own tests. Local dashboard included.
4
+
5
+ ```sh
6
+ npx compounding-loop init
7
+ ```
8
+
9
+ [![npm](https://img.shields.io/npm/v/compounding-loop)](https://www.npmjs.com/package/compounding-loop) [![CI](https://github.com/Minntooos/Compounding-Loop/actions/workflows/ci.yml/badge.svg)](https://github.com/Minntooos/Compounding-Loop/actions/workflows/ci.yml) [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
10
+
11
+ **[Watch this repo build itself](https://minntooos.github.io/Compounding-Loop/)**: the live demo reads this repo's commits from GitHub in your browser, next to the real five-site data. Steering and scheduling: [Glance, steer, sleep](#glance-steer-sleep). Working with a teammate? [Five steps](docs/team.md).
12
+
13
+ ![The Compounding Loop fleet screen: six loops, one with lanes](docs/assets/screens/fleet-light-laptop.png)
14
+
15
+ ![A loop's detail screen in the dark theme](docs/assets/screens/loop-dark-laptop.png)
16
+
17
+ **Lanes:** each lane is a scheduled run that owns a set of paths (`.ai/lanes.json`). `loop check-lanes` fails any commit that touches files outside its lane, so parallel runs do not collide. This repo is the proof: five lanes (core, kit, server, web, docs) built and documented it by pushing to one `main`. See [Lanes](docs/lanes.md).
18
+
19
+ Most agent tools show a demo. This one ships with proof: the method in this repo built five live sites, 215 pages in all, with nobody watching, and made **verification** the product. A round only counts when the repo's own tests pass and a reviewer that did not write the code has checked the diff.
20
+
21
+ ## Proof
22
+
23
+ Five calculator sites, each built by scheduled Claude Code runs working from a written brief. Numbers come from the repo data in [`demo/five-sites.json`](demo/five-sites.json).
24
+
25
+ | Site | Pages | Tests passing | Commits | Rounds | Elapsed* |
26
+ |---|---:|---:|---:|---:|---:|
27
+ | [powerstationruntime.netlify.app](https://powerstationruntime.netlify.app) | 55 | 278 | 112 | 5 / 5 | 22 h |
28
+ | [clearpoolmath.netlify.app](https://clearpoolmath.netlify.app) | 40 | 543 | 83 | 5 / 5 | 22 h |
29
+ | [yarnsums.netlify.app](https://yarnsums.netlify.app) | 40 | 419 | 109 | 5 / 5 | 19 h |
30
+ | [quiltcalculators.netlify.app](https://quiltcalculators.netlify.app) | 43 | 452 | 77 | 5 / 5 | 18 h |
31
+ | [mathtank.netlify.app](https://mathtank.netlify.app) | 37 | 433 | 83 | 5 / 5 | 18 h |
32
+ | **Total** | **215** | **2,125** | **464** | | |
33
+
34
+ \*Wall-clock time from the first to the last commit, rounded. It is not the time a human spent: that was writing five briefs.
35
+
36
+ ## How it works
37
+
38
+ ```mermaid
39
+ flowchart LR
40
+ B[Brief] --> T[task.md contract]
41
+ T --> R[Scheduled run]
42
+ R --> U[One unit of work]
43
+ U --> C{npm test}
44
+ C -- red --> X[Log under Tried, discard]
45
+ C -- green --> V[Reviewer subagent]
46
+ V --> P[Push]
47
+ P --> K[Retro: knowledge]
48
+ K --> R
49
+ R -. stuck .-> Q[BLOCKED.md: your inbox]
50
+ ```
51
+
52
+ 1. **Brief and contract.** You write what "done" means as commands that can be checked.
53
+ 2. **Scheduled runs.** A routine, a GitHub Action or `loop run` wakes up, reads its task file and does one unit at a time.
54
+ 3. **Checks decide.** The agent never declares itself done; `npm test` does.
55
+ 4. **Reviewer gate.** A fresh subagent attacks the diff before it is pushed.
56
+ 5. **Compounding.** Every run writes what it learned into a small indexed knowledge base the next run reads first.
57
+ 6. **Escalation.** When it is stuck it writes `BLOCKED.md`. You answer from the dashboard inbox or `loop answer`.
58
+
59
+ ## Learn once, one-shot the next
60
+
61
+ When work finishes, what it taught is kept as a **recipe** (steps, traps and the check that proves each step, under 6 KB) and as **triggers** that fire at the moment they matter, through Claude Code hooks. The next project of the same kind starts from one small, exact context instead of from zero, so a smaller model has a chance to do it in one pass.
62
+
63
+ ```sh
64
+ npx compounding-loop recipe match "add a mortgage calculator page" # which recipe fits this repo
65
+ npx compounding-loop init --hooks # wire triggers without the plugin
66
+ npx compounding-loop eval run --runner fake # free dry run of the proof
67
+ ```
68
+
69
+ The proof is a number from `loop eval`: known tasks rebuilt cold and with a recipe, reporting pass rate, tokens and time. <!-- eval: 2026-10-18 -->Haiku, 3 tasks × 2 runs (2026-10-18): **6/6 with a recipe vs 4/6 cold**. The gap is the planted-trap task (0/2 cold, 2/2 with the recipe); on the two plain tasks both arms passed and the recipe used 4-17% more tokens (26% fewer on the trap task, about even overall). A small sample: `evals/results/2026-10-18-haiku.jsonl`. Details: [Memory](docs/memory.md), [Prove it with an eval](docs/eval.md).
70
+
71
+ ## Glance, steer, sleep
72
+
73
+ Leave a laned loop running, look at the dashboard, and steer it without opening a chat. The top strip says in five seconds whether anything needs you, whether lanes are running or stalled, whether runs hit the usage limit, whether CI is green and whether it is ready to ship.
74
+
75
+ ```sh
76
+ npx compounding-loop steer docs "Update the README screenshots first" # saved as a file in the repo, read at the lane's next run
77
+ npx compounding-loop schedule snooze web 6h # or pause, resume, wake
78
+ npx compounding-loop pace set sprint # how much checking each run does
79
+ ```
80
+
81
+ Lanes with nothing to do back off (3 h, then 6 h, then asleep until there is steering, a message or a new round), so a loop can stay on around the clock and a skipped run costs seconds. Routine prompts stay short and point at `.ai/lane-run.md`, so changing how every lane runs is one commit. Details: [Steer, schedule and pace a loop](docs/steering.md).
82
+
83
+ ## Quick start
84
+
85
+ ```sh
86
+ npx compounding-loop new static-site my-site # a new loop repo from a template
87
+ cd my-site
88
+ npx compounding-loop run # one round now
89
+ npx compounding-loop dashboard # the local dashboard
90
+ ```
91
+
92
+ Several lanes in one repo (needs an `IDEA.md` brief first): `npx compounding-loop init --lanes core,web,docs`, then `npx compounding-loop run --lane core`. Check ownership with `npx compounding-loop check-lanes`. Details: [Lanes](docs/lanes.md). Sharing a loop with a teammate: [Work on a loop with a teammate](docs/team.md).
93
+
94
+ **Run the loop without tokens.** Pause every lane (`npx compounding-loop pause`, undo with `resume`), start the next round from a brief in a laned repo (`next-round --brief`), see which scheduled runs happened (`runs`) and check whether you can ship (`ship-check`). The dashboard has buttons for all of it (Pause all, Start next round, Release), each with a confirm step, plus the run history and a Ready to ship checklist: see [Dashboard](docs/dashboard.md#controls). Releases go through a GitHub Actions workflow with npm trusted publishing and a protected `release` environment: see [Release](docs/release.md).
95
+
96
+ Already have a repo? `npx compounding-loop init` adds the method to it without overwriting anything. Details: [Getting started](docs/getting-started.md).
97
+
98
+ ## Just loop the agent?
99
+
100
+ An honest comparison with `while true; do claude -p "keep going"; done`:
101
+
102
+ | | Plain loop | Compounding Loop |
103
+ |---|---|---|
104
+ | Knows when it is done | When the model says so | When your checks pass |
105
+ | Reviews its own work | Same context, same blind spots | A fresh reviewer must find no blockers |
106
+ | Survives context limits | Starts cold every time | Writes a handoff the next run resumes from |
107
+ | Learns from mistakes | No | Notes and a retro, indexed |
108
+ | Overlapping runs | Collide on the same files | Session lock |
109
+ | Stuck for hours | Burns budget | Run budget, then `BLOCKED.md` |
110
+ | You can see what happened | `git log` | Fleet dashboard and inbox |
111
+
112
+ If your task is one afternoon of work and you are at the keyboard, a plain loop is fine. This pays off for work that runs for days.
113
+
114
+ ## How it differs from other tools
115
+
116
+ These tools solve a neighbouring problem: running several agents side by side while you watch. Compounding Loop is for work that runs unattended and has to prove itself. Descriptions come from each project's own page (checked 2026-10-08).
117
+
118
+ | Tool | What it is for | Where Compounding Loop differs |
119
+ |---|---|---|
120
+ | [Claude Squad](https://github.com/smtg-ai/claude-squad) | A terminal app that manages several agents, each in its own tmux session and git worktree; an experimental `-y` flag auto-accepts prompts. | Squad isolates work by branch. We isolate by path ownership on one `main`, and a round only counts when your tests and a reviewer pass. |
121
+ | [Parallel Code](https://github.com/johannesjo/parallel-code) | Runs several agents at once, each in its own git worktree; you merge the branch back yourself. | Interactive and merge-driven. We are scheduled, and the agent never marks itself done. |
122
+ | [Agent Orchestrator](https://github.com/ComposioHQ/agent-orchestrator) | A local desktop workspace where an orchestrator delegates work to agents on separate branches and worktrees, shown on a Kanban board. | A desktop planner. We are a plain-file method (task file, notes, handoffs) you can read in git. |
123
+ | [Vibe Kanban](https://github.com/BloopAI/vibe-kanban) | Plan issues on a kanban board, then run coding agents in workspaces with a branch and a terminal. Its page says it is sunsetting. | We have no board; the contract is a file of checkable commands. |
124
+ | [Claude Code agent teams](https://code.claude.com/docs/en/agent-teams) | Experimental, off by default: a lead session coordinates teammates with a shared task list and messages. Needs an interactive session (not `claude -p`). | Teams are live and interactive. Lanes are scheduled, unattended and leave a git trail. They can be combined. |
125
+
126
+ If you work at the keyboard and want to watch agents in parallel, those tools are a good fit.
127
+
128
+ ## Docs
129
+
130
+ [Getting started](docs/getting-started.md) · [Lanes](docs/lanes.md) · [Team](docs/team.md) · [Concepts](docs/concepts.md) · [Runners](docs/runners.md) · [Dashboard](docs/dashboard.md) · [Steering](docs/steering.md) · [Memory](docs/memory.md) · [Eval](docs/eval.md) · [Release](docs/release.md) · [Templates](docs/templates.md) · [FAQ](docs/faq.md) · [Troubleshooting](docs/troubleshooting.md) · [Contributing](CONTRIBUTING.md)
131
+
132
+ ## FAQ
133
+
134
+ **Isn't this cron + `claude -p`?** Partly, and the table above shows what is added: your checks decide when a round is done, a fresh reviewer attacks the diff, each run writes a handoff and notes the next run reads, a run budget stops runaway loops, and lanes keep parallel runs out of each other's files.
135
+
136
+ **What does it cost (plan usage vs API)?** The tool is free and MIT licensed. Runs use whatever your Claude Code login uses: plan usage on a subscription, or API billing if you authenticate with a key. The `Run: N / LIMIT` budget caps how many runs happen. More lanes means more runs, so cost scales with lanes times runs.
137
+
138
+ **Why not agent teams?** Agent teams are experimental, off by default and need an interactive session ([docs](https://code.claude.com/docs/en/agent-teams)). Lanes are for scheduled runs nobody is watching. Use teams when you are at the keyboard; use lanes when you are away.
139
+
140
+ **What happens when it goes wrong?** A red round is discarded and logged under Tried. The same failure twice triggers the stuck protocol, then a `BLOCKED.md` appears in your dashboard inbox. When the run budget is reached the lane stops. Everything is a git commit, so it is reversible. See [Troubleshooting](docs/troubleshooting.md).
141
+
142
+ **Is my code sent anywhere?** Only what Claude Code and git already send. Compounding Loop has no telemetry and no call home, the dashboard binds to loopback, and tokens come from your environment or `gh` and are never stored. See [SECURITY.md](SECURITY.md).
143
+
144
+ **What won't it do?** It will not publish, spend money, create accounts or change repo settings on its own, and it stops and asks (`BLOCKED.md`) rather than guess.
145
+
146
+ **Why does the live demo sometimes say "Snapshot"?** The demo page reads this repo from GitHub's public API without a login, which allows 60 requests an hour per IP address (a page load uses about three). When that runs out, or you are offline, it shows the bundled snapshot and the header chip says so; it switches back to live on the next refresh once GitHub allows requests again. Nothing is sent anywhere else.
147
+
148
+ **Does it need a GitHub login?** Only to create a private repo with `loop new`; `--dry-run` and every test work offline.
149
+
150
+ ## License
151
+
152
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,69 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { mkdir, readFile, readdir, stat, writeFile } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { lintBrief } from '../core/brief.js';
5
+ import { buildDraftPrompt, draftClaudeArgs, extractOpenFindings, extractOutOfScope, nextRoundNumber } from '../core/draftBrief.js';
6
+ import { parseLanesConfig } from '../core/lanes.js';
7
+ import { parseSteer, steerPath, unacknowledged } from '../core/parse/steer.js';
8
+ import { extractNextItems } from '../core/rounds.js';
9
+ import { resolveInvocation } from './run.js';
10
+ export const DRAFT_FILE = '.ai/next-round.draft.md';
11
+ const read = (dir, rel) => readFile(path.join(dir, ...rel.split('/')), 'utf8').catch(() => undefined);
12
+ export const realClaude = async (rawArgs, cwd) => {
13
+ const { command, args } = await resolveInvocation('claude', rawArgs);
14
+ return new Promise((resolve, reject) => {
15
+ execFile(command, args, { cwd, maxBuffer: 16 * 1024 * 1024, timeout: 15 * 60_000 }, (error, stdout) => {
16
+ if (error)
17
+ reject(new Error(`Could not run \`claude -p\`: ${error.message}. Is Claude Code installed, on your PATH and signed in? \`loop next-round --draft --print-prompt\` prints the prompt to paste yourself.`));
18
+ else
19
+ resolve(stdout);
20
+ });
21
+ });
22
+ };
23
+ /** Collects the ingredients: DONE.md next-10 lists, open audit findings, unacknowledged steer, the old out-of-scope. */
24
+ export async function gatherDraftPrompt(dir) {
25
+ const idea = (await read(dir, 'IDEA.md')) ?? '';
26
+ const lanes = parseLanesConfig((await read(dir, '.ai/lanes.json')) ?? '').config?.lanes.map((l) => l.name) ?? [];
27
+ const next = [];
28
+ for (const source of ['DONE.md', ...lanes.map((l) => `.ai/lanes/${l}/DONE.md`)]) {
29
+ const items = extractNextItems((await read(dir, source)) ?? '');
30
+ if (items.length)
31
+ next.push({ source, items });
32
+ }
33
+ const findings = [];
34
+ for (const file of (await readdir(path.join(dir, '.ai', 'audit')).catch(() => [])).filter((f) => f.endsWith('.md')).sort()) {
35
+ findings.push(...extractOpenFindings(`.ai/audit/${file}`, (await read(dir, `.ai/audit/${file}`)) ?? ''));
36
+ }
37
+ const steer = [];
38
+ for (const lane of lanes) {
39
+ const entries = unacknowledged(parseSteer((await read(dir, steerPath(lane))) ?? ''));
40
+ if (entries.length)
41
+ steer.push({ lane, entries });
42
+ }
43
+ const name = path.basename(dir);
44
+ return buildDraftPrompt({ name, round: nextRoundNumber(idea), next, findings, steer, outOfScope: extractOutOfScope(idea) });
45
+ }
46
+ /** The draft goes inside the repo, as a new .md file (only the default draft file may be overwritten). */
47
+ async function assertDraftTarget(dir, outFile, isDefault) {
48
+ const rel = path.relative(path.resolve(dir), outFile);
49
+ if (rel.startsWith('..') || path.isAbsolute(rel) || !/\.md$/i.test(outFile))
50
+ throw new Error(`--out must be a .md file inside ${dir}, not ${outFile}.`);
51
+ if (!isDefault && (await stat(outFile).then(() => true, () => false)))
52
+ throw new Error(`${rel} already exists; pick a new file name for the draft (nothing is overwritten).`);
53
+ }
54
+ /** `loop next-round --draft`: builds the prompt, runs `claude -p` through the usual runner, writes the draft and scores it. */
55
+ export async function runDraft(dir, options = {}) {
56
+ const prompt = await gatherDraftPrompt(dir);
57
+ if (options.printPrompt)
58
+ return { prompt };
59
+ const draft = (await (options.claude ?? realClaude)(draftClaudeArgs(prompt, options.model), dir)).trim();
60
+ if (!draft)
61
+ throw new Error('Claude returned an empty draft. Run again, or use --print-prompt and paste the prompt into Claude Code yourself.');
62
+ const rel = options.out ?? DRAFT_FILE;
63
+ const outFile = path.resolve(dir, rel);
64
+ await assertDraftTarget(dir, outFile, rel === DRAFT_FILE);
65
+ await mkdir(path.dirname(outFile), { recursive: true });
66
+ await writeFile(outFile, `${draft}\n`);
67
+ return { prompt, draft, outFile, lint: lintBrief(draft) };
68
+ }
69
+ //# sourceMappingURL=draft.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"draft.js","sourceRoot":"","sources":["../../src/cli/draft.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,186 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { appendFile, cp, mkdir, mkdtemp, readFile, readdir, rm, stat, writeFile } from 'node:fs/promises';
3
+ import { tmpdir } from 'node:os';
4
+ import path from 'node:path';
5
+ import { promisify } from 'node:util';
6
+ import { EVAL_ARMS, buildEvalReport, evalClaudeArgs, formatEvalTable, formatResult, parseClaudeJson, parseResults, resultsFile } from '../core/eval.js';
7
+ import { mergeHookSettings } from '../core/hooksInit.js';
8
+ import { recipeContext } from '../core/recipes.js';
9
+ import { findRecipe } from './recipe.js';
10
+ import { packageRoot } from './init.js';
11
+ import { resolveInvocation } from './run.js';
12
+ const execFileAsync = promisify(execFile);
13
+ const readText = (file) => readFile(file, 'utf8').catch(() => undefined);
14
+ const splitCommand = (line) => line.trim().split(/\s+/).filter(Boolean);
15
+ /** Loads `evals/<id>/`: `task.md`, `repo/`, and the check and recipe id from `eval.json` (`{"check": "node --test" | ["node","--test"], "recipe": "R-…"}`) or the plain files `check` and `recipe`. */
16
+ export async function loadTask(evalsDir, id) {
17
+ const dir = path.join(evalsDir, id);
18
+ const prompt = (await readText(path.join(dir, 'task.md')))?.trim();
19
+ if (!prompt)
20
+ throw new Error(`evals/${id}/task.md is missing or empty.`);
21
+ if (!(await stat(path.join(dir, 'repo')).then((s) => s.isDirectory(), () => false)))
22
+ throw new Error(`evals/${id}/repo/ is missing.`);
23
+ let meta = {};
24
+ try {
25
+ meta = JSON.parse((await readText(path.join(dir, 'eval.json'))) ?? '{}');
26
+ }
27
+ catch {
28
+ throw new Error(`evals/${id}/eval.json is not valid JSON.`);
29
+ }
30
+ const rawCheck = meta.check ?? (await readText(path.join(dir, 'check')));
31
+ const check = Array.isArray(rawCheck) ? rawCheck.filter((c) => typeof c === 'string') : typeof rawCheck === 'string' ? splitCommand(rawCheck) : [];
32
+ if (check.length === 0)
33
+ throw new Error(`evals/${id} has no check: put "check" in eval.json or write the command in evals/${id}/check.`);
34
+ const recipe = typeof meta.recipe === 'string' ? meta.recipe : (await readText(path.join(dir, 'recipe')))?.trim();
35
+ let recipeText;
36
+ if (typeof meta.recipeFile === 'string') {
37
+ const file = path.resolve(dir, meta.recipeFile);
38
+ const rel = path.relative(dir, file);
39
+ if (rel.startsWith('..') || path.isAbsolute(rel))
40
+ throw new Error(`evals/${id}/eval.json: recipeFile must stay inside the task folder.`);
41
+ recipeText = await readText(file);
42
+ if (recipeText === undefined)
43
+ throw new Error(`evals/${id}/eval.json: recipeFile ${meta.recipeFile} is missing.`);
44
+ }
45
+ return { id, dir, prompt, check, ...(recipe && { recipe }), ...(recipeText !== undefined && { recipeText }) };
46
+ }
47
+ export async function listTaskIds(evalsDir) {
48
+ const entries = await readdir(evalsDir, { withFileTypes: true }).catch(() => []);
49
+ return entries.filter((e) => e.isDirectory() && e.name !== 'results').map((e) => e.name).sort();
50
+ }
51
+ /** `KEY=value` lines (comments and `export ` allowed, matching quotes stripped). Values are never printed. */
52
+ export function parseEnvFile(text) {
53
+ const env = {};
54
+ for (const raw of text.split(/\r?\n/)) {
55
+ const m = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$/.exec(raw);
56
+ if (!m || raw.trimStart().startsWith('#'))
57
+ continue;
58
+ env[m[1]] = m[2].replace(/^(["'])(.*)\1$/, '$2');
59
+ }
60
+ return env;
61
+ }
62
+ const realClaudeRunner = async ({ prompt, cwd, model, env }) => {
63
+ const { command, args } = await resolveInvocation('claude', evalClaudeArgs(prompt, model === 'default' ? undefined : model));
64
+ const { stdout } = await execFileAsync(command, args, { cwd, env: { ...process.env, ...env }, maxBuffer: 64 * 1024 * 1024, timeout: 20 * 60_000 }).catch((error) => {
65
+ // A non-zero exit still prints the JSON result for turn limits and errors.
66
+ if (error.stdout?.trim().startsWith('{') || error.stdout?.trim().startsWith('['))
67
+ return { stdout: error.stdout };
68
+ throw new Error(`Could not run \`claude -p\`: ${error.message}. Is Claude Code installed, on your PATH and signed in?`);
69
+ });
70
+ return stdout;
71
+ };
72
+ /** A stand-in for Claude: `solve` copies the task's `solution/` over the fixture, `nothing` changes nothing. It prints a result in the same JSON shape, with zero tokens. */
73
+ export const fakeRunner = (mode) => async ({ cwd, task }) => {
74
+ if (mode === 'solve')
75
+ await cp(path.join(task.dir, 'solution'), cwd, { recursive: true, force: true });
76
+ return JSON.stringify({ type: 'result', subtype: 'success', is_error: false, duration_ms: 1, num_turns: 1, total_cost_usd: 0, usage: { input_tokens: 0, output_tokens: 0 } });
77
+ };
78
+ /** Expands `both` (cold + recipe) and `all`. */
79
+ export function parseArms(value) {
80
+ if (value === 'both')
81
+ return ['cold', 'recipe'];
82
+ if (value === 'all')
83
+ return [...EVAL_ARMS];
84
+ if (EVAL_ARMS.includes(value))
85
+ return [value];
86
+ throw new Error(`--arm must be cold, recipe, recipe+triggers, both or all, not "${value}".`);
87
+ }
88
+ /** The recipe's context: the user's or project's store first, then the seeds shipped in the package. */
89
+ async function recipeFor(task, repoDir) {
90
+ if (task.recipeText !== undefined)
91
+ return recipeContext(task.recipeText);
92
+ if (!task.recipe)
93
+ throw new Error(`evals/${task.id} names no recipe, so the recipe arm cannot run.`);
94
+ const stored = await findRecipe(repoDir, task.recipe).then((s) => s.text, () => undefined);
95
+ const seed = stored ?? (await readdir(path.join(packageRoot, 'templates', 'recipes')).catch(() => []).then(async (names) => {
96
+ for (const n of names) {
97
+ const text = await readText(path.join(packageRoot, 'templates', 'recipes', n));
98
+ if (text && new RegExp(`^id:\\s*${task.recipe}\\s*$`, 'm').test(text))
99
+ return text;
100
+ }
101
+ return undefined;
102
+ }));
103
+ if (!seed)
104
+ throw new Error(`Recipe ${task.recipe} (named by evals/${task.id}) is not in .ai/recipes/, ~/.ai/recipes/ or the package seeds.`);
105
+ return recipeContext(seed);
106
+ }
107
+ /** For the recipe+triggers arm: the task's `triggers.yaml` (and `knowledge/`) and the hooks, in the temp copy. */
108
+ async function installTriggers(task, cwd) {
109
+ const triggers = await readText(path.join(task.dir, 'triggers.yaml'));
110
+ if (triggers === undefined)
111
+ throw new Error(`evals/${task.id}/triggers.yaml is missing, so the recipe+triggers arm cannot run.`);
112
+ await mkdir(path.join(cwd, '.ai'), { recursive: true });
113
+ await writeFile(path.join(cwd, '.ai', 'triggers.yaml'), triggers);
114
+ await cp(path.join(task.dir, 'knowledge'), path.join(cwd, '.ai', 'knowledge'), { recursive: true }).catch(() => undefined);
115
+ const merged = mergeHookSettings(undefined, `node ${path.join(packageRoot, 'bin', 'loop.js')} hook`);
116
+ await mkdir(path.join(cwd, '.claude'), { recursive: true });
117
+ await writeFile(path.join(cwd, '.claude', 'settings.json'), merged.content ?? '{}');
118
+ }
119
+ /** `loop eval run`: each run gets a fresh temp copy of the task's `repo/`, never the user's repo. */
120
+ export async function runEval(options) {
121
+ const env = options.envFile ? parseEnvFile((await readText(options.envFile)) ?? (() => { throw new Error(`Cannot read --env-file ${options.envFile}.`); })()) : {};
122
+ const ids = options.tasks?.length ? options.tasks : await listTaskIds(options.evalsDir);
123
+ if (ids.length === 0)
124
+ throw new Error(`No eval tasks in ${options.evalsDir}. Each task is a folder with task.md, repo/ and a check.`);
125
+ if (!Number.isInteger(options.runs) || options.runs < 1)
126
+ throw new Error('--runs must be a whole number, 1 or more.');
127
+ const results = [];
128
+ // Each result is appended the moment its run ends, so one failed run late in a sweep loses no paid runs.
129
+ const rel = options.save && resultsFile(options.save.date, options.model);
130
+ const file = options.save && rel ? path.join(options.save.dir, ...rel.split('/')) : undefined;
131
+ if (file)
132
+ await mkdir(path.dirname(file), { recursive: true });
133
+ for (const id of ids) {
134
+ const task = await loadTask(options.evalsDir, id);
135
+ for (const arm of options.arms) {
136
+ for (let n = 1; n <= options.runs; n++) {
137
+ const cwd = await mkdtemp(path.join(tmpdir(), `loop-eval-${id}-`));
138
+ try {
139
+ await cp(path.join(task.dir, 'repo'), cwd, { recursive: true });
140
+ await execFileAsync('git', ['init', '-q'], { cwd });
141
+ if (arm === 'recipe+triggers')
142
+ await installTriggers(task, cwd);
143
+ const context = arm === 'cold' ? '' : `${await recipeFor(task, path.dirname(options.evalsDir))}\n---\n\n`;
144
+ const prompt = `${context}${task.prompt}`;
145
+ const shown = evalClaudeArgs('<prompt>', options.model === 'default' ? undefined : options.model);
146
+ options.log?.(`${id} · ${arm} · run ${n}/${options.runs}: claude ${shown.join(' ')} (in a temp copy of evals/${id}/repo)`);
147
+ const started = Date.now();
148
+ const stdout = await options.runner({ prompt, cwd, model: options.model, task, arm, env });
149
+ const stats = parseClaudeJson(stdout);
150
+ // The check lives outside repo/ so the agent cannot read or edit it; it joins the copy only after the run.
151
+ await cp(path.join(task.dir, 'verify'), path.join(cwd, 'verify'), { recursive: true }).catch(() => undefined);
152
+ const [bin, ...args] = task.check;
153
+ const pass = await execFileAsync(bin, args, { cwd, timeout: 120_000, env: process.env }).then(() => true, () => false);
154
+ const result = {
155
+ at: (options.now?.() ?? new Date()).toISOString(), task: id, arm, model: options.model, pass,
156
+ inputTokens: stats.inputTokens, outputTokens: stats.outputTokens, cacheReadTokens: stats.cacheReadTokens, cacheCreationTokens: stats.cacheCreationTokens,
157
+ costUsd: stats.costUsd, durationMs: stats.durationMs || Date.now() - started, turns: stats.turns,
158
+ };
159
+ results.push(result);
160
+ if (file)
161
+ await appendFile(file, formatResult(result));
162
+ }
163
+ finally {
164
+ await rm(cwd, { recursive: true, force: true, maxRetries: 5 });
165
+ }
166
+ }
167
+ }
168
+ }
169
+ return { results, ...(rel && { savedTo: rel }) };
170
+ }
171
+ /** `loop eval report`: every `evals/results/*.jsonl`. No results, no rows, no headline. */
172
+ export async function readEvalReport(evalsDir) {
173
+ const dir = path.join(evalsDir, 'results');
174
+ const files = (await readdir(dir).catch(() => [])).filter((n) => n.endsWith('.jsonl')).sort();
175
+ const results = [];
176
+ for (const f of files)
177
+ results.push(...parseResults((await readText(path.join(dir, f))) ?? ''));
178
+ return buildEvalReport(results);
179
+ }
180
+ export function formatEvalReport(report) {
181
+ if (report.rows.length === 0)
182
+ return 'No eval results yet. Run: loop eval run --task calc-page --arm both --model haiku';
183
+ return `${formatEvalTable(report.rows)}\n\n${report.headline ?? 'No headline: it needs a cold and a recipe arm on the same model.'}`;
184
+ }
185
+ export const realRunner = realClaudeRunner;
186
+ //# sourceMappingURL=eval.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eval.js","sourceRoot":"","sources":["../../src/cli/eval.ts"],"names":[],"mappings":"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"}