@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/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,97 @@
|
|
|
3
3
|
All notable changes to pi-loops are documented here. The format follows
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow SemVer.
|
|
5
5
|
|
|
6
|
+
## [0.19.2] - 2026-09-14
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
- **A plain job of another session was promised a next run, could be run into the wrong chat, and
|
|
10
|
+
was called dormant as if that were a fault** (#15). A plain job belongs to the session that
|
|
11
|
+
created it — its result is a message in that conversation — and dispatch has always honoured
|
|
12
|
+
that; the lists did not. `cron_list` printed a `next_run` for a job this process would never
|
|
13
|
+
dispatch (the same class the `[other host]` fix closed for the machine half of the predicate),
|
|
14
|
+
the browser panel showed the time it read from `next-runs.json`, and `/cron run` injected the job
|
|
15
|
+
into whatever chat was open. On 2026-09-14 a daily digest missed its slot exactly this way, and
|
|
16
|
+
the only surface that knew was the terminal's `[dormant …]` marker. Now one predicate
|
|
17
|
+
(`src/job-owner.ts`) answers "does it run here" for dispatch, `/cron`, `cron_list`, the panel and
|
|
18
|
+
`/cron run`: another session lists it as `[session <id> — resume it to run]` with no next run,
|
|
19
|
+
`cron_list` says the same and sets `owner_session` / `asleep`, the panel says what would wake it,
|
|
20
|
+
and `/cron run` refuses it with the same sentence. "Asleep" replaces "dormant" in the vocabulary:
|
|
21
|
+
it is waiting for its conversation, not broken. The `catch_up` description now says when a
|
|
22
|
+
plain job's missed slot is honoured (when its session is next opened); the skill tells the model
|
|
23
|
+
that anything meant to run unattended is a loop. Parking stays what it was and is now documented
|
|
24
|
+
as such: a plain job is disabled once its session's file has been deleted, not before.
|
|
25
|
+
|
|
26
|
+
## [0.19.1] - 2026-09-14
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
- **`pi-loops sessions` says what a session is, not only which.** It printed the id, the mtime and
|
|
30
|
+
the cwd, so telling two sessions apart meant opening them. It now prints one line a person can
|
|
31
|
+
tell them apart by: the short id, when the session started, the automation it has
|
|
32
|
+
(`[2 cron, 1 trigger]`, `[automation off]`), and what was first said in it — the name it was
|
|
33
|
+
given, or the first message cut at eighty characters, by characters and never inside one.
|
|
34
|
+
`--all` puts the cwd after the id. `src/session-head.ts` reads the head of a session file for
|
|
35
|
+
it; the browser front end's own copy of that reading is unchanged.
|
|
36
|
+
- **The session commands are two words in the browser front end.** `/sessions` lists this
|
|
37
|
+
project's sessions in the feed, and `/session export [path]` / `/session import <path>` are the
|
|
38
|
+
archive commands, delivered as `/session-export` / `/session-import`. pi's built-in `/session` is
|
|
39
|
+
a terminal command that does not exist over rpc — that is why the extension's commands are
|
|
40
|
+
hyphenated in the terminal, and why the page can spell them plainly.
|
|
41
|
+
|
|
42
|
+
## [0.19.0] - 2026-09-14
|
|
43
|
+
|
|
44
|
+
### Removed
|
|
45
|
+
- **The machine, as a concept.** Every job and rule used to carry the hostname that created it;
|
|
46
|
+
another machine sharing the `$HOME` ignored it and listed it as `[other host: <name>]`,
|
|
47
|
+
`/cron set <ref> --host here|-` and `/triggers set --host` re-homed it, `cron_list` printed
|
|
48
|
+
`other_host:`, the leader and next-runs files were named `scheduler.<host>.json` and
|
|
49
|
+
`next-runs.<host>.json`, and an imported archive was re-stamped for the importing machine. None
|
|
50
|
+
of it matches the way pi-loops is used: pi runs on one machine, the loops run
|
|
51
|
+
there, and a phone or a laptop reaches the browser front end over a tailnet without moving the
|
|
52
|
+
execution anywhere. All of it is gone. A `host` field an older build left in `jobs.json`,
|
|
53
|
+
`triggers.json` or a `.pisession` archive is ignored when read and dropped on the next write; the
|
|
54
|
+
leader file is `scheduler.json`, the next-runs file `next-runs.json`. Two machines syncing one
|
|
55
|
+
`$HOME` now both run every job — not supported, and said so rather than half-handled
|
|
56
|
+
(`docs/design.md` §19). "Host" means one thing here now: the headless process.
|
|
57
|
+
|
|
58
|
+
## [0.18.0] - 2026-09-14
|
|
59
|
+
|
|
60
|
+
### Added
|
|
61
|
+
- **Recipes: a project run on loops, installed in one command.** A recipe is a directory —
|
|
62
|
+
`recipe.toml`, one playbook per job, an optional setup script — and `/recipe add <name>` installs
|
|
63
|
+
it into the open project: one question (the autonomy level, `report` / `propose` / `act`, written
|
|
64
|
+
as the first line of each playbook), one confirmation showing every file, the setup script and
|
|
65
|
+
every `/cron add` line, then the jobs through the same code path `/cron add` uses. The playbooks
|
|
66
|
+
are copied to `.agents/skills/<name>/` — where pi also discovers them as `/skill:` commands — and
|
|
67
|
+
the directory is listed in `.git/info/exclude`, so the repository is untouched; a job's prompt is
|
|
68
|
+
a pointer to its playbook, read fresh on every run, so editing the file changes the next run.
|
|
69
|
+
`/recipe update` is a three-way merge against the untouched copy kept at install (under `.orig/`), and a conflict
|
|
70
|
+
is handed to the agent to resolve with you rather than left as markers in a file a loop would
|
|
71
|
+
follow. `/recipe remove` takes the jobs and leaves the files unless `--purge`. Each `[[job]]` in a
|
|
72
|
+
manifest is validated by building the `/cron add` line it stands for and parsing it with the same
|
|
73
|
+
parser. The one step no template can do — describing this project's tracker — is handed to the
|
|
74
|
+
session with the prompt in `recipes/_tracker-setup.md`, the way `/inbox claim` hands over a
|
|
75
|
+
finding, written to `docs/agents/issue-tracker.md` in the layout Matt Pocock's engineering
|
|
76
|
+
skills use and excluded from the repository like the playbooks — and the wizard resumes on its
|
|
77
|
+
own once that turn ends with the file in place. `pi-loops recipe list|show|add` in a shell does the deterministic part and stops
|
|
78
|
+
before the setup script and the jobs. `docs/recipes.md`; vocabulary in `CONTEXT.md`, decisions in
|
|
79
|
+
`docs/design.md` §15–18.
|
|
80
|
+
- **Two recipes.** `issue-loop`: the tracker as a state machine (Matt Pocock's five triage states
|
|
81
|
+
plus `agent-working`, `in-review`, `agent-blocked`) that two loops turn, a triage loop that
|
|
82
|
+
reproduces, checks `.out-of-scope/` and writes agent briefs, and an implement loop that claims
|
|
83
|
+
one issue per run, builds it in a worktree and opens the pull request; a person promotes and
|
|
84
|
+
merges. `autoresearch`: one experiment per run against a `RESEARCH.md` contract you wrote, a
|
|
85
|
+
`results.tsv` ledger where retired ideas stay as negative evidence, a worktree per experiment,
|
|
86
|
+
and promotion only when the `--verify` checker has re-run the held-out command itself; with a
|
|
87
|
+
pure-Python exact k-NN example whose baseline is a speedup of 1.0. `changelog-draft` is packaged
|
|
88
|
+
as the release-side half of the issue loop.
|
|
89
|
+
|
|
90
|
+
### Fixed
|
|
91
|
+
- **The browser front end reported a slash command as timed out while it waited in a dialog.** A
|
|
92
|
+
`/…` prompt is answered by pi when the command has finished, and a command that shows a setup
|
|
93
|
+
script and asks for a yes finishes when the person answers; sixty seconds later the feed said
|
|
94
|
+
`timed out after 60000ms` and the install went on to succeed. A slash command now gets fifteen
|
|
95
|
+
minutes; everything else keeps sixty seconds.
|
|
96
|
+
|
|
6
97
|
## [0.17.6] - 2026-09-14
|
|
7
98
|
|
|
8
99
|
### Added
|
package/README.md
CHANGED
|
@@ -38,7 +38,7 @@ the first sign of credentials that do not work should not be an empty inbox tomo
|
|
|
38
38
|
|
|
39
39
|
```bash
|
|
40
40
|
pi install npm:@alphacoder-v0/pi-loops # from npm, following new releases
|
|
41
|
-
pi install git:github.com/alphacoder-v0/pi-loops@v0.
|
|
41
|
+
pi install git:github.com/alphacoder-v0/pi-loops@v0.19.2 # or from GitHub, at a pinned tag
|
|
42
42
|
pi install /path/to/pi-loops # or a local checkout — `pi install .` in this repo
|
|
43
43
|
pi -e /path/to/pi-loops # or none of them: try it for one run, installing nothing
|
|
44
44
|
```
|
|
@@ -138,6 +138,11 @@ A job records its directory and model at creation, so it is not tied to the wind
|
|
|
138
138
|
`--cwd` runs it in another checkout (absolute, or relative to this project — no shell, so nothing
|
|
139
139
|
expands `~`), and `--model` pins it whatever this session is on (`/cron set <ref> --model -` unpins).
|
|
140
140
|
|
|
141
|
+
And two you do not write at all: `/recipe add issue-loop` runs the issue tracker as a state machine
|
|
142
|
+
that two loops turn, `/recipe add autoresearch` runs one experiment per run against a contract you
|
|
143
|
+
wrote — each a directory of playbooks copied into the project, installed with one question and one
|
|
144
|
+
confirmation ([docs/recipes.md](docs/recipes.md)).
|
|
145
|
+
|
|
141
146
|
Two that are not loops:
|
|
142
147
|
|
|
143
148
|
```text
|
|
@@ -263,7 +268,7 @@ State stays in `~/.pi/agent/loops` until you delete it.
|
|
|
263
268
|
|
|
264
269
|
| Command | What it does |
|
|
265
270
|
|---|---|
|
|
266
|
-
| `/cron add [--stateful] [--verify] "<schedule>" <prompt>` | Schedule a job: plain
|
|
271
|
+
| `/cron add [--stateful] [--verify] "<schedule>" <prompt>` | Schedule a job: a plain job belongs to this chat and injects its result here (it sleeps while this session is not open), `--stateful` makes a loop that runs wherever the clock is, with memory and inbox routing, `--verify` adds the checker |
|
|
267
272
|
| `/cron`, `/cron all` | This project's jobs, or every project on this machine |
|
|
268
273
|
| `/cron enable\|disable\|remove <ref>` | Pause, resume or delete one job — and write an audit entry into the session, as `add` does |
|
|
269
274
|
| `/cron disable --all` | Pause every job in this project (`--all-projects` for the machine); `/cron enable --all` resumes |
|
|
@@ -284,11 +289,12 @@ State stays in `~/.pi/agent/loops` until you delete it.
|
|
|
284
289
|
| `/new-trigger <natural language>` | Create a condition-based rule ("when ~/build.done exists, run cargo test") |
|
|
285
290
|
| `/triggers [status\|rules\|enable\|disable\|remove\|running\|audit [N]\|abort]` | Dynamic rules: what exists, what is running, what happened |
|
|
286
291
|
| `/triggers run <id>` | Check one rule now instead of waiting for its poll slot |
|
|
287
|
-
| `/triggers set <id> --model\|--thinking\|--timeout
|
|
292
|
+
| `/triggers set <id> --model\|--thinking\|--timeout` | Change what a rule runs with — the settings that decide how an unattended action behaves |
|
|
288
293
|
| `/triggers sources`, `/triggers hooks` | Every source feeding the trigger runtime: each connected MCP server, whose notifications can fire a rule; the local crontab; the dynamic checker |
|
|
289
294
|
| `/triggers panel [on\|off]` | The same panel, toggled from the trigger side |
|
|
290
|
-
| `/session-export [path]`, `/session-import <path>` | Portable `.pisession` archive: transcript + jobs + rules + loop state |
|
|
295
|
+
| `/session-export [path]`, `/session-import <path>` | Portable `.pisession` archive: transcript + jobs + rules + loop state (in the browser front end, `/session export` and `/session import` too; `/sessions` lists) |
|
|
291
296
|
| `/session-share [--public]` | Upload a redacted transcript as a GitHub gist via `gh`, after showing you what it contains. (pi has its own `/share`, which sends the raw session elsewhere first — see [docs/session-archive.md](docs/session-archive.md)) |
|
|
297
|
+
| `/recipe [list\|show\|add\|update\|remove <name>]` | Install a packaged way of running this project on loops — the issue tracker as a state machine, or one research experiment per run — with one question and one confirmation ([docs/recipes.md](docs/recipes.md)) |
|
|
292
298
|
| `/pi-loops [install-launcher]` | Version and paths; `install-launcher` puts the `pi-loops` command on your `PATH` |
|
|
293
299
|
|
|
294
300
|
A schedule is a 5-field cron expression, or one of `hourly` / `daily` / `weekly` (also `每天`), or
|
|
@@ -317,6 +323,7 @@ notifications, and lifecycle hooks — each has a page here.
|
|
|
317
323
|
- [docs/mcp.md](docs/mcp.md) — MCP notification sources and tool registration (`mcp.toml`)
|
|
318
324
|
- [docs/hooks.md](docs/hooks.md) — lifecycle hooks (`hooks.toml`), including `run_start` / `run_end` for scheduled runs
|
|
319
325
|
- [docs/goal.md](docs/goal.md) — `/goal`: holding a session to a stop condition
|
|
326
|
+
- [docs/recipes.md](docs/recipes.md) — `/recipe`: a project run on loops, installed in one command; the issue loop and autoresearch, and how to write your own
|
|
320
327
|
- [docs/session-archive.md](docs/session-archive.md) — `/session-export`, `/session-import`
|
|
321
328
|
- [docs/cli.md](docs/cli.md) — the `pi-loops` command line: export, import, and looking in on the host
|
|
322
329
|
- [docs/web-ui-parity.md](docs/web-ui-parity.md) — what the browser front end owes you, line by line
|
|
@@ -337,7 +344,7 @@ notifications, and lifecycle hooks — each has a page here.
|
|
|
337
344
|
| `~/.pi/agent/loops/logs/pi-<pid>.log` | what each pi process's automation did |
|
|
338
345
|
| `~/.pi/agent/loops/triggers.json`, `triggers-audit.jsonl` | dynamic rules and trigger audit |
|
|
339
346
|
| `~/.pi/agent/loops/{config,mcp,hooks}.toml` | configuration |
|
|
340
|
-
| `~/.pi/agent/loops/scheduler
|
|
347
|
+
| `~/.pi/agent/loops/scheduler.json` | which pi process currently owns the timer |
|
|
341
348
|
|
|
342
349
|
Set `PI_LOOPS_DIR` to relocate all of it. The notes are yours to edit when the agent got something
|
|
343
350
|
wrong, and `logs/pi-<pid>.log` is the file to read after an overnight failure.
|
|
@@ -347,7 +354,7 @@ wrong, and `logs/pi-<pid>.log` is the file to read after an overnight failure.
|
|
|
347
354
|
Scheduled work is only worth trusting if it survives you closing the editor. So "pi was restarted"
|
|
348
355
|
is treated here as the normal case rather than the exception.
|
|
349
356
|
|
|
350
|
-
Jobs are machine-global
|
|
357
|
+
Jobs are machine-global and never expire. Any open pi can own the timer:
|
|
351
358
|
leadership is a file with a heartbeat, and when the process holding it exits or dies, the next tick
|
|
352
359
|
in another window picks it up. A project's trigger checks run in a pi that is open in that project,
|
|
353
360
|
so a result that belongs in a conversation lands in the right one. A tick a loop missed while
|
package/README.zh-CN.md
CHANGED
|
@@ -45,7 +45,7 @@ pi-loops 自己没有任何运行时依赖。
|
|
|
45
45
|
|
|
46
46
|
```bash
|
|
47
47
|
pi install npm:@alphacoder-v0/pi-loops # 从 npm 装,跟着新版本走
|
|
48
|
-
pi install git:github.com/alphacoder-v0/pi-loops@v0.
|
|
48
|
+
pi install git:github.com/alphacoder-v0/pi-loops@v0.19.2 # 或者从 GitHub 装,钉住一个 tag
|
|
49
49
|
pi install /path/to/pi-loops # 或者本地检出;本仓库里就是 pi install .
|
|
50
50
|
```
|
|
51
51
|
|
|
@@ -215,6 +215,19 @@ pi update --extensions # 对齐已安装的包
|
|
|
215
215
|
|
|
216
216
|
任务在创建时就记下自己的目录和模型,所以它不绑在敲出它的那个窗口上。`--cwd` 让它在另一个检出里跑——绝对路径,或相对当前项目;这里没有 shell,`~` 不会展开。`--model` 把模型钉死,`/cron set <ref> --model -` 解钉。
|
|
217
217
|
|
|
218
|
+
### 不用自己写的:recipe
|
|
219
|
+
|
|
220
|
+
```text
|
|
221
|
+
/recipe 有哪些打包好的 recipe,这个项目装了哪些
|
|
222
|
+
/recipe add issue-loop 把 issue tracker 跑成状态机:分诊成 agent brief、在 worktree 里实现、开 PR;推进和合并留给人
|
|
223
|
+
/recipe add autoresearch 按你写的研究合同每次跑一个实验,账本记下每次尝试,只凭 held-out 数据提议晋升
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
一个 recipe 是一组 loop 加上它们各自每次 run 都会读的 playbook。安装只问一个问题(自治级别
|
|
227
|
+
`report` / `propose` / `act`)、确认一次(每个要写的文件、setup 脚本全文、每一行 `/cron add`),
|
|
228
|
+
playbook 拷到 `.agents/skills/<name>/`,通过 `.git/info/exclude` 排除在仓库之外——你随时可以改它,
|
|
229
|
+
下一次 run 就照改后的做。见 [docs/recipes.md](docs/recipes.md)。
|
|
230
|
+
|
|
218
231
|
## 用法
|
|
219
232
|
|
|
220
233
|
会话里的命令分几摊:`/cron` 管任务,`/inbox` 管分诊,`/goal` 管「做到什么算完」,`/triggers` 管不看钟的那些规则,`/session-share` 把这次会话脱敏后发出去。
|
|
@@ -228,7 +241,7 @@ pi update --extensions # 对齐已安装的包
|
|
|
228
241
|
/cron · /cron list|ls|status 本项目的任务,[stateful] 标记;/cron all 看整台机器
|
|
229
242
|
/cron enable|resume|disable|pause|remove <n|id|name>
|
|
230
243
|
/cron set <ref> … 改已有任务而不换 id(笔记因此留着):--prompt、--schedule,
|
|
231
|
-
以及 --model、--thinking、--timeout、--name
|
|
244
|
+
以及 --model、--thinking、--timeout、--name 这几个钉子(`-` 解掉)
|
|
232
245
|
/cron run 1 立刻跑一次(once 跑完即删,every 的间隔从现在重算,cron 的下次不变;停用的普通任务会被拒绝)
|
|
233
246
|
/cron state ci loop 的笔记(状态脊柱)
|
|
234
247
|
/cron runs [ci] 最近运行,最新在前
|
|
@@ -299,7 +312,7 @@ at 2026-09-08 → UTC 零点 ← 只给日期按 UTC,在北京就
|
|
|
299
312
|
|
|
300
313
|
要表示时间就把时间写出来。另外 `in 10m` / `at` 在**创建那一刻**就被算成一个绝对时刻存下来,之后改时区不影响它;`every 30m` 是纯间隔,和时区、夏令时都无关。
|
|
301
314
|
|
|
302
|
-
|
|
315
|
+
**一台机器**:pi-loops 在运行 pi 的那台机器上跑,没有跨机器的概念(0.19.0 去掉了任务上的主机名戳;两台机器同步一个 `$HOME` 会各跑一遍,不支持)。要在别处看它,走 tailnet 开浏览器前端。stateful loop 的笔记是模型自己写的自由文本,里面常有 watermark(「看到这里为止」),而机器的时区会变(设了 `TZ`、带着笔记本出差、容器重建成 UTC),所以 prompt 会要求模型写时间时带上偏移。0.14.1 之前写下的笔记没有这个保障,时区变过又带 watermark 的 loop 值得用 `/cron state <id>` 看一眼。
|
|
303
316
|
|
|
304
317
|
## Maker/checker:先质疑每条 finding,再给你看
|
|
305
318
|
|
|
@@ -473,8 +486,8 @@ Output protocol (mandatory):
|
|
|
473
486
|
|
|
474
487
|
定时任务值不值得信,取决于你关掉编辑器之后会发生什么。所以这里把「pi 重启过」当成常态:
|
|
475
488
|
|
|
476
|
-
1.
|
|
477
|
-
2. **loop 归本机唯一的 leader 跑;动态检查归「开在那个项目里的那个 pi」跑。** `scheduler
|
|
489
|
+
1. **任务是机器全局的**,存在 `~/.pi/agent/loops/jobs.json`,不绑会话、不绑目录(每个任务记住自己的 `cwd`,子代理在那里跑)。任何目录里打开的任何 pi 都能看到并执行 stateful 任务;普通任务因为要注入对话,只在创建它的那个会话里触发(`--resume` 回来就继续),会话没了,leader 会把它停用,`/cron gc` 清掉;子代理创建的普通任务归它所服务的那个父会话。
|
|
490
|
+
2. **loop 归本机唯一的 leader 跑;动态检查归「开在那个项目里的那个 pi」跑。** `scheduler.json` 里放 pid 加心跳,30 秒一 tick;leader 退出或者崩了,其它 pi 在下一 tick 接手。
|
|
478
491
|
|
|
479
492
|
每个进程每 tick 往 `presence/` 登记自己的 pid、会话和 cwd。一个项目的规则检查和推送评估,就交给开在那个项目里的 pi——优先创建规则的那个会话,其次 pid 最小的那个——所以 promote_to_chat 一定落在对的对话里。项目里一个 pi 都没开,才轮到 leader 代跑,结果进 inbox。
|
|
480
493
|
|
|
@@ -503,7 +516,7 @@ Output protocol (mandatory):
|
|
|
503
516
|
| `~/.pi/agent/loops/spend.json` | 轮转掉的那部分花费按天留一份,预算上限不会因为 run log 被截断而失效 |
|
|
504
517
|
| `~/.pi/agent/loops/logs/pi-<pid>.log` | 每个 pi 进程的自动化诊断,超 2 MB 保留后半,只留最近五个进程(进程还活着的那份不算) |
|
|
505
518
|
| `~/.pi/agent/loops/sessions/<id>/*.jsonl` | 子代理完整 transcript,每个 loop 保留最近 20 份 |
|
|
506
|
-
| `~/.pi/agent/loops/scheduler
|
|
519
|
+
| `~/.pi/agent/loops/scheduler.json` | 当前 leader 的 pid / 心跳 |
|
|
507
520
|
| `~/.pi/agent/loops/triggers.json` | 动态 trigger 规则(全局,带 cwd) |
|
|
508
521
|
| `~/.pi/agent/loops/triggers-audit.jsonl` | trigger audit,超 2 MB 保留后半 |
|
|
509
522
|
| `~/.pi/agent/loops/sessions/triggers-<项目>-<哈希>/*.jsonl` | 动态检查子代理的 transcript,每个项目一个目录(目录名带项目路径的哈希,同名的两个项目不会共用),保留最近 40 份 |
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Issue tracker: GitHub
|
|
2
|
+
|
|
3
|
+
Work items for this repository live as GitHub Issues in `alphacoder-v0/pi-loops`; pull requests
|
|
4
|
+
are the review surface. Use the `gh` CLI for every operation; it infers the repository from the
|
|
5
|
+
remote when run in a clone. Loops shell out to `gh`, so the account must be authenticated in the
|
|
6
|
+
environment a loop runs in — `gh auth status` answers that.
|
|
7
|
+
|
|
8
|
+
## Operations
|
|
9
|
+
|
|
10
|
+
- **List**: `gh issue list --state open --limit 200 --json number,title,labels,updatedAt,author` (add `--label <state>` to filter by state)
|
|
11
|
+
- **Read**: `gh issue view <n> --comments --json number,title,body,labels,author,createdAt,comments`
|
|
12
|
+
- **Comment**: `gh issue comment <n> --body "..."` (a heredoc for multi-line bodies)
|
|
13
|
+
- **Change state**: `gh issue edit <n> --add-label "<new state>" --remove-label "<old state>"` — exactly one state label at a time
|
|
14
|
+
- **Claim**: `gh issue edit <n> --add-label agent-working --remove-label ready-for-agent --add-assignee @me`
|
|
15
|
+
- **Close**: `gh issue close <n> --comment "..."`
|
|
16
|
+
- **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>`
|
|
17
|
+
|
|
18
|
+
## State vocabulary
|
|
19
|
+
|
|
20
|
+
One category label (`bug` | `enhancement`) and exactly one state label per open issue. The state
|
|
21
|
+
labels are `needs-triage`, `needs-info`, `ready-for-agent`, `ready-for-human`, `agent-working`,
|
|
22
|
+
`in-review`, `agent-blocked`, `wontfix`. They already exist in this repository; `labels.sh`
|
|
23
|
+
refreshes their colour and description and touches no issue. A playbook that moves an issue names
|
|
24
|
+
the label it moves to.
|
|
25
|
+
|
|
26
|
+
## Blocking
|
|
27
|
+
|
|
28
|
+
A `Blocked by: #<n>, #<m>` line near the top of an issue body (or the tracker's native
|
|
29
|
+
dependency) gates it: the item is unblocked when every listed issue is closed.
|
|
30
|
+
|
|
31
|
+
## Who counts as the agent
|
|
32
|
+
|
|
33
|
+
Comments and assignments made by loops are made as the account `gh` is signed in as — currently
|
|
34
|
+
`alphacoder-v0`. Every comment a loop posts starts with a line saying it was generated by AI.
|
package/docs/cli.md
CHANGED
|
@@ -213,8 +213,12 @@ pi-loops host status | abort <run-id|trace-id> | stop
|
|
|
213
213
|
|
|
214
214
|
## sessions, inspect
|
|
215
215
|
|
|
216
|
-
`sessions` lists
|
|
217
|
-
|
|
216
|
+
`sessions` lists this project's sessions newest first, one line each: the first sixteen
|
|
217
|
+
characters of the id, when the session started, the automation it
|
|
218
|
+
has (`[2 cron, 1 trigger]`, or `[automation off]` when all of it is disabled), and what was first
|
|
219
|
+
said in it — the session's name if it was given one, otherwise the first message, cut at eighty
|
|
220
|
+
characters. `--all` lists every project and puts the cwd after the id. The id is what
|
|
221
|
+
`export --session` accepts; a unique prefix will do. `inspect` prints an archive's schedules, prompts and rules without
|
|
218
222
|
writing anything, which is what you want before restoring on a machine you care about.
|
|
219
223
|
|
|
220
224
|
## export
|
|
@@ -260,3 +264,19 @@ accepted). `host stop` ends the host; the next pi to open would have taken the c
|
|
|
260
264
|
|
|
261
265
|
The channel is a unix socket at `host.sock` in the loops directory, created 0600, and it is
|
|
262
266
|
read-mostly on purpose: a host you could prompt would be a second chat.
|
|
267
|
+
|
|
268
|
+
## recipe
|
|
269
|
+
|
|
270
|
+
```sh
|
|
271
|
+
pi-loops recipe list # the packaged recipes, and which are installed in this directory
|
|
272
|
+
pi-loops recipe show issue-loop # its files, jobs, levels, setup script
|
|
273
|
+
pi-loops recipe add issue-loop [--cwd <dir>] [--level report|propose|act] [--overwrite]
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
`add` is the deterministic half of `/recipe add` ([recipes.md](recipes.md)): it copies the
|
|
277
|
+
playbooks to `.agents/skills/<name>/`, lists that directory in `.git/info/exclude` and writes the
|
|
278
|
+
record — and stops. The setup script and the jobs need a pi open in the project: `/recipe add
|
|
279
|
+
<name>` there finds the copies in place, shows the script and the `/cron add` lines, and creates
|
|
280
|
+
them after you say yes. Without `--level` the lowest level the recipe supports is written; an edited
|
|
281
|
+
copy is kept unless `--overwrite` says otherwise.
|
|
282
|
+
|
package/docs/configuration.md
CHANGED
|
@@ -15,7 +15,7 @@ Everything lives under `~/.pi/agent/loops/` (override: `PI_LOOPS_DIR`).
|
|
|
15
15
|
| `runs.jsonl`, `triggers-audit.jsonl` | run log (halved past 1 MB) and trigger audit (2 MB) |
|
|
16
16
|
| `spend.json` | per-day totals of what rotation dropped from the run log, so a daily budget still counts it |
|
|
17
17
|
| `sessions/<job-id>/`, `sessions/triggers-<project>-<hash>/` | sub-agent transcripts (20 per job, 40 per project's checks; the hash of the project's path keeps two projects named alike apart) |
|
|
18
|
-
| `scheduler
|
|
18
|
+
| `scheduler.json` | timer owner on this machine: pid, heartbeat |
|
|
19
19
|
| `presence/` | one file per live pi process: pid, session, cwd (who acts for which project) |
|
|
20
20
|
| `host.json`, `host.log` | the headless host that keeps the clock while no pi is open: pid, and its log |
|
|
21
21
|
| `polls.json` | last dynamic check per project (shared, so a hand-over never double-checks) |
|
package/docs/design.md
CHANGED
|
@@ -24,19 +24,21 @@ The premise is that **"pi was restarted" is the normal case**. Automation that d
|
|
|
24
24
|
it was configured in is automation you cannot rely on, so jobs and rules live on disk for the whole
|
|
25
25
|
machine. Everything here follows from that, and each one has a price.
|
|
26
26
|
|
|
27
|
-
1. **Machine-global jobs and rules, each carrying a `cwd
|
|
27
|
+
1. **Machine-global jobs and rules, each carrying a `cwd`.** `/cron` and
|
|
28
28
|
`/triggers rules` show the current project by default, and say how many are elsewhere. A plain
|
|
29
|
-
(inject) job belongs to the session that created it
|
|
30
|
-
|
|
29
|
+
(inject) job belongs to the session that created it — its result is a message in that
|
|
30
|
+
conversation — and everything that lists it agrees with dispatch about that: another session
|
|
31
|
+
shows it as `[session <id> — resume it to run]` with no next run, `cron_list` says the same to
|
|
32
|
+
the model, `/cron run` refuses it. Asleep is not broken. It is parked as disabled only once its
|
|
33
|
+
session's file has been deleted (`/cron gc` removes it). A loop
|
|
31
34
|
whose `cwd` disappeared waits half an hour — a mount can be late at boot — and is disabled after
|
|
32
|
-
that.
|
|
33
|
-
with whose they are; leader election is per host.
|
|
35
|
+
that.
|
|
34
36
|
|
|
35
37
|
The cost: a list has to be scoped, and a job you cannot see reads as a job that is gone. Hence
|
|
36
38
|
the counts, the markers, and `/cron all`.
|
|
37
39
|
|
|
38
40
|
2. **Who runs what.** Every process ticks. Loops run in the machine leader
|
|
39
|
-
(`scheduler
|
|
41
|
+
(`scheduler.json`, a pid and a heartbeat). A project's dynamic checks and push
|
|
40
42
|
evaluations run in a pi that is *open in that project* — preferring the session that created the
|
|
41
43
|
rules, lowest pid otherwise (`presence/`) — so a promotion lands in the conversation it belongs
|
|
42
44
|
to. Only a project with no pi open falls to the leader, and its results go to the inbox
|
|
@@ -117,6 +119,61 @@ machine. Everything here follows from that, and each one has a price.
|
|
|
117
119
|
failed — the slot is still owed and the failure streak does not advance. Diagnostics go to
|
|
118
120
|
`logs/pi-<pid>.log` rather than only to a chat notification that a new session erases.
|
|
119
121
|
|
|
122
|
+
15. **Recipes are files, not code.** A recipe (`CONTEXT.md` has the vocabulary) is a directory:
|
|
123
|
+
a `recipe.toml` manifest whose `[[job]]` fields are the arguments of `/cron add` and nothing
|
|
124
|
+
else, one playbook per job, an optional setup script. Anything the wizard does with one is
|
|
125
|
+
something a person could have done by hand with `/cron add` and `cp`. The first catalogue is
|
|
126
|
+
seven: issue-loop, daily-digest, changelog-draft, pr-watch, ci-sweeper, autoresearch, and
|
|
127
|
+
ecosystem — the last capped at `propose`, because every outward word it drafts is a finding a
|
|
128
|
+
person claims before it is said.
|
|
129
|
+
|
|
130
|
+
The cost: a project cannot add a recipe by writing TypeScript, only by writing a directory,
|
|
131
|
+
and the wizard can only ask what a manifest can declare.
|
|
132
|
+
|
|
133
|
+
16. **Playbooks are copied into the project and kept out of its history.** `/recipe add` copies
|
|
134
|
+
them to `<project>/.agents/skills/<recipe>/` — where pi also discovers them as `/skill:`
|
|
135
|
+
commands, so a person can run one step by hand — and lists the directory in
|
|
136
|
+
`.git/info/exclude`, so the repository is not touched. A loop's prompt is a pointer to that
|
|
137
|
+
file; the procedure is read fresh on every run. The tracker description the playbooks read
|
|
138
|
+
(`docs/agents/issue-tracker.md`, in the layout Matt Pocock's engineering skills use, so a
|
|
139
|
+
repository that has those reads the same file) is excluded the same way: it names an account
|
|
140
|
+
and a workflow, which are the project's to keep and not the repository's to publish.
|
|
141
|
+
|
|
142
|
+
The cost: two clones of one project each install the recipe, and a playbook edited on one
|
|
143
|
+
machine is not on the other. Upgrading pi-loops upgrades the packaged recipes, not the copies:
|
|
144
|
+
`/recipe update` is a three-way merge against the untouched copy kept at install (under
|
|
145
|
+
`.orig/` beside the playbooks), silent when the person changed nothing, and conflicts are handed to the
|
|
146
|
+
session as a prompt to resolve with the person rather than left as markers in a file the next
|
|
147
|
+
run will read.
|
|
148
|
+
|
|
149
|
+
17. **The wizard is deterministic; one step is not.** Choosing a recipe, its autonomy level, the
|
|
150
|
+
copy and the `/cron add` lines run with no model and end in a confirmation that shows exactly
|
|
151
|
+
what will be created. The one thing a template cannot do — describe *this* project's tracker —
|
|
152
|
+
is handed to the running session with `sendUserMessage`, the way `/inbox claim` hands over a
|
|
153
|
+
finding: explore the remote, propose, confirm, write the file — and when that turn settles with
|
|
154
|
+
the file there, the wizard resumes by itself, so the command is typed once. `pi-loops recipe`
|
|
155
|
+
in the terminal does the deterministic part only.
|
|
156
|
+
|
|
157
|
+
18. **One autonomy dial, three positions, in prose.** `report` reads and files findings; `propose`
|
|
158
|
+
may also write to the tracker and open draft pull requests but never reach a terminal state;
|
|
159
|
+
`act` may. A manifest declares which positions a recipe supports, the wizard defaults to the
|
|
160
|
+
lowest, and the chosen one is a line at the top of each playbook — text the model reads and a
|
|
161
|
+
person edits, enforced by nothing in code. The cost is exactly that: the level is an instruction,
|
|
162
|
+
not a permission, and the permissions that exist (the danger policy, the daily budget, branch
|
|
163
|
+
protection on the remote) are the ones that hold.
|
|
164
|
+
|
|
165
|
+
19. **One machine.** Until 0.19.0 every job and rule carried the hostname that created it, another
|
|
166
|
+
machine sharing the `$HOME` ignored it and listed it as `[other host]`, `/cron set --host`
|
|
167
|
+
re-homed it, and the leader file was named per host. None of it matched the way pi-loops is
|
|
168
|
+
used: pi runs on one machine, the loops run there, and a phone or a
|
|
169
|
+
laptop reaches the browser front end over a tailnet (`tailscale serve`) without moving the
|
|
170
|
+
execution anywhere. So the concept is gone: no `host` on a job or a rule (a stamp an older
|
|
171
|
+
build left is dropped when the file is read), no `--host`, no marker, `scheduler.json` and
|
|
172
|
+
`next-runs.json` plain. "Host" now means one thing here, the headless process.
|
|
173
|
+
|
|
174
|
+
The cost: two machines syncing one `$HOME` both run every job. That is not supported, and it
|
|
175
|
+
is said rather than half-handled.
|
|
176
|
+
|
|
120
177
|
## Where the browser front end came from
|
|
121
178
|
|
|
122
179
|
pi owns the `pi` command and its terminal, so a second front end cannot replace the first one. It
|
package/docs/loops.md
CHANGED
|
@@ -10,8 +10,14 @@ Everything here is `/cron`; `/crontab` and `/loop` are the same command under ot
|
|
|
10
10
|
|
|
11
11
|
When due, the prompt is injected into the session that created the job as a user message with the
|
|
12
12
|
engine prefix `[Trigger <run-id>] `, and the agent runs one turn (an *inject-and-run* job). If the agent
|
|
13
|
-
is busy the message is queued as a follow-up.
|
|
14
|
-
|
|
13
|
+
is busy the message is queued as a follow-up. A plain job belongs to the session that created it:
|
|
14
|
+
its result is a message in that conversation, so it fires only in the process holding that session.
|
|
15
|
+
Open another session and `/cron` lists it as `[session <id> — resume it to run]` with no next run,
|
|
16
|
+
`cron_list` says the same to the model, and `/cron run` refuses it; `--resume` the session it
|
|
17
|
+
belongs to and it wakes, catching up a missed slot if it was created with `--catchup`. It is not
|
|
18
|
+
broken while it sleeps — it is waiting for its conversation. Something that should run whether or
|
|
19
|
+
not a window is open (a nightly digest, a watch) is a loop: `--stateful`, with the inbox as its
|
|
20
|
+
outlet.
|
|
15
21
|
|
|
16
22
|
Schedules are local time: 5-field cron (`*/n`, ranges, lists, `mon-fri`, `jan`), the aliases
|
|
17
23
|
(`hourly` / `every hour` / `once an hour` → `0 * * * *`; `daily` / `every day` → `0 9 * * *`;
|
|
@@ -106,8 +112,8 @@ and answer with one `<verdict n="i">keep|drop — reason</verdict>` per finding
|
|
|
106
112
|
|
|
107
113
|
## Restart behavior
|
|
108
114
|
|
|
109
|
-
Jobs live in `~/.pi/agent/loops/jobs.json` and survive pi restarts. Exactly one pi process
|
|
110
|
-
|
|
115
|
+
Jobs live in `~/.pi/agent/loops/jobs.json` and survive pi restarts. Exactly one pi process on the
|
|
116
|
+
machine owns the timer (`scheduler.json`, 30-second ticks, 90-second heartbeat); when it exits
|
|
111
117
|
or dies another open pi takes over on its next tick. A tick that was missed while no pi was running
|
|
112
118
|
is fired once at startup (collapsed, not replayed) for stateful loops unless `[cron] catch_up = false`;
|
|
113
119
|
plain inject jobs do not catch up unless created with `--catchup` (`--no-catchup` turns it off for
|
|
@@ -116,12 +122,15 @@ loops). A run that died with its process is retried on the next tick; at most
|
|
|
116
122
|
a server pushing many distinct events cannot open one sub-agent per event.
|
|
117
123
|
Jobs run with the model and thinking level recorded
|
|
118
124
|
on them (`/cron set <id> --model … --thinking … --timeout …`, `-` to follow the running session).
|
|
119
|
-
`/cron scheduler` shows who owns the timer; `/cron` marks
|
|
120
|
-
is not open here, parks
|
|
121
|
-
|
|
122
|
-
mount can be late at boot. A job created by a sub-agent belongs to the session that ran it.
|
|
123
|
-
|
|
124
|
-
|
|
125
|
+
`/cron scheduler` shows who owns the timer; `/cron` marks a plain job as `[session <id> — resume it
|
|
126
|
+
to run]` when its session is not open here, parks it as disabled once that session's file has been
|
|
127
|
+
deleted (`/cron gc` removes it), and marks a loop `[orphan: cwd missing]` when their checkout is gone — disabled half an hour later, since a
|
|
128
|
+
mount can be late at boot. A job created by a sub-agent belongs to the session that ran it.
|
|
129
|
+
|
|
130
|
+
pi-loops runs on one machine. Two machines syncing one `$HOME` would both run every job; that is
|
|
131
|
+
not supported (until 0.19.0 a hostname stamp on each job half-handled it, and is now ignored). To
|
|
132
|
+
reach a running pi from elsewhere, use the browser front end over your tailnet ([cli.md](cli.md));
|
|
133
|
+
the loops keep running where pi runs.
|
|
125
134
|
|
|
126
135
|
## Time, and which clock it is
|
|
127
136
|
|
|
@@ -147,14 +156,13 @@ Two things are deliberately still UTC, because neither is a time anybody reads:
|
|
|
147
156
|
session file, which cannot hold the `+` and `:` an offset brings, and pi's own session header, whose
|
|
148
157
|
format is pi's to decide.
|
|
149
158
|
|
|
150
|
-
The offset in the run line above is the offset of
|
|
151
|
-
the prompt.
|
|
152
|
-
|
|
153
|
-
between machines in different timezones. Its notes are free text the model writes — watermarks,
|
|
159
|
+
The offset in the run line above is the offset of the machine **at the time it ran**, not a fixed
|
|
160
|
+
part of the prompt. A machine's clock is not a constant: `TZ` gets set, a laptop travels, a
|
|
161
|
+
container is rebuilt in UTC. A loop's notes are free text the model writes — watermarks,
|
|
154
162
|
"everything up to here has been seen" — and a watermark without an offset is a watermark the next
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
`/cron state <id>`.
|
|
163
|
+
run cannot read once the clock has moved. The prompt asks for the offset for that reason. Notes
|
|
164
|
+
written before 0.14.1 do not have it; a loop that keeps a watermark on a machine whose timezone has
|
|
165
|
+
changed is worth one look at `/cron state <id>`.
|
|
158
166
|
|
|
159
167
|
### Daylight saving
|
|
160
168
|
|