@alphacoder-v0/pi-loops 0.20.0 → 0.22.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 (48) hide show
  1. package/CHANGELOG.md +143 -0
  2. package/CONTRACT.md +139 -0
  3. package/README.md +11 -7
  4. package/README.zh-CN.md +6 -4
  5. package/docs/cli.md +19 -1
  6. package/docs/downstream.md +60 -0
  7. package/docs/goal.md +4 -2
  8. package/docs/loops.md +55 -9
  9. package/docs/recipes.md +94 -18
  10. package/docs/session-archive.md +8 -3
  11. package/docs/web-ui-parity.md +11 -1
  12. package/package.json +5 -2
  13. package/recipes/_tracker/issue-tracker-local.md +3 -1
  14. package/recipes/autoresearch/experiment.md +30 -6
  15. package/recipes/autoresearch/recipe.toml +2 -0
  16. package/recipes/changelog-draft/recipe.toml +4 -0
  17. package/recipes/changelog-draft/release.md +8 -3
  18. package/recipes/ci-sweeper/recipe.toml +4 -0
  19. package/recipes/ci-sweeper/sweep.md +2 -1
  20. package/recipes/daily-digest/digest.md +5 -4
  21. package/recipes/daily-digest/recipe.toml +2 -0
  22. package/recipes/deps-sweeper/README.md +11 -0
  23. package/recipes/deps-sweeper/audit.md +99 -0
  24. package/recipes/deps-sweeper/recipe.toml +17 -0
  25. package/recipes/ecosystem/recipe.toml +3 -0
  26. package/recipes/ecosystem/scan.md +1 -1
  27. package/recipes/issue-loop/implement.md +17 -4
  28. package/recipes/issue-loop/labels.sh +18 -0
  29. package/recipes/issue-loop/recipe.toml +2 -0
  30. package/recipes/issue-loop/triage.md +16 -2
  31. package/recipes/pr-watch/recipe.toml +3 -0
  32. package/skills/pi-loops/SKILL.md +3 -2
  33. package/src/archive.ts +97 -2
  34. package/src/cli.ts +103 -16
  35. package/src/goal.ts +11 -0
  36. package/src/hooks.ts +25 -1
  37. package/src/host-runtime.ts +4 -8
  38. package/src/inbox.ts +118 -55
  39. package/src/job-signal.ts +78 -0
  40. package/src/pi-loops.ts +177 -109
  41. package/src/presence.ts +5 -0
  42. package/src/protocol.ts +45 -2
  43. package/src/recipe.ts +158 -2
  44. package/src/scheduler.ts +6 -2
  45. package/src/store.ts +8 -3
  46. package/src/tools.ts +17 -11
  47. package/src/triggers.ts +8 -3
  48. package/src/web.mjs +73 -7
package/docs/recipes.md CHANGED
@@ -14,17 +14,21 @@ anything a person could not have typed.
14
14
  /recipe remove issue-loop its jobs go; the files stay unless you say --purge
15
15
  ```
16
16
 
17
- Seven recipes ship, 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
- | [daily-digest](../recipes/daily-digest/) | `daily-digest` at 09:00 | one finding saying what needs a look today — open items, a red CI, a loop that keeps failing — or none | `report` |
24
- | [pr-watch](../recipes/pr-watch/) | `pr-watch` every 15 min | the open pull requests, and only what changed about them: a red check, a conflict, a review waiting, an author who answered | `report`, `propose` |
25
- | [ci-sweeper](../recipes/ci-sweeper/) | `ci-sweeper` every 15 min, 40-minute timeout | a red default branch reported, or repaired in a worktree and put up as a pull request; the same failure twice unfixed is a stop | `report`, `propose` |
26
- | [changelog-draft](../recipes/changelog-draft/) | `changelog-draft` weekdays at 18:00 | when the default branch is ahead of the last tag, the changelog entry and a version, drafted — or put in a release pull request | `report`, `propose` |
27
- | [ecosystem](../recipes/ecosystem/) | `ecosystem` Mondays at 10:00 | who uses or forks this project, and the one reply or upstream invitation worth sending, drafted as a finding a person sends | `propose` |
17
+ Eight recipes ship, and the shape is meant to be copied. Three are **starters** — they read, and
18
+ file findings, and are the ones to install first; the rest are **advanced** — they write to a
19
+ worktree, a tracker or a pull request, and want their playbooks read before the level question is
20
+ answered. `/recipe list` shows them in those two groups.
21
+
22
+ | recipe | tier | jobs | what it runs | levels |
23
+ |---|---|---|---|---|
24
+ | [issue-loop](../recipes/issue-loop/) | advanced | `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` |
25
+ | [autoresearch](../recipes/autoresearch/) | advanced | `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` |
26
+ | [daily-digest](../recipes/daily-digest/) | starter | `daily-digest` at 09:00 | one finding saying what needs a look today — open items, a red CI, a loop that keeps failing — or none | `report` |
27
+ | [pr-watch](../recipes/pr-watch/) | starter | `pr-watch` every 15 min | the open pull requests, and only what changed about them: a red check, a conflict, a review waiting, an author who answered | `report`, `propose` |
28
+ | [ci-sweeper](../recipes/ci-sweeper/) | advanced | `ci-sweeper` every 15 min, 40-minute timeout | a red default branch reported, or repaired in a worktree and put up as a pull request; the same failure twice unfixed is a stop | `report`, `propose` |
29
+ | [changelog-draft](../recipes/changelog-draft/) | starter | `changelog-draft` weekdays at 18:00 | when the default branch is ahead of the last tag, the changelog entry and a version, drafted — or put in a release pull request | `report`, `propose` |
30
+ | [ecosystem](../recipes/ecosystem/) | advanced | `ecosystem` Mondays at 10:00 | who uses or forks this project, and the one reply or upstream invitation worth sending, drafted as a finding a person sends | `propose` |
31
+ | [deps-sweeper](../recipes/deps-sweeper/) | advanced | `deps-sweeper` Mondays at 08:00, 40-minute timeout | the advisories and major-version gaps not already in its notes; under `propose`, the patch and minor updates applied in a worktree, checked, and put up as one pull request | `report`, `propose` |
28
32
 
29
33
  ## What `/recipe add` does, in order
30
34
 
@@ -43,28 +47,45 @@ Seven recipes ship, and the shape is meant to be copied:
43
47
  `--level act` skips it.
44
48
  3. **A name check.** A job with the same name already in `jobs.json` stops the install and says
45
49
  which recipe or `/cron add` it came from.
46
- 4. **The confirmation.** Every file that will be written and where, every `/cron add` line that
47
- will be run, the budget hint. The setup script is printed into the transcript first, whole and
50
+ 4. **The preflight.** What the project has and lacks for this recipe's runs, checked now and
51
+ printed in the confirmation under *Before the first run* — reported, never enforced, so a
52
+ missing `gh` login is a line you read tonight rather than an empty inbox tomorrow morning.
53
+ A manifest names its checks (`needs`, and `needs_propose` for the levels that push and open
54
+ pull requests); a recipe that reads the tracker is checked for `gh` only when
55
+ `docs/agents/issue-tracker.md` uses it, so a local Markdown tracker asks for nothing.
56
+
57
+ | check | what is looked at |
58
+ |---|---|
59
+ | `gh` | `gh` on the PATH and `gh auth status` succeeding (10 seconds, then "could not check") |
60
+ | `git-remote` | `git remote get-url origin` |
61
+ | `ci-workflows` | a `.yml` under `.github/workflows/` |
62
+ | `lockfile` | one of the package managers' lockfiles at the root |
63
+ | `tracker-github` | the tracker description names GitHub; a local one has no pull requests to watch |
64
+
65
+ `/recipe show` runs the same checks at the recipe's highest level.
66
+ 5. **The confirmation.** Every file that will be written and where, what the playbooks forbid a
67
+ run to do (each playbook's `## Never` section, the same lines `/recipe show` prints), every
68
+ `/cron add` line that will be run, the budget hint, the preflight. The setup script is printed into the transcript first, whole and
48
69
  as written — it will run with this session's environment, and a dialog clips what does not fit,
49
70
  so it is not put in one; a script too long to show is not run at all. A recipe installed from a path
50
71
  rather than by name is flagged as not shipped with pi-loops, with the playbooks to read first.
51
72
  Nothing has happened yet.
52
- 5. **The install.** The playbooks are copied to `.agents/skills/<recipe>/`, with an untouched copy of each
73
+ 6. **The install.** The playbooks are copied to `.agents/skills/<recipe>/`, with an untouched copy of each
53
74
  under `.orig/` and a `.recipe.json` record (version, level, when). The directory (and the
54
75
  tracker description) is listed in `.git/info/exclude` — git's own place for a rule that belongs to one clone — so the repository
55
76
  is not touched and nothing shows in `git status`. pi discovers the copies as `/skill:` commands,
56
77
  which is how a person runs one step by hand when the loop's judgement needs checking — once the
57
78
  project is trusted: a directory that gains `.agents/skills/` is one pi asks about at its next
58
79
  start (`/trust`). The loops do not depend on that; they read the files by path.
59
- 6. **The setup script**, if any, runs once in the project (issue-loop's creates the tracker's
80
+ 7. **The setup script**, if any, runs once in the project (issue-loop's creates the tracker's
60
81
  labels; it is idempotent). A failure stops before any job is created and prints the output; the
61
82
  files stay in place and `/recipe add` again resumes.
62
- 7. **The jobs**, through the same code path as `/cron add`, each carrying `recipe: "<name>"` so
83
+ 8. **The jobs**, through the same code path as `/cron add`, each carrying `recipe: "<name>"` so
63
84
  `/recipe list` and `/recipe remove` can find them. Each prompt is a pointer:
64
85
  `Read .agents/skills/issue-loop/triage.md and do what it says for this repository.` The
65
86
  procedure is read fresh on every run; edit the file and the next run follows the edit.
66
87
 
67
- `pi-loops recipe list|show|add` in a shell does steps 5 and nothing after: the setup script and the
88
+ `pi-loops recipe list|show|add` in a shell does step 6 and nothing after: the setup script and the
68
89
  jobs are things a person should see before they exist, and only the pi inside `/recipe add` can show
69
90
  them. It leaves the files in place and says so; `/recipe add` then finds nothing to copy.
70
91
 
@@ -85,6 +106,29 @@ were there before recipes: the [dangerous-command policy](loops.md#when-somethin
85
106
  night ([loops.md](loops.md#what-it-costs-and-capping-it)); each recipe's `show` says roughly what
86
107
  its default schedules cost.
87
108
 
109
+ ## Checkpoints
110
+
111
+ A checkpoint is a finding that asks a person to decide, and at three in the morning it is read by
112
+ someone who has not seen the tracker since yesterday. So every one of them, in every packaged
113
+ playbook, says the same three things in one line: **the decision**, **what waits on it**, and
114
+ **what happens if nobody acts**.
115
+
116
+ ```text
117
+ #14 brief posted — recommend ready-for-agent (bug, reproduced by npm test) · waits: the implement loop skips it until labeled · if not: stays needs-triage, no reminder
118
+ promote research/007-argpartition: dev 1.0 → 3.1, held-out 1.0 → 2.8 · waits: your merge · if not: the branch and worktree stay, later runs build on it as the best kept
119
+ CI red on main: test/web.test.ts — two fixes did not hold · waits: a person · if not: this signature is not retried
120
+ ```
121
+
122
+ The shape is also how the inbox tells a checkpoint from news: ` · waits:` marks the entry, it is
123
+ listed first with a `⚑`, and `/inbox` counts it apart (`3 new, 1 needs a decision`;
124
+ [loops.md](loops.md#the-inbox)).
125
+
126
+ The third clause is the one that matters overnight. A loop that waits for an answer says so and
127
+ keeps going with everything the answer does not block; a loop that would do something on silence
128
+ (retry, re-propose when the head moves, keep building on a kept branch) says that instead, so a
129
+ person who decides to do nothing knows what nothing buys. Findings that are only news — a merged
130
+ pull request, a red check, a digest line — are not checkpoints and do not carry the clauses.
131
+
88
132
  ## Editing, updating, removing
89
133
 
90
134
  The copies are yours. `/recipe update <name>` after a pi-loops upgrade is a three-way merge of the
@@ -109,7 +153,11 @@ and should be read before it is installed).
109
153
  ```toml
110
154
  name = "issue-loop" # lowercase, digits, dashes; also the install directory
111
155
  summary = "one line"
156
+ tier = "advanced" # optional; "starter" reads and files findings, "advanced" (the default) writes somewhere
157
+ useful_when = ["one sentence"] # optional; the situations it is for, printed by `show`
112
158
  needs_tracker = true # gate on docs/agents/issue-tracker.md
159
+ needs = ["gh", "ci-workflows"] # optional; preflight checks at every level (gh, git-remote, ci-workflows, lockfile, tracker-github)
160
+ needs_propose = ["git-remote"] # optional; the same, added at propose and act
113
161
  levels = ["propose", "act"] # which positions of the dial the playbooks understand
114
162
  setup = "labels.sh" # optional; shown, then run once, before the jobs exist
115
163
  files = ["RESEARCH.template.md"] # optional; copied beside the playbooks unchanged
@@ -127,7 +175,9 @@ same parser: a bad schedule, a `--timeout` that is not a duration, an expression
127
175
  matches, is refused when the manifest is read, not on the first run. A playbook is Markdown with
128
176
  pi's skill frontmatter (`name`, `description`) and an `Autonomy:` line the wizard will set; the
129
177
  rest is what a run should do, written for a sub-agent that has only its notes, the files, and the
130
- tools. The two packaged recipes are the reference for the voice.
178
+ tools. End it with a `## Never` section: the lines under it are what `/recipe show` and the
179
+ install confirmation print as the run's safety envelope, and every packaged playbook has one. The
180
+ packaged recipes are the reference for the voice.
131
181
 
132
182
  ---
133
183
 
@@ -195,6 +245,14 @@ mid-implementation resumes from its branch and notes; a check command failing tw
195
245
  is a block, not a third attempt; the loop and you editing the same issue is settled by the
196
246
  tracker, which wins.
197
247
 
248
+ With the local Markdown tracker the shape is the same and the pull request is not there: the
249
+ implement loop pushes its branch (or leaves it local), sets the item to `in-review` and files a
250
+ finding naming the branch. Requesting changes is a comment under the item's `## Comments` and the
251
+ status set back to `agent-working`; merging is `git merge` by a person, the status set to `done`
252
+ and the file moved to `closed/`. The setup script creates no labels there — it says so and exits —
253
+ and does the same when `gh` is missing or not logged in, so the install goes through and the
254
+ first run reports what it cannot do.
255
+
198
256
  Specific to this repository: the labels are on
199
257
  [github.com/alphacoder-v0/pi-loops](https://github.com/alphacoder-v0/pi-loops/labels), the check
200
258
  command is `npm run ci`, and a release needs a one-time password — so the release side can only
@@ -245,6 +303,19 @@ bringing back, an unanswered question. Each draft is a finding with the text rea
245
303
  claiming the finding is what sends it, as the person, from their chat. The loop itself never
246
304
  posts, comments or opens anything anywhere, which is why its only level is `propose`.
247
305
 
306
+ ## deps-sweeper
307
+
308
+ Mondays at eight. The loop finds the project's package manager from its lockfile and runs the
309
+ audit and outdated commands that manager has. The **advisory id** and the **package at a major
310
+ version** are the memory: under `report` each advisory not in the notes is one finding, each direct
311
+ dependency a whole major behind is one finding once, and a finding is repeated only when it
312
+ changes — an advisory fixed, a major that moved again. Under `propose` the loop also applies the
313
+ patch and minor updates the manager offers, in a worktree, as **one** pull request per run: the
314
+ project's check command has to be green first, a package on the playbook's deny list is never
315
+ touched, a major is never applied (it is a finding, and a person's decision), and the same package
316
+ attempted twice without a green check is a stop with a finding that says so. The lockfile is
317
+ only ever written by the package manager, never by hand. Merging stays with a person at every level.
318
+
248
319
  ## Another tracker
249
320
 
250
321
  The shape Karpathy's autoresearch and Arbor made familiar, without a kernel: a **research
@@ -267,6 +338,11 @@ was tuning against.
267
338
  better: `retired`, worktree removed, branch deleted, no finding — a retired experiment is the
268
339
  normal outcome and the row is what matters. Better on dev: measure on held-out; a dev-only gain
269
340
  is `retired` too, with that note.
341
+ - **A streak of retired rows is the ledger's own checkpoint.** Five in a row since the last kept
342
+ row files one finding asking whether the contract's directions, metric or budget are wrong, and
343
+ the loop keeps going only in idea classes it has not tried; ten in a row stops the loop until
344
+ `RESEARCH.md` changes (its hash is in the notes). An hourly loop in the wrong search space is the
345
+ expensive failure, and the person who wrote the contract is the one who can fix it.
270
346
  - **Promotion** is the checkpoint. Under `propose` the finding names the branch and both numbers
271
347
  and a person merges; under `act` the loop merges into the checkout's current branch and the
272
348
  finding says so. The job runs with `--verify`, and the playbook's *For the checker* section tells
@@ -15,7 +15,7 @@ pi-session-<first 16 chars of the id>.pisession uncompressed ustar, mode 0600,
15
15
 
16
16
  ```text
17
17
  /session-export [path] [--exclude-triggers]
18
- /session-import <path> [--activate-triggers=on|off] [--cwd <dir>] [--resume]
18
+ /session-import <path> [--activate-triggers=off|ask|on] [--cwd <dir>] [--resume]
19
19
  ```
20
20
 
21
21
  Both commands print a sensitivity warning before doing anything, success or failure.
@@ -24,8 +24,13 @@ the source machine's parent-session pointer dropped)
24
24
  into this project's session directory and rewrites the sidecars: automation disabled
25
25
  unless `--activate-triggers=on`, running markers / errors / overlap counters cleared, ids
26
26
  regenerated when they collide with existing ones (loop state follows the new id), non-stateful jobs
27
- rebound to the imported session. Afterwards you are asked once whether to re-enable what was
28
- enabled in the source. `--resume` switches to the imported session; otherwise `pi --session <path>`.
27
+ rebound to the imported session. `--resume` switches to the imported session; otherwise
28
+ `pi --session <path>`.
29
+
30
+ `--activate-triggers=off|ask|on` decides what happens to the automation the archive carries, and
31
+ means the same thing here as in `pi-loops import`. The default is `ask`: the import leaves it
32
+ disabled and then asks once, where there is a terminal or a UI to ask in, whether to enable what
33
+ was enabled in the source. `off` never asks; `on` activates it as part of the import.
29
34
 
30
35
  Validation: manifest schema, session checksum, path traversal, and size caps (session 50 MiB,
31
36
  sidecars 2 MiB). Every sidecar is validated before the session file is written, and store writes
@@ -17,7 +17,9 @@ weaker, and worth converting whenever one of them breaks.
17
17
  - [x] Enter does nothing while an input method is composing — typing Chinese, Japanese or Korean
18
18
  means Enter picks a candidate, not "send". `test/web-page.test.ts`: *Enter while an input
19
19
  method is mid-word does not send*.
20
- - [x] Submitting while a turn is running queues instead of racing it, the way the TUI does.
20
+ - [x] Submitting while a turn is running queues instead of racing it, the way the TUI does — and a
21
+ slash command typed into it runs while the turn runs, as it does in the terminal.
22
+ `test/web.test.ts`: *a command typed while a turn is running reaches pi as one it can run*.
21
23
  - [x] The queue is visible and can be cleared.
22
24
  - [x] Abort stops the current turn.
23
25
  - [x] Busy, queued count, and "pi exited" are all visible without opening a terminal.
@@ -49,6 +51,14 @@ weaker, and worth converting whenever one of them breaks.
49
51
  - [x] A tap on a composer button lands on that button, even with a soft keyboard open — tapping it
50
52
  blurs the box, which dismisses the keyboard, which moves everything.
51
53
  `test/web-page.test.ts`: *a phone's soft keyboard cannot steal the tap on send*.
54
+ - [x] What is in the composer survives a reload, and belongs to the tab that typed it: the header's
55
+ reload button, the stale-page banner and a phone that discards a backgrounded tab all come
56
+ back to the same text, and a second tab of the same address keeps its own.
57
+ `test/web-page.test.ts`: *a reload keeps what is in the composer, in that tab only*.
58
+ - [x] The composer grows with what is typed into it, up to the ceiling the stylesheet declares, and
59
+ comes back down when the text goes or is sent — a multi-line prompt is written and read at its
60
+ own height rather than through a one-line slot. `test/web-page.test.ts`: *the composer grows
61
+ with a multi-line prompt, and shrinks again*.
52
62
 
53
63
  ## What the feed shows
54
64
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alphacoder-v0/pi-loops",
3
- "version": "0.20.0",
3
+ "version": "0.22.0",
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",
@@ -35,6 +35,7 @@
35
35
  "recipes",
36
36
  "README.md",
37
37
  "README.zh-CN.md",
38
+ "CONTRACT.md",
38
39
  "CHANGELOG.md",
39
40
  "LICENSE"
40
41
  ],
@@ -45,10 +46,12 @@
45
46
  "publishedToNpm": true
46
47
  },
47
48
  "scripts": {
48
- "ci": "npm run typecheck && npm run lint && npm run check:scripts && npm run check:docs && npm test",
49
+ "ci": "npm run typecheck && npm run lint && npm run check:scripts && npm run check:docs && npm test && npm run check:downstream && npm run check:contract",
49
50
  "typecheck": "node scripts/typecheck.mjs",
50
51
  "lint": "node scripts/lint.mjs",
51
52
  "check:docs": "node scripts/check-docs.mjs",
53
+ "check:downstream": "sh test/downstream/run.sh",
54
+ "check:contract": "sh test/contract/run.sh",
52
55
  "test": "node --import ./src/register-pi.mjs --test test/*.test.ts",
53
56
  "check:scripts": "node --check src/web.mjs && node --check src/cli-entry.mjs && node --check src/host-entry.mjs && node --check src/ts-entry.mjs && node --check src/pi-resolver.mjs && node --check src/register-pi.mjs && node --check examples/mcp-notify-server.mjs"
54
57
  },
@@ -29,7 +29,9 @@ checkpoint is a finding in the inbox that names the branch.
29
29
  - **Change state**: edit the `**Status:**` line — exactly one state
30
30
  - **Claim**: set `**Status:** agent-working` and add `**Assignee:** <who>`, and commit that edit on the work branch before any other work
31
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
32
+ - **Review**: push the branch (or leave it local), set `**Status:** in-review`, and file a finding that names the branch and the item — `in-review` here means the branch exists and the finding has been filed; there is no pull request
33
+ - **Request changes** (a person): append a comment under `## Comments` saying what to change and set `**Status:** agent-working`; the implement loop reads comments newer than the timestamp in its notes and addresses them
34
+ - **Merge** (a person): merge the branch with git, set `**Status:** done`, and move the file to `closed/`; the loop's housekeeping removes the worktree on its next run
33
35
 
34
36
  ## State vocabulary
35
37
 
@@ -42,6 +42,20 @@ before anything slow. Your final reply is read by a program.
42
42
  trying first" and "not to try" lists bound the space. Write one sentence: what you will change
43
43
  and why it should move the metric. Append the row now with status `running` — a crash leaves a
44
44
  record, not a mystery.
45
+
46
+ **First, look back.** Count the `retired` rows since the last `kept` or `promoted` one (or the
47
+ baseline). A streak is the ledger telling you the space is wrong, not that the next idea is:
48
+ - **5 in a row**, and you have not said so since the streak began (your notes' `stalled-at`):
49
+ one finding — `autoresearch: 5 retired in a row (<the idea classes tried>) · waits: a
50
+ change to RESEARCH.md's directions, metric or budget · if not: I keep trying, but only in
51
+ classes not yet tried` — and choose this run's hypothesis outside every class in your notes'
52
+ `tried` line.
53
+ - **10 in a row**: stop choosing. One finding — `autoresearch: 10 retired in a row, stopping
54
+ until RESEARCH.md changes · waits: an edit to the contract · if not: nothing runs; the ledger
55
+ is at <path>` — write the notes with the contract's hash, and end the run. A later run whose
56
+ `RESEARCH.md` hash differs from the notes' starts the count again at zero; one whose hash is
57
+ the same files nothing and stops again.
58
+ A `kept` or `promoted` row resets the streak.
45
59
  6. **Branch.** `git worktree add <worktrees>/<NNN> -b research/<NNN>-<slug> <current branch>`
46
60
  (create the worktrees directory first if it is missing).
47
61
  Change only the editable files. Commit in the worktree before measuring.
@@ -53,7 +67,8 @@ before anything slow. Your final reply is read by a program.
53
67
  contract's promotion rule → status `kept` and go to 8; otherwise `retired` with note
54
68
  `dev-only gain` (the classic overfit; still negative evidence) and remove the worktree.
55
69
  8. **Promote.** Under `propose`: one finding —
56
- `promote research/<NNN>-<slug>: dev <x> → <y>, held-out <a> → <b> (<hypothesis>)` — and leave
70
+ `promote research/<NNN>-<slug>: dev <x> → <y>, held-out <a> → <b> (<hypothesis>) · waits: your
71
+ merge · if not: branch and worktree stay; later runs build on it as the best kept` — and leave
57
72
  the worktree and branch for the person. Under `act`: in the project checkout,
58
73
  `git merge --no-ff research/<NNN>-<slug>`, set status `promoted`, remove the worktree, keep the
59
74
  branch, and file the finding as `promoted …`.
@@ -68,15 +83,24 @@ A second sub-agent reviews the findings of this run before they reach the inbox.
68
83
  or `promoted` finding: check out the named branch in its worktree (or the merge in the checkout),
69
84
  run the contract's **held-out** command yourself, and keep the finding only if the number you get
70
85
  satisfies the promotion rule against the ledger's best row; drop it with the number otherwise. A
71
- `no RESEARCH.md` or protected-file finding is kept if the file state is as described.
86
+ `no RESEARCH.md` or protected-file finding is kept if the file state is as described; a `retired
87
+ in a row` finding is kept if the ledger shows the streak it names.
72
88
 
73
89
  ## Notes for the next run
74
90
 
75
91
  `best=<id> <heldout>` · `next=<NNN>` · `in-progress: <id> worktree=<path>` or `none` ·
76
- `tried: <one line per idea class you have exhausted>`. Under 2000 characters; the ledger holds
77
- the rest.
92
+ `tried: <one line per idea class you have exhausted>` · `stalled-at=<row id the 5-streak finding
93
+ was filed at, or none>` · `contract=<sha256 of RESEARCH.md>`. Under 2000 characters; the ledger
94
+ holds the rest.
78
95
 
79
96
  ## Inbox
80
97
 
81
- Only: a promotion (proposed or done), a contract problem, an eval that cannot run. Never a retired
82
- experiment — the ledger is where those live, and a person reads the ledger when they want to.
98
+ Only: a promotion (proposed or done), a contract problem, an eval that cannot run, a streak of
99
+ retired experiments long enough to question the contract. Never a single retired experiment — the
100
+ ledger is where those live, and a person reads the ledger when they want to.
101
+
102
+ ## Never
103
+
104
+ Never write the contract (`RESEARCH.md`) yourself, never edit a protected file. Never push; never
105
+ merge under `propose`; never run two experiments in one run. Never retry an eval that failed for
106
+ a reason that is not your change — that is one finding and a stop.
@@ -2,6 +2,8 @@
2
2
  # a ledger, a checker re-measures on held-out data before anything is proposed. docs/recipes.md.
3
3
  name = "autoresearch"
4
4
  summary = "One experiment per run against a research contract you wrote; a ledger of what was tried; promotion only on held-out evidence."
5
+ tier = "advanced"
6
+ useful_when = ["You have a metric a script can print and a search space wider than your evenings.", "You want every attempt in a ledger, the failed ones included."]
5
7
  needs_tracker = false
6
8
  levels = ["propose", "act"]
7
9
  files = ["RESEARCH.template.md", "example/RESEARCH.md", "example/solution.py", "example/eval.py", "example/eval.sh"]
@@ -1,7 +1,11 @@
1
1
  name = "changelog-draft"
2
2
  summary = "When the default branch is ahead of the last tag, draft the changelog entry and propose a version; a person tags."
3
+ tier = "starter"
4
+ useful_when = ["Releases wait on someone finding an evening to write the entry.", "You want the version and the entry proposed from the commits, and to tag it yourself."]
3
5
  needs_tracker = false
4
6
  levels = ["report", "propose"]
7
+ needs = ["git-remote"]
8
+ needs_propose = ["gh"]
5
9
  budget_hint_usd = 1
6
10
 
7
11
  [[job]]
@@ -43,8 +43,13 @@ number), so the checkpoint is placed here, after everything that can be prepared
43
43
  instead of opening a second one. Never tag, never publish, never push the default branch.
44
44
 
45
45
  5. Report: `release proposal: v<next> — <k> commits since v<last> (<f> feat, <b> fix); draft: <the
46
- entry, condensed to one line per change>` — under `propose`, `PR <url>` first and the draft
47
- after. Keep it under 500 characters; if the draft is longer, the pull request has it whole and
48
- the finding says so.
46
+ entry, condensed to one line per change> · waits: your tag · if not: re-proposed when the head
47
+ moves, not before` — under `propose`, `PR <url>` first and the draft after. Keep it under 500
48
+ characters; if the draft is longer, the pull request has it whole and the finding says so.
49
49
 
50
50
  Notes: `last-reported-head=<sha> last-tag=<tag> open-pr=<url or none>`.
51
+
52
+ ## Never
53
+
54
+ Never tag, never publish, never push the default branch. Never open a second release pull
55
+ request while one of yours is open — update its branch instead.
@@ -1,8 +1,12 @@
1
1
  # ci-sweeper: a red default branch gets a finding, or a fix in a worktree and a pull request. docs/recipes.md.
2
2
  name = "ci-sweeper"
3
3
  summary = "When the default branch's checks go red, says so — or, at propose, reproduces the failure in a worktree, fixes it, and opens the pull request; twice failing to fix the same failure is a stop."
4
+ tier = "advanced"
5
+ useful_when = ["The default branch goes red and every open pull request looks red with it.", "The failures are the boring kind: a flaky assertion, a renamed fixture, a dependency that moved."]
4
6
  needs_tracker = false
5
7
  levels = ["report", "propose"]
8
+ needs = ["gh", "ci-workflows"]
9
+ needs_propose = ["git-remote"]
6
10
  budget_hint_usd = 5
7
11
 
8
12
  [[job]]
@@ -39,7 +39,8 @@ the branch is green, which is most runs: then you write your notes and stop.
39
39
  <default> (<sha>)`.
40
40
 
41
41
  4. **`propose`:** if your notes show this signature was attempted **twice** already without a green
42
- check, stop: finding `CI red on <default>: <signature> — two fixes did not hold; needs a person`
42
+ check, stop: finding `CI red on <default>: <signature> — two fixes did not hold · waits: a person
43
+ · if not: this signature is not retried; a new signature is`
43
44
  and do not try again for this signature. Otherwise:
44
45
  - `git fetch origin` and `git worktree add ../<repo>-ci/<run id> -b ci/<run id>-<slug> origin/<default>`.
45
46
  - Reproduce with the project's check command (the one its `AGENTS.md` or CI workflow names);
@@ -33,10 +33,11 @@ already reported, so a thing you reported yesterday and that has not changed is
33
33
  "still".
34
34
  3. **Commits in the last 24 hours.** `git log --since=24.hours --oneline` on the default branch,
35
35
  fetched first. Summarize as one line: how many, what they were about. Zero is not a line.
36
- 4. **The automation itself.** Read `runs.jsonl` and `jobs.json` in the pi-loops directory (the
37
- `cron_list` tool lists jobs with `last_error` and `consecutive_failures`): a job that failed on
38
- its last two runs is a line, with the error's first sentence. A finding sitting in the inbox
39
- for more than three days is a line.
36
+ 4. **The automation itself.** The `cron_list` tool lists every job with its `last_error` and
37
+ its `consecutive_failures`: a job that failed on its last two runs is a line, with the error's
38
+ first sentence. `pi-loops inbox list --json` lists the findings nobody has handled, each with
39
+ its `created_at`: one that has waited more than three days is a line. Nothing else about the
40
+ automation is read from disk; the files under the pi-loops directory are not an interface.
40
41
  5. **Anything the person asked you to watch**, if this playbook was edited to add it below this
41
42
  list. (Nothing is listed by default.)
42
43
 
@@ -1,6 +1,8 @@
1
1
  # daily-digest: one finding a morning saying what needs a look today, or nothing. docs/recipes.md.
2
2
  name = "daily-digest"
3
3
  summary = "Every morning, one finding that says what in this project needs a look today — open items, a red CI, a loop that keeps failing — or nothing when nothing does."
4
+ tier = "starter"
5
+ useful_when = ["You open the tracker each morning to ask what happened overnight.", "You want the first loop in a project to write nowhere but the inbox."]
4
6
  needs_tracker = true
5
7
  levels = ["report"]
6
8
  budget_hint_usd = 1
@@ -0,0 +1,11 @@
1
+ # deps-sweeper — the dependencies, reported or bumped
2
+
3
+ | file | what it is |
4
+ |---|---|
5
+ | [recipe.toml](recipe.toml) | one job, Mondays at 08:00, 40-minute timeout, `report` or `propose` |
6
+ | [audit.md](audit.md) | the playbook: lockfile → package manager → audit and outdated → findings, or worktree, patch and minor bumps, checks, one pull request |
7
+
8
+ The advisory id and the package at a major version are the memory: each is reported once and
9
+ again only when it changes. Under `propose` a major is still never applied — it is a finding and a
10
+ person's decision — and the packages listed under **Never bump** in the playbook are not touched
11
+ at any level. The lockfile is written by the package manager, never by hand.
@@ -0,0 +1,99 @@
1
+ ---
2
+ name: deps-sweeper
3
+ description: One run of the dependency sweeper. Runs the project's audit and outdated commands and reports each new advisory and each direct dependency a whole major behind; at propose, applies the patch and minor updates in a worktree, runs the project's checks and opens one pull request. Never applies a major; the same package failing twice is a stop.
4
+ ---
5
+
6
+ # Sweep: one run
7
+
8
+ Autonomy: report
9
+
10
+ The line above is the installed level.
11
+ - `report`: an advisory not in your notes, or a direct dependency a whole major behind, is a
12
+ finding; nothing is changed.
13
+ - `propose`: additionally, you apply the patch and minor updates the package manager offers, in a
14
+ worktree, run the project's check command, and open **one** pull request. Merging stays with a
15
+ person. A major is still never applied.
16
+
17
+ You are the dependency sweeper. Nobody is watching. Dependencies rot in two ways a person stops
18
+ noticing: an advisory lands against a version the project pins, and a package moves on a major
19
+ while the project stays behind. The first is worth a line the day it appears; the second is worth
20
+ a line once. Most weeks there is nothing new, and then you write your notes and stop.
21
+
22
+ ## Never bump
23
+
24
+ Packages a person has decided to hold, one per line, with the reason. Edit this list in place; it
25
+ is read on every run, and a package on it is not updated at any level and not reported as behind.
26
+
27
+ - *(none yet)*
28
+
29
+ ## What to do
30
+
31
+ 1. **Find the package manager** from the lockfile at the project root, and take the commands for
32
+ it: `package-lock.json` → `npm audit --json`, `npm outdated --json`, `npm update <pkg>`;
33
+ `pnpm-lock.yaml` → `pnpm audit --json`, `pnpm outdated --format json`, `pnpm update <pkg>`;
34
+ `yarn.lock` → `yarn npm audit --json` (or `yarn audit --json` for classic), `yarn outdated`,
35
+ `yarn upgrade <pkg>` for classic and `yarn up "<pkg>@<the range package.json has>"` for Berry
36
+ (a bare `yarn up <pkg>` jumps to latest, which can be a major); `Cargo.lock` → `cargo audit --json` and `cargo outdated --format json` if they
37
+ are installed, `cargo update -p <pkg>`; `uv.lock` / `poetry.lock` / `requirements*.txt` →
38
+ `pip-audit --format json` if installed, then `pip list --outdated --format json`, and the
39
+ manager's own update command. No lockfile, or the audit tool is not installed: one finding
40
+ saying which, notes unchanged, stop — installing tools is not yours to do. Never run an install
41
+ that writes outside the project or the worktree.
42
+
43
+ 2. **Audit.** Run the audit command and read the advisories. For each one whose id is **not** in
44
+ your notes: one finding, under 200 characters —
45
+
46
+ ```
47
+ advisory GHSA-xxxx-xxxx-xxxx: <package>@<installed> — <severity>, fixed in <version> (<title>)
48
+ ```
49
+
50
+ Then add the id to your notes. An advisory already in your notes is not a new finding; one
51
+ that has gone (the package was updated, the advisory withdrawn) is a line saying so, once, and
52
+ its id leaves your notes.
53
+
54
+ 3. **Outdated.** Run the outdated command and keep the **direct** dependencies whose latest
55
+ version is a whole major above the installed one. For each not in your notes at that major:
56
+ one finding, `<package> <installed> → <latest> (major; changelog: <url if the package names
57
+ one>) · waits: your decision to take the major · if not: not reported again until the next
58
+ major`, and record the package with the major you reported. The same gap is not repeated the
59
+ next week; a gap that moved to a new major is a new finding. Skip anything under **Never bump**.
60
+
61
+ 4. **`propose`:** the patch and minor updates. If your notes say a package was attempted **twice**
62
+ already without a green check, leave it out and file `deps: <package> — two attempts did not
63
+ pass the checks · waits: a person · if not: left out of every later pull request`. Otherwise:
64
+ - `git fetch origin` and `git worktree add ../<repo>-deps/<YYYY-MM-DD> -b deps/<YYYY-MM-DD>
65
+ origin/<default branch>`; do everything below in that worktree.
66
+ - Install there, then apply the updates with the package manager's own update command, patch
67
+ and minor only, skipping **Never bump** and anything in your notes' stop list. Never edit the
68
+ lockfile by hand and never change a version range's shape (a caret stays a caret).
69
+ - Run the project's check command (the one its `AGENTS.md`, `package.json` scripts or CI
70
+ workflow names). Green → commit `chore: update dependencies (<n> packages)` with the list in
71
+ the body, push `-u`, and open the pull request with a body that starts with a line saying it
72
+ was generated by AI, lists each package `old → new`, and names any advisory it closes.
73
+ Finding: `deps PR opened: <n> updates (<the notable ones>) — <url>`.
74
+ Not green → bisect once by reverting the updates to the package the failing check names (or
75
+ half of them, if it names none) and run the checks again; a package whose update alone makes
76
+ them red is recorded as attempted in your notes and left out of this pull request. Still red
77
+ with every update reverted: the branch is red on its own, that is the CI sweeper's problem —
78
+ commit nothing, remove the worktree, finding `deps: checks are red on <default branch>
79
+ without any update; nothing applied`.
80
+ - If a pull request of yours is already open and unmerged, update its branch instead of
81
+ opening a second one; if it was closed without merging, do not reopen it this run — a finding
82
+ says a person closed it, and the packages it carried go on the stop list.
83
+
84
+ 5. **Housekeeping.** A worktree of yours whose pull request is merged or closed is removed and its
85
+ branch deleted. Advisories closed by a merged pull request leave your notes.
86
+
87
+ ## Notes for the next run
88
+
89
+ `watermark=<ISO with offset>` · `advisories: <id> <id> …` · `majors-reported: <package>@<major> …`
90
+ · `attempted: <package> ×<n> …` · `open-pr=<url or none>` · `worktree=<path or none>`. Under 2000
91
+ characters; when the advisory list grows past that, keep the open ones and say `older ones in the
92
+ merged PRs`.
93
+
94
+ ## Never
95
+
96
+ Never apply a major version, never touch a package under **Never bump**, never edit a lockfile by
97
+ hand, never push to the default branch, never force-push, never merge, never touch a branch you did
98
+ not create. Never disable an audit
99
+ rule, an audit level, or a check to get to green — that is a finding, not a fix.
@@ -0,0 +1,17 @@
1
+ # deps-sweeper: new advisories and major-version gaps reported; patch and minor updates applied in a
2
+ # worktree and put up as one pull request. docs/recipes.md.
3
+ name = "deps-sweeper"
4
+ summary = "Once a week, runs the project's audit and outdated commands and reports each new advisory and each dependency a major behind — or, at propose, applies the patch and minor updates in a worktree, runs the checks, and opens one pull request; a major is never applied, and the same package failing twice is a stop."
5
+ tier = "advanced"
6
+ useful_when = ["Advisories arrive by email and the patch bumps pile up.", "You want the safe updates put up as one pull request a week and the majors left to you."]
7
+ needs_tracker = false
8
+ levels = ["report", "propose"]
9
+ needs = ["lockfile"]
10
+ needs_propose = ["gh", "git-remote"]
11
+ budget_hint_usd = 5
12
+
13
+ [[job]]
14
+ name = "deps-sweeper"
15
+ schedule = "0 8 * * 1"
16
+ playbook = "audit.md"
17
+ timeout = "40m"
@@ -1,8 +1,11 @@
1
1
  # ecosystem: who uses this project, drafted — every outward word is a finding a person claims. docs/recipes.md.
2
2
  name = "ecosystem"
3
3
  summary = "Once a week, finds projects that use or fork this one and drafts the one reply or upstream invitation worth sending; every draft is a finding, and a person sends it."
4
+ tier = "advanced"
5
+ useful_when = ["People use or fork the project and you find out months later.", "You want the reply drafted and to send it yourself."]
4
6
  needs_tracker = true
5
7
  levels = ["propose"]
8
+ needs = ["gh"]
6
9
  budget_hint_usd = 2
7
10
 
8
11
  [[job]]
@@ -39,7 +39,7 @@ It names this repository; the searches below take that name.
39
39
  4. **Findings**, one per draft, under 500 characters:
40
40
 
41
41
  ```
42
- ecosystem: <repo> — <what they did/hit>; draft reply for <url>: "<the text, ready to post>"
42
+ ecosystem: <repo> — <what they did/hit>; draft reply for <url>: "<the text, ready to post>" · waits: you, to send it by claiming · if not: never sent, not drafted again
43
43
  ```
44
44
 
45
45
  A person claims the finding to send it (the claim hands the draft to the agent in their chat,