@alphacoder-v0/pi-loops 0.17.6 → 0.19.2

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 (48) hide show
  1. package/CHANGELOG.md +91 -0
  2. package/README.md +13 -6
  3. package/README.zh-CN.md +19 -6
  4. package/docs/agents/issue-tracker.md +34 -0
  5. package/docs/cli.md +22 -2
  6. package/docs/configuration.md +1 -1
  7. package/docs/design.md +63 -6
  8. package/docs/loops.md +25 -17
  9. package/docs/recipes.md +239 -0
  10. package/docs/triggers.md +1 -1
  11. package/docs/troubleshooting.md +3 -2
  12. package/docs/web-ui-parity.md +9 -3
  13. package/package.json +2 -1
  14. package/recipes/_tracker/issue-tracker-github.md +31 -0
  15. package/recipes/_tracker/issue-tracker-local.md +38 -0
  16. package/recipes/_tracker-setup.md +12 -0
  17. package/recipes/autoresearch/README.md +27 -0
  18. package/recipes/autoresearch/RESEARCH.template.md +37 -0
  19. package/recipes/autoresearch/example/RESEARCH.md +31 -0
  20. package/recipes/autoresearch/example/eval.py +60 -0
  21. package/recipes/autoresearch/example/eval.sh +3 -0
  22. package/recipes/autoresearch/example/solution.py +20 -0
  23. package/recipes/autoresearch/experiment.md +82 -0
  24. package/recipes/autoresearch/recipe.toml +15 -0
  25. package/recipes/changelog-draft/recipe.toml +9 -0
  26. package/recipes/changelog-draft/release.md +35 -0
  27. package/recipes/issue-loop/README.md +16 -0
  28. package/recipes/issue-loop/implement.md +135 -0
  29. package/recipes/issue-loop/labels.sh +21 -0
  30. package/recipes/issue-loop/recipe.toml +18 -0
  31. package/recipes/issue-loop/triage.md +147 -0
  32. package/skills/pi-loops/SKILL.md +3 -1
  33. package/src/archive.ts +5 -5
  34. package/src/args.ts +1 -8
  35. package/src/cli.ts +83 -6
  36. package/src/job-edit.ts +0 -3
  37. package/src/job-health.ts +6 -7
  38. package/src/job-owner.ts +29 -0
  39. package/src/pi-loops.ts +339 -25
  40. package/src/protocol.ts +2 -3
  41. package/src/recipe.ts +651 -0
  42. package/src/scheduler.ts +13 -13
  43. package/src/session-head.ts +98 -0
  44. package/src/store.ts +9 -17
  45. package/src/tools.ts +17 -19
  46. package/src/trigger-runtime.ts +3 -5
  47. package/src/triggers.ts +3 -4
  48. package/src/web.mjs +40 -17
@@ -0,0 +1,239 @@
1
+ # Recipes: a project run on loops, installed in one command
2
+
3
+ A **recipe** is a packaged way of running a project on loops: one or more jobs, the **playbook**
4
+ each of them reads on every run, a **manifest** that says what to create, and sometimes a setup
5
+ script. `/recipe add <name>` installs one into the project you have open; what you get is exactly
6
+ what you could have made by hand with `cp` and `/cron add`, which is the point — a recipe cannot do
7
+ anything a person could not have typed.
8
+
9
+ ```text
10
+ /recipe what is packaged, and what is installed here
11
+ /recipe show issue-loop the files, the jobs, the levels, the setup script
12
+ /recipe add issue-loop install it: a question, a confirmation, done
13
+ /recipe update issue-loop merge a newer pi-loops' playbooks into your copies
14
+ /recipe remove issue-loop its jobs go; the files stay unless you say --purge
15
+ ```
16
+
17
+ Two recipes ship in this version, and the shape is meant to be copied:
18
+
19
+ | recipe | jobs | what it runs | levels |
20
+ |---|---|---|---|
21
+ | [issue-loop](../recipes/issue-loop/) | `issue-triage` every 30 min, `issue-implement` every 30 min offset | the issue tracker as a state machine: triage into agent briefs, build accepted issues in a worktree, open pull requests | `propose`, `act` |
22
+ | [autoresearch](../recipes/autoresearch/) | `autoresearch` every hour, with `--verify` | one experiment per run against a contract you wrote, a ledger of every attempt, promotion only on held-out evidence | `propose`, `act` |
23
+
24
+ `changelog-draft` (a nightly changelog entry and version proposal) is packaged with its playbook
25
+ and manifest but is still the release-side half of the issue loop; it and four more — a daily
26
+ digest, a pull-request watch, a CI sweeper, an ecosystem scan — are the next batch.
27
+
28
+ ## What `/recipe add` does, in order
29
+
30
+ 1. **Tracker first, if the recipe needs one.** issue-loop reads `docs/agents/issue-tracker.md` on
31
+ every run to learn where the project's work items live — GitHub Issues, or one Markdown file
32
+ per item under `.scratch/issues/` — and how to list, comment, label and close them. If the file
33
+ is missing, the wizard hands that one step to the agent in your chat (the way `/inbox claim`
34
+ hands over a finding): it looks at the remote, proposes, writes the file after you agree — and
35
+ the moment that turn ends with the file in place, the wizard resumes on its own with the
36
+ question below. Everything else is done without a model. The file's layout
37
+ is the one Matt Pocock's engineering skills use, so a repository that has `/triage` reads the
38
+ same description. Like the playbooks it is kept out of the repository through
39
+ `.git/info/exclude`: it names an account and a workflow, which are the project's to keep and
40
+ not the repository's to publish. Another clone is asked the same questions once.
41
+ 2. **The autonomy level.** One question, defaulting to the lowest the recipe supports (below).
42
+ `--level act` skips it.
43
+ 3. **A name check.** A job with the same name already in `jobs.json` stops the install and says
44
+ which recipe or `/cron add` it came from.
45
+ 4. **The confirmation.** Every file that will be written and where, every `/cron add` line that
46
+ will be run, the budget hint. The setup script is printed into the transcript first, whole and
47
+ as written — it will run with this session's environment, and a dialog clips what does not fit,
48
+ so it is not put in one; a script too long to show is not run at all. A recipe installed from a path
49
+ rather than by name is flagged as not shipped with pi-loops, with the playbooks to read first.
50
+ Nothing has happened yet.
51
+ 5. **The install.** The playbooks are copied to `.agents/skills/<recipe>/`, with an untouched copy of each
52
+ under `.orig/` and a `.recipe.json` record (version, level, when). The directory (and the
53
+ tracker description) is listed in `.git/info/exclude` — git's own place for a rule that belongs to one clone — so the repository
54
+ is not touched and nothing shows in `git status`. pi discovers the copies as `/skill:` commands,
55
+ which is how a person runs one step by hand when the loop's judgement needs checking — once the
56
+ project is trusted: a directory that gains `.agents/skills/` is one pi asks about at its next
57
+ start (`/trust`). The loops do not depend on that; they read the files by path.
58
+ 6. **The setup script**, if any, runs once in the project (issue-loop's creates the tracker's
59
+ labels; it is idempotent). A failure stops before any job is created and prints the output; the
60
+ files stay in place and `/recipe add` again resumes.
61
+ 7. **The jobs**, through the same code path as `/cron add`, each carrying `recipe: "<name>"` so
62
+ `/recipe list` and `/recipe remove` can find them. Each prompt is a pointer:
63
+ `Read .agents/skills/issue-loop/triage.md and do what it says for this repository.` The
64
+ procedure is read fresh on every run; edit the file and the next run follows the edit.
65
+
66
+ `pi-loops recipe list|show|add` in a shell does steps 5 and nothing after: the setup script and the
67
+ jobs are things a person should see before they exist, and only the pi inside `/recipe add` can show
68
+ them. It leaves the files in place and says so; `/recipe add` then finds nothing to copy.
69
+
70
+ ## The autonomy level
71
+
72
+ One dial with three positions, declared per recipe in its manifest and written as the first line
73
+ of each playbook — `Autonomy: propose` — where the model reads it and a person can change it:
74
+
75
+ | level | may |
76
+ |---|---|
77
+ | `report` | read, and file findings; nothing is written outside the inbox |
78
+ | `propose` | also write to the tracker (comments, non-terminal labels) and open draft pull requests; never reach a terminal state |
79
+ | `act` | also promote, close and merge — the terminal states |
80
+
81
+ It is an instruction, not a permission. What actually holds an unattended run are the things that
82
+ were there before recipes: the [dangerous-command policy](loops.md#when-something-looks-wrong),
83
+ `[limits] daily_budget_usd`, and branch protection on the remote. Set the budget before the first
84
+ night ([loops.md](loops.md#what-it-costs-and-capping-it)); each recipe's `show` says roughly what
85
+ its default schedules cost.
86
+
87
+ ## Editing, updating, removing
88
+
89
+ The copies are yours. `/recipe update <name>` after a pi-loops upgrade is a three-way merge of the
90
+ packaged playbook against your copy with the untouched copy in `.orig/` as the base: files you never touched are
91
+ replaced silently, your edits are kept where they do not overlap the upstream change, and a file
92
+ with overlapping edits is **not written** — git's merge with markers goes to `.orig/<file>.merge`,
93
+ and the agent in your chat is handed the four paths (yours, the base, the packaged version, the
94
+ merge) to resolve with you, because conflict markers in a playbook are text the next run would follow.
95
+ A playbook installed by hand, with no untouched copy, is left alone and named.
96
+
97
+ `/recipe remove <name>` removes the recipe's jobs and keeps the files and the loops' notes;
98
+ `--purge` deletes both — of the files, only what the install wrote, since `.agents/skills/` may
99
+ hold the project's own. `/cron` shows recipe jobs like any other, and `/cron set` edits them like
100
+ any other — a recipe is how they were made, not what they are.
101
+
102
+ ## Writing a recipe
103
+
104
+ A directory with a `recipe.toml`; `/recipe add ./path/to/it` installs from a directory as readily
105
+ as from the packaged set (no git URLs: a playbook is an instruction a model will follow unattended,
106
+ and should be read before it is installed).
107
+
108
+ ```toml
109
+ name = "issue-loop" # lowercase, digits, dashes; also the install directory
110
+ summary = "one line"
111
+ needs_tracker = true # gate on docs/agents/issue-tracker.md
112
+ levels = ["propose", "act"] # which positions of the dial the playbooks understand
113
+ setup = "labels.sh" # optional; shown, then run once, before the jobs exist
114
+ files = ["RESEARCH.template.md"] # optional; copied beside the playbooks unchanged
115
+ budget_hint_usd = 10 # optional; only a hint in `show` and the confirmation
116
+
117
+ [[job]]
118
+ name = "issue-triage" # the /cron add --name
119
+ schedule = "*/30 * * * *" # anything /cron add takes, except in/at (a recipe job recurs)
120
+ playbook = "triage.md" # the file this job's runs read
121
+ timeout = "45m" # optional: verify, timeout, thinking, tools, prompt
122
+ ```
123
+
124
+ Each `[[job]]` is checked by building the `/cron add` line it stands for and parsing it with the
125
+ same parser: a bad schedule, a `--timeout` that is not a duration, an expression that never
126
+ matches, is refused when the manifest is read, not on the first run. A playbook is Markdown with
127
+ pi's skill frontmatter (`name`, `description`) and an `Autonomy:` line the wizard will set; the
128
+ rest is what a run should do, written for a sub-agent that has only its notes, the files, and the
129
+ tools. The two packaged recipes are the reference for the voice.
130
+
131
+ ---
132
+
133
+ ## issue-loop
134
+
135
+ The tracker becomes a state machine that two loops turn, with the two decisions that need a person
136
+ — *is this worth building?* and *is this worth merging?* — left in the inbox with everything
137
+ prepared. The state machine is Matt Pocock's `triage` skill's (five states, one category, a brief
138
+ that outlives the codebase it describes) plus three states for the implement side. Every open issue
139
+ carries one category label (`bug` | `enhancement`) and **exactly one** state label:
140
+
141
+ ```mermaid
142
+ stateDiagram-v2
143
+ [*] --> needs_triage: triage loop, first sighting
144
+ needs_triage --> needs_info: triage loop asks
145
+ needs_info --> needs_triage: reporter replies
146
+ needs_triage --> ready_for_agent: maintainer, after reading the brief (act: the loop, for a reproduced bug)
147
+ needs_triage --> ready_for_human: maintainer
148
+ needs_triage --> wontfix: maintainer (act: the loop, for a duplicate or an already-built request)
149
+ ready_for_agent --> agent_working: implement loop claims
150
+ agent_working --> in_review: implement loop opens the PR
151
+ agent_working --> agent_blocked: implement loop cannot finish
152
+ agent_blocked --> ready_for_agent: maintainer answers
153
+ in_review --> agent_working: reviewer requests changes
154
+ in_review --> [*]: maintainer merges (Closes #n)
155
+ ```
156
+
157
+ | state | meaning | leaves it |
158
+ |---|---|---|
159
+ | *(none)* | never looked at | the triage loop labels it `needs-triage` |
160
+ | `needs-triage` | being evaluated; a brief or questions get posted here | the triage loop (→ `needs-info`) or the maintainer (→ the three below) |
161
+ | `needs-info` | waiting on the reporter | the triage loop, once the reporter has replied |
162
+ | `ready-for-agent` | the brief is accepted; an agent may build it | the implement loop claims it |
163
+ | `ready-for-human` | judgement, access, design, manual testing | a person |
164
+ | `agent-working` | claimed: a worktree and a branch exist | the implement loop (→ `in-review` or `agent-blocked`) |
165
+ | `in-review` | a pull request is open | the maintainer merges, or a reviewer requests changes |
166
+ | `agent-blocked` | the loop stopped; its last comment says what it needs | the maintainer answers and moves it back |
167
+ | `wontfix` | closed; the reason is on the issue, and for a rejected enhancement in `.out-of-scope/` | — |
168
+
169
+ Four rules make it run unattended:
170
+
171
+ - **The tracker is the truth; a loop's notes are a cache.** Every run reads labels and comments
172
+ fresh. Notes hold only what the tracker cannot say — the comment timestamp a run last handled,
173
+ the worktree a run was cut off in. A person can move any issue at any time, from the web, and
174
+ the next run sees the new state; two machines sharing a `$HOME` see the same thing.
175
+ - **The claim is the first write.** `agent-working` plus an assignee; a run that finds the label
176
+ already gone picks the next issue. Nothing is held in memory across runs. It is a soft lock —
177
+ the tracker has no compare-and-swap — and at thirty-minute cadence that has been enough.
178
+ - **The brief is the contract.** An *Agent Brief* comment describes behaviour, interfaces and
179
+ acceptance criteria, never file paths or line numbers, so it is still right after the weeks it
180
+ may wait. The implement loop builds from the latest brief; when the brief and the code disagree
181
+ in a way the brief did not foresee, that is `agent-blocked`, not a guess.
182
+ - **Every AI comment says so** on its first line.
183
+
184
+ The two checkpoints are pushed as far right as they go. `#14 brief posted — recommend
185
+ ready-for-agent (bug, reproduced by npm test)` arrives after the loop has reproduced the bug,
186
+ checked `.out-of-scope/` and the open issues for a prior answer, and written the brief; your
187
+ click on the label is the whole approval (`act` makes that click for reproduced bugs). `#12 PR
188
+ opened — <url> (3 commits, checks green)` arrives after the build, the project's check command and
189
+ a test plan; requesting changes sends it back to the loop, which addresses each comment and
190
+ re-requests review. Merging stays with a person at every level.
191
+
192
+ What a run looks like when it goes wrong is in the playbooks themselves: a run cut off
193
+ mid-implementation resumes from its branch and notes; a check command failing twice on one issue
194
+ is a block, not a third attempt; the loop and you editing the same issue is settled by the
195
+ tracker, which wins.
196
+
197
+ Specific to this repository: the labels are on
198
+ [github.com/alphacoder-v0/pi-loops](https://github.com/alphacoder-v0/pi-loops/labels), the check
199
+ command is `npm run ci`, and a release needs a one-time password — so the release side can only
200
+ propose, which is what `changelog-draft` does everywhere.
201
+
202
+ ## autoresearch
203
+
204
+ The shape Karpathy's autoresearch and Arbor made familiar, without a kernel: a **research
205
+ contract** a person writes, a **ledger** of every attempt including the failed ones, one isolated
206
+ worktree per experiment, and a rule that the held-out number decides — never the dev number a run
207
+ was tuning against.
208
+
209
+ - **`RESEARCH.md`** at the project root is the contract: objective, metric and direction, the dev
210
+ and held-out commands (each prints `metric=<number>`), which files may change, which are
211
+ protected, the promotion rule, the time budget. The loop never writes it; a run that finds it
212
+ missing files one finding and stops, and a run that finds a protected file's hash changed does
213
+ the same. `RESEARCH.template.md` is installed beside the playbook; `example/` is a filled-in one
214
+ with a naive exact k-NN in pure Python whose baseline speedup is 1.0.
215
+ - **`results.tsv`** (the path the contract names) is the ledger: one row per experiment with its
216
+ branch, hypothesis, dev and held-out numbers and a status — `baseline`, `running`, `retired`,
217
+ `kept`, `promoted`. A retired row is negative evidence the next run reads before choosing; the
218
+ loop writes the file and never commits it.
219
+ - **One run, one experiment.** Choose a hypothesis the ledger has not retired, write its row as
220
+ `running`, branch in a worktree, change only the editable files, commit, measure on dev. Not
221
+ better: `retired`, worktree removed, branch deleted, no finding — a retired experiment is the
222
+ normal outcome and the row is what matters. Better on dev: measure on held-out; a dev-only gain
223
+ is `retired` too, with that note.
224
+ - **Promotion** is the checkpoint. Under `propose` the finding names the branch and both numbers
225
+ and a person merges; under `act` the loop merges into the checkout's current branch and the
226
+ finding says so. The job runs with `--verify`, and the playbook's *For the checker* section tells
227
+ the checker what verifying means here: check out the branch, run the held-out command yourself,
228
+ keep the finding only if your number satisfies the rule.
229
+
230
+ Try it on the example before your own problem:
231
+
232
+ ```sh
233
+ cp -r .agents/skills/autoresearch/example /tmp/knn && cd /tmp/knn && git init -q && git add -A && git commit -qm baseline
234
+ pi # /recipe add autoresearch, then /cron run autoresearch
235
+ ```
236
+
237
+ The first run measures the baseline and creates the ledger; each later one tries one idea. With
238
+ the example's five-percent rule and a four-second evaluation, a promotion arrives within a few
239
+ runs — and so do the retired ideas, in the ledger, which is what makes the runs after that better.
package/docs/triggers.md CHANGED
@@ -97,7 +97,7 @@ rule evaluation, per window for injected pushes).
97
97
  /triggers sources local checker + each MCP server: state, queued/dropped/deduped, tools
98
98
  (/triggers hooks is the same view)
99
99
  /triggers enable <id> /triggers disable <id> /triggers remove <id> also --all | --all-projects
100
- /triggers set <id> --model | --thinking | --timeout | --host: what the action runs with
100
+ /triggers set <id> --model | --thinking | --timeout: what the action runs with
101
101
  /triggers run <id> check one rule now, without waiting for its poll slot
102
102
  /triggers running sub-agents in flight (dynamic checks and cron runs) /triggers abort <trace>|--all
103
103
  /triggers audit [N]
@@ -1,8 +1,9 @@
1
1
  # Troubleshooting
2
2
 
3
3
  **Nothing fires.** `/cron scheduler`: is this process the timer owner or standby? If the owner died
4
- without cleanup, a standby takes over within 90 seconds. Jobs show `next …` when enabled. A plain
5
- job marked `[dormant …]` only fires in the session that created it (`--resume` it); a loop marked
4
+ without cleanup, a standby takes over within 90 seconds. A job shows `next …` when it will run
5
+ *here*. A plain job marked `[session <id> — resume it to run]` belongs to another session and fires
6
+ only there (`--resume` it, or `/cron add` it again in this chat); a loop marked
6
7
  `[orphan: cwd missing]` was disabled because its checkout is gone.
7
8
 
8
9
  **A run failed.** `/cron runs` shows the error; `/cron trace <job> 1` shows the sub-agent's
@@ -116,9 +116,7 @@ weaker, and worth converting whenever one of them breaks.
116
116
  says so as a count — without it, a job somewhere else is indistinguishable from a job that is
117
117
  gone. Jobs and rules are counted and labelled apart, each line naming the command that lists
118
118
  them (`/cron all`, `/triggers rules --all`): one number covering both agrees with neither
119
- command, and the line exists to send you to the command. A job stamped with a hostname this
120
- machine no longer has (a rebuilt container, a rename) is listed and marked, the way `/cron`
121
- lists it, rather than filtered out of sight.
119
+ command, and the line exists to send you to the command.
122
120
  What counts as "this project" is what the extension says it is — symlinks resolved, `$HOME`
123
121
  too broad to be one — because a panel that disagrees with the command is worse than either.
124
122
  `test/web.test.ts`: *the panel and /cron agree about what this project is*.
@@ -173,6 +171,8 @@ weaker, and worth converting whenever one of them breaks.
173
171
  be read as prose and waved through. `test/web-page.test.ts`: *a confirmation shows what is
174
172
  about to run*.
175
173
  - [x] Enter does not approve: the focus starts on cancel.
174
+ - [x] A slash command that waits in a dialog is not reported as timed out while the person reads:
175
+ the browser waits up to fifteen minutes for a `/…` prompt, sixty seconds for anything else.
176
176
  - [x] Clicking away from any dialog closes it. `test/web-page.test.ts`: *clicking outside a dialog
177
177
  closes it*.
178
178
 
@@ -213,6 +213,12 @@ weaker, and worth converting whenever one of them breaks.
213
213
  left is a file that `resume` lists, labelled by what was said in it rather than by a filename.
214
214
  `test/web.test.ts`: *a new session is a path pi has not written yet, and going back is one it
215
215
  has*. `test/web-page.test.ts`: *the session you are in is not offered as one to go back to*.
216
+ - [x] **The session commands are two words here.** `/sessions` prints this project's sessions into
217
+ the feed — short id, when, what was first said — and `/session export [path]` / `/session
218
+ import <path>` are the archive commands, sent as `/session-export` / `/session-import`. pi's
219
+ own `/session` is a terminal command that does not exist over rpc, which is why the extension
220
+ had to hyphenate its names in the terminal and why the page is free to spell them plainly.
221
+ `test/web-page.test.ts`: */sessions and /session export are typed here*.
216
222
  - [x] A session started here is a session pi-loops recognises. pi names the sessions it starts after
217
223
  their id and the front end cannot know that id in time, so anything identifying a session by
218
224
  its file name is wrong about this one — which parked every inject-and-run cron job made in a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alphacoder-v0/pi-loops",
3
- "version": "0.17.6",
3
+ "version": "0.19.2",
4
4
  "description": "An automation layer for pi: cron + stateful loops with a triage inbox, maker/checker verification, dynamic triggers, MCP notifications and tools, lifecycle hooks, session archives. A non-invasive pi extension.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -32,6 +32,7 @@
32
32
  "skills",
33
33
  "docs",
34
34
  "examples",
35
+ "recipes",
35
36
  "README.md",
36
37
  "README.zh-CN.md",
37
38
  "CHANGELOG.md",
@@ -0,0 +1,31 @@
1
+ # Issue tracker: GitHub
2
+
3
+ Work items for this repository live as GitHub Issues; pull requests are the review surface. Use
4
+ the `gh` CLI for every operation; it infers the repository from the remote when run in a clone.
5
+
6
+ ## Operations
7
+
8
+ - **List**: `gh issue list --state open --limit 200 --json number,title,labels,updatedAt,author` (add `--label <state>` to filter by state)
9
+ - **Read**: `gh issue view <n> --comments --json number,title,body,labels,author,createdAt,comments`
10
+ - **Comment**: `gh issue comment <n> --body "..."` (a heredoc for multi-line bodies)
11
+ - **Change state**: `gh issue edit <n> --add-label "<new state>" --remove-label "<old state>"` — exactly one state label at a time
12
+ - **Claim**: `gh issue edit <n> --add-label agent-working --remove-label ready-for-agent --add-assignee @me`
13
+ - **Close**: `gh issue close <n> --comment "..."`
14
+ - **Pull requests**: `gh pr create`, `gh pr list --state open --json number,url,headRefName,reviewDecision,comments,updatedAt`, `gh pr view <n> --comments`, `gh pr diff <n>`
15
+
16
+ ## State vocabulary
17
+
18
+ One category label (`bug` | `enhancement`) and exactly one state label per open issue. The state
19
+ labels are `needs-triage`, `needs-info`, `ready-for-agent`, `ready-for-human`, `agent-working`,
20
+ `in-review`, `agent-blocked`, `wontfix`. The recipe's setup script creates them; a playbook that
21
+ moves an issue names the label it moves to.
22
+
23
+ ## Blocking
24
+
25
+ A `Blocked by: #<n>, #<m>` line near the top of an issue body (or the tracker's native
26
+ dependency) gates it: the item is unblocked when every listed issue is closed.
27
+
28
+ ## Who counts as the agent
29
+
30
+ Comments and assignments made by loops are made as the account `gh` is signed in as. Every comment
31
+ a loop posts starts with a line saying it was generated by AI.
@@ -0,0 +1,38 @@
1
+ # Issue tracker: local Markdown
2
+
3
+ Work items for this repository live as Markdown files in `.scratch/issues/`, one file per item,
4
+ committed with the code. There are no pull requests: a change is a branch, and the review
5
+ checkpoint is a finding in the inbox that names the branch.
6
+
7
+ ## Layout
8
+
9
+ - `.scratch/issues/<NN>-<slug>.md`, numbered from `01`; the number is the item's id (`#07`)
10
+ - The first lines of a file:
11
+
12
+ ```markdown
13
+ # 07: <title>
14
+
15
+ **Category:** bug | enhancement
16
+ **Status:** needs-triage
17
+ **Blocked by:** 03, 05 (or "none")
18
+ ```
19
+
20
+ - Discussion appends to the bottom under `## Comments`, one `### <date> — <who>` heading per
21
+ comment; a comment by a loop starts with a line saying it was generated by AI
22
+ - An *Agent Brief* is a `## Agent Brief` section in the file, not a comment
23
+
24
+ ## Operations
25
+
26
+ - **List**: read every file in `.scratch/issues/`; the `**Status:**` line is the state
27
+ - **Read**: read the file
28
+ - **Comment**: append under `## Comments`
29
+ - **Change state**: edit the `**Status:**` line — exactly one state
30
+ - **Claim**: set `**Status:** agent-working` and add `**Assignee:** <who>`, and commit that edit on the work branch before any other work
31
+ - **Close**: set the status to `wontfix` or `done` and move the file to `.scratch/issues/closed/`
32
+ - **Review**: push the branch (or leave it local) and file a finding that names the branch and the item; a person merges with git
33
+
34
+ ## State vocabulary
35
+
36
+ `needs-triage`, `needs-info`, `ready-for-agent`, `ready-for-human`, `agent-working`, `in-review`,
37
+ `agent-blocked`, `wontfix`, plus `done` for a merged item. A playbook that moves an item names the
38
+ state it moves to.
@@ -0,0 +1,12 @@
1
+ `/recipe add {recipe}` stopped before installing anything: that recipe's playbooks read `docs/agents/issue-tracker.md` on every run to learn where this project's work items live and how to list, read, comment on, label and close them — and `{project}` has no such file yet. Write it with me now. This is a conversation, not a template fill: explore first, propose, and write only after I have agreed.
2
+
3
+ 1. **Explore.** Run `git remote -v` in `{project}`. Look for an existing `.scratch/` directory (a sign the local Markdown tracker is already in use), and for an `## Agent skills` section in `AGENTS.md` or `CLAUDE.md` (a sign Matt Pocock's engineering skills were set up here; if so, their `docs/agents/issue-tracker.md` convention is the one we are writing, and it may already be partly there).
4
+
5
+ 2. **Propose, one question.** Lead with the recommendation so I can accept it in a word:
6
+ - a GitHub remote → recommend **GitHub Issues** (the `gh` CLI; check `gh auth status` works);
7
+ - no remote, or I say so → recommend **local Markdown**: one file per item under `.scratch/issues/`, a `**Status:**` line as the state.
8
+ Anything else (GitLab, Jira, Linear): ask me to describe the workflow in a paragraph and record it as prose.
9
+
10
+ 3. **Write `docs/agents/issue-tracker.md`** from the matching template in `{templates}/` (`issue-tracker-github.md` or `issue-tracker-local.md`), adjusted to what we agreed. Show me the draft before writing. Keep the state vocabulary section as it is: the playbooks name those states. The file stays out of the repository (the wizard lists it in `.git/info/exclude`, like the playbooks), but it is still read by every run: keep it to how the tracker works — no secrets, no tokens.
11
+
12
+ 4. **Stop there.** The wizard is waiting for this file: as soon as this turn ends with it in place, `/recipe add {recipe}` resumes on its own — the level question, the setup script, the confirmation. Do not create cron jobs, labels, worktrees or branches yourself, and do not tell me to run anything; the wizard does the rest after showing me exactly what it will do.
@@ -0,0 +1,27 @@
1
+ # autoresearch — one experiment per run, a ledger, held-out evidence before promotion
2
+
3
+ The shape is the one Karpathy's autoresearch and Arbor made familiar: a **research contract** a
4
+ person writes (objective, metric, which files may change, which are protected, dev and held-out
5
+ commands, a promotion rule), a **ledger** of every attempt including the failed ones, an isolated
6
+ worktree per experiment, and a rule that the held-out number decides — never the dev number a run
7
+ was tuning against. What pi-loops adds is that nothing here needs a kernel: the contract and the
8
+ ledger are files in the repository, each run is one stateful loop run, and the checker of
9
+ `--verify` is the evaluator that re-measures before a promotion reaches you.
10
+
11
+ | file | what it is |
12
+ |---|---|
13
+ | [recipe.toml](recipe.toml) | one job, `every 1h`, `--verify`, 40-minute timeout |
14
+ | [experiment.md](experiment.md) | the playbook: contract → ledger → one hypothesis → worktree → measure → row → maybe promote |
15
+ | [RESEARCH.template.md](RESEARCH.template.md) | copy to the project root as `RESEARCH.md` and fill in; the loop stops until it exists |
16
+ | [example/](example/) | a filled-in contract with a naive exact k-NN in pure Python: `bash eval.sh dev` prints `metric=<speedup>` |
17
+
18
+ Try it on the example first:
19
+
20
+ ```sh
21
+ cp -r .agents/skills/autoresearch/example /tmp/knn && cd /tmp/knn && git init -q && git add -A && git commit -qm "baseline"
22
+ pi # then: /recipe add autoresearch and /cron run autoresearch
23
+ ```
24
+
25
+ The first run measures the baseline and writes `results.tsv`; the next ones each try one idea.
26
+ Under `propose` a promotable branch arrives in `/inbox` with both numbers; under `act` it is
27
+ merged into the checkout and the finding says so.
@@ -0,0 +1,37 @@
1
+ # Research contract
2
+
3
+ Copy this file to the project root as `RESEARCH.md` and fill it in. The autoresearch loop reads it
4
+ on every run and never changes it; a run that finds it missing or its protected files altered stops
5
+ with a finding. The example in `.agents/skills/autoresearch/example/` is a filled-in one.
6
+
7
+ ## Objective
8
+
9
+ One sentence a person can check: what better means. *Make the exact k-nearest-neighbour search in
10
+ `solution.py` faster while keeping its output identical.*
11
+
12
+ ## Metric
13
+
14
+ - **name:** `speedup` — **higher is better** (or: `val_loss`, lower is better)
15
+ - **dev command:** `bash eval.sh dev` — prints one line `metric=<number>`; used to iterate
16
+ - **held-out command:** `bash eval.sh test` — same shape, other data; used only to decide promotion
17
+ - **baseline:** measured by the first run on the untouched code and written to the ledger; leave blank
18
+
19
+ ## Files
20
+
21
+ - **editable:** `solution.py` (one path per line; nothing else may change)
22
+ - **protected:** `eval.sh`, `eval.py`, `task.py` — the loop records their `sha256sum` in the ledger's
23
+ header on the first run and refuses to work if any differs since
24
+
25
+ ## Promotion rule
26
+
27
+ A branch is promotable when its **held-out** metric beats the best promoted (or the baseline) by at
28
+ least **5%**, and the dev metric agreed. Under `propose` the loop files a finding and a person
29
+ merges; under `act` the loop merges the branch into this checkout's current branch itself.
30
+
31
+ ## Budget and layout
32
+
33
+ - **per experiment:** at most 10 minutes of evaluation; a run that exceeds it retires the experiment
34
+ - **ledger:** `research/results.tsv` (created by the loop, never committed by it)
35
+ - **worktrees:** `../<repo>-research/<NNN>`, branch `research/<NNN>-<slug>`; retired branches are deleted, the ledger row stays
36
+ - **directions worth trying first (optional):** …
37
+ - **directions not to try (optional):** …
@@ -0,0 +1,31 @@
1
+ # Research contract
2
+
3
+ ## Objective
4
+
5
+ Make the exact k-nearest-neighbour search in `solution.py` faster while keeping its output
6
+ identical to the reference.
7
+
8
+ ## Metric
9
+
10
+ - **name:** `speedup` — higher is better (wall-clock of the reference divided by wall-clock of `solution.py`, measured in the same process, median of 3)
11
+ - **dev command:** `bash eval.sh dev`
12
+ - **held-out command:** `bash eval.sh test`
13
+ - **baseline:** (written by the first run)
14
+
15
+ ## Files
16
+
17
+ - **editable:** `solution.py`
18
+ - **protected:** `eval.sh`, `eval.py`
19
+
20
+ ## Promotion rule
21
+
22
+ Held-out `speedup` at least 5% above the best promoted row (or the baseline), with the dev run
23
+ agreeing and the output check passing.
24
+
25
+ ## Budget and layout
26
+
27
+ - **per experiment:** at most 5 minutes of evaluation
28
+ - **ledger:** `results.tsv`
29
+ - **worktrees:** `../<repo>-research/<NNN>`, branch `research/<NNN>-<slug>`
30
+ - **directions worth trying first:** partial selection instead of a full sort; avoid recomputing the query's coordinates in the inner loop; batch the distance computation
31
+ - **directions not to try:** approximate search (the output must stay exact); C extensions or third-party packages (standard library only)
@@ -0,0 +1,60 @@
1
+ """Protected: generates the data, checks exactness, measures speedup. Do not edit in an experiment.
2
+
3
+ Usage: python3 eval.py dev|test → prints `metric=<speedup>` (or `metric=0 error=...`).
4
+
5
+ The reference is the naive algorithm frozen here, so a speedup is relative to the starting point
6
+ and independent of the machine's absolute speed. Timings are the median of three runs of each.
7
+ """
8
+ import random
9
+ import statistics
10
+ import sys
11
+ import time
12
+
13
+ import solution
14
+
15
+ SPLITS = {"dev": (1, 2500, 80, 8, 4), "test": (2, 3000, 90, 8, 5)} # seed, n points, n queries, dims, k
16
+
17
+
18
+ def reference(points, queries, k):
19
+ # Deliberately the same loops as the untouched solution.py, so the baseline speedup is 1.0.
20
+ out = []
21
+ for q in queries:
22
+ dists = []
23
+ for i, p in enumerate(points):
24
+ d = 0.0
25
+ for a, b in zip(p, q):
26
+ d += (a - b) * (a - b)
27
+ dists.append((d, i))
28
+ dists.sort()
29
+ out.append([i for _, i in dists[:k]])
30
+ return out
31
+
32
+
33
+ def timed(fn, *args):
34
+ runs = []
35
+ for _ in range(3):
36
+ t0 = time.perf_counter()
37
+ fn(*args)
38
+ runs.append(time.perf_counter() - t0)
39
+ return statistics.median(runs)
40
+
41
+
42
+ def main():
43
+ split = sys.argv[1] if len(sys.argv) > 1 else "dev"
44
+ seed, n, m, dims, k = SPLITS[split]
45
+ rng = random.Random(seed)
46
+ points = [[rng.random() for _ in range(dims)] for _ in range(n)]
47
+ queries = [[rng.random() for _ in range(dims)] for _ in range(m)]
48
+ expected = reference(points, queries, k)
49
+ got = solution.knn(points, queries, k)
50
+ if got != expected:
51
+ print("metric=0 error=output differs from the reference")
52
+ return 1
53
+ ref_t = timed(reference, points, queries, k)
54
+ sol_t = timed(solution.knn, points, queries, k)
55
+ print(f"metric={ref_t / sol_t:.3f} split={split} ref_s={ref_t:.3f} sol_s={sol_t:.3f}")
56
+ return 0
57
+
58
+
59
+ if __name__ == "__main__":
60
+ sys.exit(main())
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+ # Protected. `bash eval.sh dev` iterates; `bash eval.sh test` decides promotion.
3
+ cd "$(dirname "$0")" && exec python3 eval.py "${1:-dev}"
@@ -0,0 +1,20 @@
1
+ """The editable file: exact k-nearest neighbours, deliberately naive.
2
+
3
+ `knn(points, queries, k)` returns, for each query, the indices of the k closest points by squared
4
+ Euclidean distance, ties broken by lower index. The evaluator checks the output against a
5
+ reference and times this function; only this file may change.
6
+ """
7
+
8
+
9
+ def knn(points, queries, k):
10
+ out = []
11
+ for q in queries:
12
+ dists = []
13
+ for i, p in enumerate(points):
14
+ d = 0.0
15
+ for a, b in zip(p, q):
16
+ d += (a - b) * (a - b)
17
+ dists.append((d, i))
18
+ dists.sort()
19
+ out.append([i for _, i in dists[:k]])
20
+ return out