@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.
- package/CHANGELOG.md +91 -0
- package/README.md +13 -6
- package/README.zh-CN.md +19 -6
- package/docs/agents/issue-tracker.md +34 -0
- package/docs/cli.md +22 -2
- package/docs/configuration.md +1 -1
- package/docs/design.md +63 -6
- package/docs/loops.md +25 -17
- package/docs/recipes.md +239 -0
- package/docs/triggers.md +1 -1
- package/docs/troubleshooting.md +3 -2
- package/docs/web-ui-parity.md +9 -3
- package/package.json +2 -1
- package/recipes/_tracker/issue-tracker-github.md +31 -0
- package/recipes/_tracker/issue-tracker-local.md +38 -0
- package/recipes/_tracker-setup.md +12 -0
- package/recipes/autoresearch/README.md +27 -0
- package/recipes/autoresearch/RESEARCH.template.md +37 -0
- package/recipes/autoresearch/example/RESEARCH.md +31 -0
- package/recipes/autoresearch/example/eval.py +60 -0
- package/recipes/autoresearch/example/eval.sh +3 -0
- package/recipes/autoresearch/example/solution.py +20 -0
- package/recipes/autoresearch/experiment.md +82 -0
- package/recipes/autoresearch/recipe.toml +15 -0
- package/recipes/changelog-draft/recipe.toml +9 -0
- package/recipes/changelog-draft/release.md +35 -0
- package/recipes/issue-loop/README.md +16 -0
- package/recipes/issue-loop/implement.md +135 -0
- package/recipes/issue-loop/labels.sh +21 -0
- package/recipes/issue-loop/recipe.toml +18 -0
- package/recipes/issue-loop/triage.md +147 -0
- package/skills/pi-loops/SKILL.md +3 -1
- package/src/archive.ts +5 -5
- package/src/args.ts +1 -8
- package/src/cli.ts +83 -6
- package/src/job-edit.ts +0 -3
- package/src/job-health.ts +6 -7
- package/src/job-owner.ts +29 -0
- package/src/pi-loops.ts +339 -25
- package/src/protocol.ts +2 -3
- package/src/recipe.ts +651 -0
- package/src/scheduler.ts +13 -13
- package/src/session-head.ts +98 -0
- package/src/store.ts +9 -17
- package/src/tools.ts +17 -19
- package/src/trigger-runtime.ts +3 -5
- package/src/triggers.ts +3 -4
- package/src/web.mjs +40 -17
package/docs/recipes.md
ADDED
|
@@ -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
|
|
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]
|
package/docs/troubleshooting.md
CHANGED
|
@@ -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.
|
|
5
|
-
job 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
|
package/docs/web-ui-parity.md
CHANGED
|
@@ -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.
|
|
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.
|
|
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,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
|