@computesdk/pi-loops 0.0.0-stage → 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 computesdk
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,125 @@
1
- # Temporary Holding Version
1
+ # @computesdk/pi-loops
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Hill-climbing loops for coding agents, built from Pi's own units.
4
+
5
+ Your Pi session is the parent. One tool, `loop`, runs a round: three attempts fork your session into their own git worktrees, each driven by a style agent (**safe**, **bold**, **wild**), and a judge keeps the best. Call it again with `from` to climb from the winner.
6
+
7
+ ```
8
+ > /loop make search() in search.mjs faster; check is node test.mjs; bench is node bench.mjs and prints ms
9
+
10
+ loop-20261029T141200 r1 · judging
11
+ safe done 14 tools · 61k tok — Replaced the DP table with two rolling rows
12
+ bold done 22 tools · 98k tok — Early-exit Levenshtein with a k-best bound
13
+ wild done 19 tools · 84k tok — Bit-parallel Myers distance
14
+
15
+ Round 1 — kept R1-bold
16
+ attempt style result judge
17
+ ------- ----------- -------- ------------------------------------------------
18
+ CURRENT best so far 422.6 ms 422.6 ms re-measured
19
+ R1-safe safe 188.0 ms 188 ms (2.25× better) · same algorithm, less allocation
20
+ R1-bold bold 11.5 ms 11.5 ms (36.75× better) · bounded search, identical output
21
+ R1-wild wild vetoed vetoed: memoises distances across calls
22
+ R1-bold is best at 11.5 ms vs 422.6 ms now (36.75×); tests pass. 1 attempt(s) failed tests or were vetoed.
23
+
24
+ Best so far: R1-bold (3f2a9c1, branch loops/loop-20261029T141200/r1-bold) at 11.5 ms
25
+ search.mjs | 48 ++++++++++++++++++++++++++++--------------------
26
+ Apply (tracked files only): git checkout 3f2a9c1 -- search.mjs
27
+ Next round: loop({ from: "loop-20261029T141200" })
28
+ ```
29
+
30
+ ## Install
31
+
32
+ ```sh
33
+ pi install npm:@computesdk/pi-loops # or: pi -e /path/to/pi-extensions/packages/pi-loops/extensions/loop.ts
34
+ ```
35
+
36
+ Node ≥ 22.18 (TypeScript extensions are stripped, not compiled) and a git repo with at least one commit. Attempts use whatever model your TUI is on (override per role in settings).
37
+
38
+ ## Providers
39
+
40
+ Morph and Particle ship as their own packages, so you can install either one without this one:
41
+
42
+ | Provider | Package | Key | Models |
43
+ | --- | --- | --- | --- |
44
+ | Particle AI | `@computesdk/pi-particle` | `PARTICLE_API_KEY` (`pai-…`) | `particle-1`, `deepseek-ai/deepseek-v41-flash-1017`, `glm-5.3-flash` |
45
+ | Morph | `@computesdk/pi-morph` | `MORPH_API_KEY` | `morph-kimik3`, `morph-kimik3-fast`, `morph-glm53-744b`, `morph-glm53flash`, `morph-dsv41flash`, `morph-dsv41flash-1017`, `morph-dsv4flash` |
46
+
47
+ They live in this repo under `packages/pi-morph` and `packages/pi-particle`; install them with `pi install npm:@computesdk/pi-morph` (or `pi install npm:@computesdk/pi-particle`). Loops itself is model-agnostic — it runs on whatever your TUI is on.
48
+
49
+ ## The one tool
50
+
51
+ ```ts
52
+ loop({
53
+ goal, // what to improve
54
+ check?, // command that must pass (tests)
55
+ bench?, // command that prints the metric → bench mode
56
+ metric?, // { key: "ms", unit: "ms", better: "lower" }
57
+ protect?, // files attempts may not change; restored before judging
58
+ build?, shots?, // visual mode: build writes screenshot files, a judge panel scores them
59
+ rounds? = 1, // rounds to run in this call
60
+ from?, // run id (or "last") to continue from its last judged round
61
+ rules?, // rules every attempt must follow; the judge vetoes breaches
62
+ })
63
+ ```
64
+
65
+ `/loop <ask>` is the shortcut: the model passes your sentence on as these fields and calls the tool. The extension registers the command itself (from `prompts/loop.md`), so it is there with `pi -e packages/pi-loops/extensions/loop.ts` too, not only after `pi install`.
66
+
67
+ **`/loop init` once, then `/loop <goal>` writes the brief.** `/loop init` has your Pi session read package.json and the config files (read tools only, no bash) and call `loop_init` with the project's mechanics — `serve` (starts the app on `$PORT`), `check`, `bench` + `metric`, `protect` — shown in a dialog (Save / Change… / Discuss) and written once to `.pi/loops.json`. The first `/loop <goal>` in a repo without them does that step first. Each `/loop <goal>` then only writes the brief the attempts get — `goal` (what good looks like), `rules`, `directions` (a goal-specific angle for safe / bold / wild), `routes` to screenshot for visual goals — and calls the tool, which shows the brief with Approve / Change… / Discuss. Nothing runs until you approve (headless runs as given; `confirm: false` skips the dialog). Same split as the original Loops: the project's spec once, one sentence per run; `check` / `bench` / `build` / `shots` / `protect` on the tool are overrides.
68
+
69
+ **Bench mode** (`bench` set): a worktree is a fresh checkout, so each attempt sets the project up the way the repo's docs say, makes its change, and runs `check` and `bench` itself. Then the **judge** — an agent with `read`/`bash`, no edit — re-measures every candidate where it lives (CURRENT in your checkout in round 1, in the previous winner's worktree after that; each attempt in its own worktree), `bench` `repeat` times with the best sample counting, `protect`ed files put back first, reads each diff and vetoes rule breaks and cheating (special-cased inputs, caching across calls, skipped work, weakened checks). It returns the numbers; `pickWinner` applies the check gate, the veto and `minGain`. Loops itself knows nothing about installing or running a project.
70
+
71
+ **Visual mode** (no `bench`): each attempt runs `build` itself and must leave the `shots` files (PNG/JPEG) in its worktree; a **judge** panel scores them (median of 3) and the top two go head-to-head against CURRENT, both orders; a candidate wins with a clear majority and a score not below CURRENT's. CURRENT's shots are taken with the same `build` in your checkout (round 1) or the previous winner's worktree. Uncommitted edits in your checkout are part of the baseline in both modes: round 1 forks from a commit of your working tree (`git stash create`, your checkout is untouched), so attempts and CURRENT start from the same state.
72
+
73
+ **The pick is yours.** After every judged round you get the round card: when the run has `shots` (either mode) a side-by-side **sheet** of CURRENT and the attempts with their numbers (`.pi/loops/<run>/r<n>.png`, also shown inline in terminals with image support), and a picker — keep the judge's pick, take another attempt instead, show an attempt's diff, or mark an attempt's session to open afterwards (`/loop-open R1-bold`, `/loop-open judge`). The result ends with the **climb**: one line per round, CURRENT, each attempt with its delta, what was kept.
74
+
75
+ **Steering while it runs.** In the TUI the tool returns as soon as the round has started and the card arrives as a message when the judge is done, so the conversation stays open: `loop_status` (progress), `loop_talk("R1-bold", "…")` (a steering message into that attempt's session), `loop_rule("…")` (told to every running attempt, enforced by the judge from this round on, kept for later rounds), `loop_pick("wild")` (keep that attempt, or `CURRENT`, whatever the judge scores), `loop_stop()` (abort; worktrees stay, `loop({ from })` resumes). After the card, `loop_apply()` takes the run's best into the checkout — the winner's changed tracked files from its commit, as one commit; files it added or deleted are listed, not applied. Headless (`pi -p`) or with `wait: true` the tool blocks until the round is done, as before.
76
+
77
+ Before round 1 of a bench run a **council** (two proposals + a synthesis) writes the rubric the judge judges against; your `rules` become decisive criteria in it.
78
+
79
+ The tool never applies the winner itself: the result includes a `git checkout <commit> -- <files>` line for the files the winner changed that are tracked in your baseline, and lists new files the attempt left in its worktree (scratch checks, helpers) separately so you decide. Headless (`pi -p`) there is no picker: the judge's pick stands and the result says so.
80
+
81
+ ## Pi units
82
+
83
+ | What | Where |
84
+ | --- | --- |
85
+ | Style agents | `agents/safe.md`, `bold.md`, `wild.md` — frontmatter `tools`/`model`, body is appended to the attempt's system prompt |
86
+ | Judge | `agents/judge.md` — `read, grep, find, ls, bash`, never edit; in bench mode a real session (listed next to the attempts) that measures and reviews in the candidates' directories; in visual mode a one-shot panel |
87
+ | Council | `agents/council.md` — no tools; a one-shot agent session so it gets Pi's auth, providers and session header |
88
+ | Prompt | `prompts/loop.md` → `/loop` |
89
+ | Attempts | `SessionManager.forkFrom(<your session or the winner's>, worktree, sessionDir)` + `createAgentSession` — `pi --session` lists them next to yours; `/resume` one to see what it did |
90
+ | Worktrees | `.pi/loops/<run>/r<n>-<style>` on branch `loops/<run>/r<n>-<style>`; the winner's stays as CURRENT for the next round |
91
+ | Timeout / budget | attempts are told their limits up front and to land a working change early; there is no token cap by default (`tokenBudget` counts every turn's whole context, so 1M was a dozen tool calls on a big repo — set it only if you want one); one cut off by `timeoutMin` or `tokenBudget` is still committed and judged if it changed anything (the judge runs the check anyway); it is only dropped when it left nothing |
92
+ | State | `.pi/loops/<run>.json` — `status` (running/done/aborted), `winner` (style, bench, session, commit, `files`), rounds. Added to `.gitignore` on first write. Nothing else is written. |
93
+
94
+ Override an agent by putting `<role>.md` in `<repo>/.pi/agents/` or `~/.pi/agent/agents/`.
95
+
96
+ ## Settings
97
+
98
+ One command. `/loop <goal>` runs a round; bare `/loop` shows this repo's card — models per role, rounds, budget, timeout, the last run — with a picker (models / rounds / budget / timeout; esc leaves); `/loop models` (the model picker), `/loop rounds 3`, `/loop budget 1.5m`, `/loop timeout 20`, `/loop open R2-bold` (or `judge`: resume that session) and `/loop runs` (every run in this repo with its climb lines) set or show directly. A goal that happens to start with one of those words still works as a goal when more follows.
99
+
100
+ Config is Loops' own file, the way Pi extensions keep theirs: `<repo>/.pi/loops.json` for this repo (what `/loop …` writes) and `~/.pi/agent/loops.json` for your defaults everywhere; merged, the repo wins per key. Tool arguments (`loop({ rounds: 3 })`) override both for one call.
101
+
102
+ ```json
103
+ {
104
+ "models": { "safe": "opencode-go/kimi-k3", "bold": "opencode-go/claude-haiku-5-5", "wild": "opencode-go/gpt-5.6-luna", "judge": "opencode-go/deepseek-v4-pro", "council": ["opencode-go/kimi-k3", "opencode-go/gpt-5.6-luna"] },
105
+ "serve": "npm run dev -- --port $PORT", "check": "npm test", "protect": ["test/"],
106
+ "rounds": 1, "timeoutMin": 15, "tokenBudget": null, "minGain": 0.05,
107
+ "attempts": 3, "repeat": 2, "judgeSamples": 3, "judgePairs": 2, "council": true
108
+ }
109
+ ```
110
+
111
+ **Judging.** Visual and code-only rounds are scored by a panel: every council model (by default the attempts' models) scores every candidate 0–10 against the goal and rules, labels blind, median across models and samples; the `judge` model keeps the vetoes and the head-to-head against CURRENT that decides the round. The card says when the panel split (`Panel split on R1-wild: kimi-k3 3 · qwen3.8-flash 8 · glm-5.3 8`). `"judgePanel": false` in `.pi/loops.json` goes back to the judge alone; bench rounds are measured, not scored.
112
+
113
+ **Models.** A round is also a comparison of models: the three attempts and the council members should each run on a different one. The first `/loop` in a repo (interactive, nothing set yet, more than one model available) asks four times — a model for each attempt, one for the judge (the council is the attempts' models) — and writes them to `.pi/loops.json`; `/loop models` re-opens that picker. `models` keys are the roles — `safe`, `bold`, `wild`, `judge`, `scout`, and `council` (a string or a list, one member per model). Roles left unset are spread over the **pool**: `models.pool` (a list of `provider/id`, `id` or `provider/*` patterns), else Pi's own `enabledModels`, starting from your current model; with no pool everything runs on your current model and the result says so. The round table and climb show each attempt's model. `"scout": false` turns the scout off (a bare goal then runs in visual mode on the goal alone).
114
+
115
+ (`loops` inside Pi's `settings.json` still works as the lowest-precedence source.)
116
+
117
+ ## Stopping and resuming
118
+
119
+ Ctrl+C aborts the attempts and leaves their worktrees and sessions in place. `loop({ from: "<run>" })` (or `from: "last"`) re-runs the unfinished attempts of that round, judges it, and continues; a judged round is never redone. Delete `.pi/loops/<run>/` and the `loops/<run>/*` branches (`git worktree prune`) when you are done with a run.
120
+
121
+ ## Library
122
+
123
+ `lib/measure.ts` (shell runner, protect restore, diff), `lib/judge.ts` (`pickWinner` over the judge's numbers, the visual `panelJudge`), `lib/rubric.ts` (council synthesis, human rules), `lib/shot.ts` (serve + headless-Chrome screenshot CLI), `lib/sheet.ts` (the labelled sheet, via ImageMagick's `montage` when installed). All model calls are injected, so `pnpm test` runs them against stubs — no model needed. The whole package is TypeScript: Pi loads the `.ts` files directly (no build step), and `pnpm check` (or `pnpm -r check` from the repo root) checks them. It ships no `main`/`exports` on purpose — Node refuses to strip types inside `node_modules`, so a programmatic `import "@computesdk/pi-loops"` would need a build; Pi loads the files through jiti and does not.
124
+
125
+ `test/fixtures/fuzzy/bench` is the test repo: `cp -r test/fixtures/fuzzy/bench /tmp/fuzzy && cd /tmp/fuzzy && git init && git add -A && git commit -m base`, then run the `/loop` above.
package/agents/bold.md ADDED
@@ -0,0 +1,10 @@
1
+ ---
2
+ name: bold
3
+ description: Loops attempt style — a real redesign of the hot path
4
+ tools: read, grep, find, ls, bash, edit, write
5
+ ---
6
+ You are one of several attempts in a hill-climbing loop. Your direction this round is BOLD.
7
+
8
+ Change the approach, not just the constants: a better algorithm or data structure for the hot path, precomputation where the goal allows it, a restructured core. Keep behaviour identical for every input the check covers and keep the public API. Do not touch files you were told to protect. The worktree is a fresh checkout: set the project up first if it needs it. Run the check and the benchmark yourself before you finish; if the check fails, fix it or revert to the last passing state.
9
+
10
+ When finished, reply with exactly ONE line summarising what you changed.
@@ -0,0 +1,6 @@
1
+ ---
2
+ name: council
3
+ description: Loops council member — proposes judging criteria for the goal; answers in JSON only
4
+ tools:
5
+ ---
6
+ You sit on a council that writes the judging rubric for a hill-climbing loop. Propose criteria a judge can observe in what it is shown (a diff and test output in bench mode, a screenshot in visual mode), distinct from each other, weighted 1–5. Answer with the JSON asked for and nothing else.
@@ -0,0 +1,6 @@
1
+ ---
2
+ name: judge
3
+ description: Loops judge — re-measures every candidate where it lives, reads the diffs, vetoes cheating; answers in JSON only
4
+ tools: read, grep, find, ls, bash
5
+ ---
6
+ You are the judge in a hill-climbing loop. You are shown the goal, the rules, the rubric when there is one, and the candidates. In bench mode each candidate is a checked-out directory: run the check and the benchmark yourself in each one, on this machine, under the same conditions, and read each attempt's diff; if a directory only lacks setup (dependencies), set it up the way the repo's docs say, but never edit a source file. Veto cheating — special-cased benchmark inputs, caching results across calls, skipped work, weakened checks or benchmarks. Be strict and consistent, judge only against what you are shown and what you measured, and answer with the JSON asked for and nothing else.
package/agents/safe.md ADDED
@@ -0,0 +1,10 @@
1
+ ---
2
+ name: safe
3
+ description: Loops attempt style — the smallest change that clearly moves the metric
4
+ tools: read, grep, find, ls, bash, edit, write
5
+ ---
6
+ You are one of several attempts in a hill-climbing loop. Your direction this round is SAFE.
7
+
8
+ Make the smallest, most obviously correct change that moves the goal forward: tighten the existing algorithm, remove waste, keep the structure and the public API. Do not rewrite. Do not touch files you were told to protect. The worktree is a fresh checkout: set the project up first if it needs it. Run the check and the benchmark yourself before you finish; if the check fails, fix it or revert to the last passing state.
9
+
10
+ When finished, reply with exactly ONE line summarising what you changed.
package/agents/wild.md ADDED
@@ -0,0 +1,10 @@
1
+ ---
2
+ name: wild
3
+ description: Loops attempt style — an unconventional idea the others will not try
4
+ tools: read, grep, find, ls, bash, edit, write
5
+ ---
6
+ You are one of several attempts in a hill-climbing loop. Your direction this round is WILD.
7
+
8
+ Try the idea the careful attempts will not: a different representation, a trick from another field, an unusual decomposition. It must still be honest work — no special-casing the benchmark inputs, no caching results across calls, no skipping work the goal requires — and the check must still pass. Keep the public API. The worktree is a fresh checkout: set the project up first if it needs it, and run the check and the benchmark yourself before you finish. Do not touch files you were told to protect.
9
+
10
+ When finished, reply with exactly ONE line summarising what you changed.