@alphacoder-v0/pi-loops 0.22.8 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +664 -0
- package/CONTRACT.md +7 -7
- package/README.md +91 -63
- package/README.zh-CN.md +59 -39
- package/docs/cli.md +272 -83
- package/docs/configuration.md +86 -24
- package/docs/design.md +395 -56
- package/docs/downstream.md +2 -2
- package/docs/goal.md +12 -3
- package/docs/hooks.md +6 -7
- package/docs/loops.md +288 -59
- package/docs/mcp.md +22 -14
- package/docs/recipes.md +6 -0
- package/docs/screenshot.png +0 -0
- package/docs/triggers.md +120 -26
- package/docs/troubleshooting.md +37 -26
- package/docs/web-ui-parity.md +691 -65
- package/package.json +4 -3
- package/recipes/autoresearch/recipe.toml +1 -0
- package/recipes/changelog-draft/recipe.toml +1 -0
- package/recipes/ci-sweeper/recipe.toml +1 -0
- package/recipes/daily-digest/recipe.toml +1 -0
- package/recipes/deps-sweeper/recipe.toml +1 -0
- package/recipes/ecosystem/recipe.toml +1 -0
- package/recipes/issue-loop/recipe.toml +1 -0
- package/recipes/pr-watch/recipe.toml +1 -0
- package/skills/pi-loops/SKILL.md +6 -4
- package/src/activity.ts +199 -0
- package/src/archive.ts +21 -9
- package/src/args.ts +6 -2
- package/src/cli-entry.mjs +12 -3
- package/src/cli.ts +746 -125
- package/src/config.ts +53 -5
- package/src/conversation-rows.ts +469 -0
- package/src/danger.ts +227 -6
- package/src/engine.ts +1240 -0
- package/src/goal.ts +71 -0
- package/src/held-read.ts +27 -0
- package/src/hooks.ts +49 -18
- package/src/host-control-channel.ts +455 -40
- package/src/host-control.ts +602 -111
- package/src/host-entry.mjs +5 -3
- package/src/host-runtime.ts +455 -42
- package/src/host.ts +659 -123
- package/src/inbox-card.ts +278 -0
- package/src/inbox.ts +139 -62
- package/src/job-edit.ts +7 -1
- package/src/job-health.ts +14 -2
- package/src/job-lines.ts +201 -13
- package/src/job-owner.ts +74 -13
- package/src/job-tune.ts +110 -0
- package/src/lease.ts +537 -0
- package/src/lock.ts +7 -2
- package/src/mcp-pool.ts +66 -14
- package/src/mcp.ts +1 -1
- package/src/open-file.ts +54 -0
- package/src/page-children.ts +1507 -0
- package/src/page-devices.ts +192 -0
- package/src/page-server.ts +3150 -0
- package/src/path-precheck.ts +215 -0
- package/src/paths.ts +24 -0
- package/src/pi-floor.ts +10 -0
- package/src/pi-loops.ts +1301 -450
- package/src/pi-resolver.d.mts +1 -0
- package/src/pi-resolver.mjs +110 -10
- package/src/presence.ts +12 -63
- package/src/protocol.ts +10 -1
- package/src/recipe.ts +43 -1
- package/src/requests.ts +354 -0
- package/src/rule-lines.ts +68 -0
- package/src/run-card.ts +105 -0
- package/src/runner.ts +18 -1
- package/src/schedule.ts +1 -2
- package/src/scheduler.ts +595 -245
- package/src/sdk-runner.ts +321 -27
- package/src/seen.ts +278 -0
- package/src/session-head.ts +21 -0
- package/src/share.ts +12 -2
- package/src/snapshot.ts +3 -2
- package/src/store.ts +81 -8
- package/src/tools.ts +196 -65
- package/src/trigger-runtime.ts +164 -127
- package/src/triggers.ts +39 -7
- package/src/trust.ts +46 -2
- package/src/ui-prefs.ts +16 -5
- package/src/watch-plan.ts +174 -0
- package/src/web.d.mts +3 -0
- package/src/web.mjs +2260 -2340
- package/src/window-executor.ts +459 -0
- package/src/window-guard.ts +158 -0
- package/docs/agents/issue-tracker.md +0 -34
package/CONTRACT.md
CHANGED
|
@@ -37,7 +37,7 @@ talks to it only through a recipe directory, `hooks.toml`, and the `pi-loops inb
|
|
|
37
37
|
|
|
38
38
|
### 2.2 Unit formats
|
|
39
39
|
|
|
40
|
-
- **Recipe**: `recipe.toml` is TOML. Required: `name` (a lowercase letter, then lowercase letters, digits and hyphens), `summary`, `levels` (non-empty, from the three levels), at least one `[[job]]`. Each `[[job]]` requires `name`, `schedule` (a recurring schedule) and `playbook` (a file in the same directory). Optional keys: `needs_tracker`, `setup`, `files`, `tier`, `useful_when`, `needs`, `needs_propose`, `budget_hint_usd`; per job `verify`, `timeout`, `thinking`, `tools`, `prompt`.
|
|
40
|
+
- **Recipe**: `recipe.toml` is TOML. Required: `name` (a lowercase letter, then lowercase letters, digits and hyphens), `summary`, `levels` (non-empty, from the three levels), at least one `[[job]]`. Each `[[job]]` requires `name`, `schedule` (a recurring schedule) and `playbook` (a file in the same directory). Optional keys: `line`, `needs_tracker`, `setup`, `files`, `tier`, `useful_when`, `needs`, `needs_propose`, `budget_hint_usd`; per job `verify`, `timeout`, `thinking`, `tools`, `prompt`.
|
|
41
41
|
- **Playbook**: Markdown with pi's skill frontmatter (`name`, `description`), one level line, and one `## Never` section.
|
|
42
42
|
- **Finding**: one JSON object with the fields `id`, `created_at` (RFC 3339), `status`, `kind` (`checkpoint` | `news`), `source`, `run_id`, `cwd`, `text`, `verified` (`true` | `false` | `null`), `dismiss_reason` (string | `null`). Fields are only added at the end; none is renamed, removed or changed in type.
|
|
43
43
|
- **Event**: `run_start` and `run_end`. The payload has `run_job` and `run_id`; on `run_end` also `run_ok`, `run_findings`, `run_error`, `run_cost_usd`. Each field also enters the command's environment as `PI_<FIELD IN UPPER CASE>`, unset when the payload has `null`.
|
|
@@ -59,7 +59,7 @@ A finding is written only by a run, and its `text` is what the run said. `status
|
|
|
59
59
|
|
|
60
60
|
### 2.6 Deterministic rules
|
|
61
61
|
|
|
62
|
-
- `kind`: `text` contains `· waits:` (preceded by the start of the text or whitespace, case-insensitive) → `checkpoint
|
|
62
|
+
- `kind`: a finding filed from a rule result is always `news`; otherwise `text` contains `· waits:` (preceded by the start of the text or whitespace, case-insensitive) → `checkpoint`, else `news`.
|
|
63
63
|
- Level line: the first line that is exactly `Autonomy: <one word>`. When that word is one of the three levels, it is the level; otherwise the playbook has no level.
|
|
64
64
|
- Manifest validity: the required keys and values of 2.2. TOML is parsed as standard TOML.
|
|
65
65
|
|
|
@@ -90,9 +90,9 @@ Three sentences each: what it is, what it depends on, and why the contract does
|
|
|
90
90
|
- **An install leaves an `.orig/` copy and `.recipe.json` in the project and lists the directory in `.git/info/exclude`.** Depends on the three-way merge of `/recipe update` and on keeping playbooks out of the repository's history. Another merge strategy leaves 2.1 and 2.3 as they are.
|
|
91
91
|
- **The values of `needs` and `needs_propose` are the preflight checks this version performs (`gh`, `git-remote`, `ci-workflows`, `lockfile`, `tracker-github`).** Depends on this version's set of checks. A recipe that names a check this version does not have is refused; that binds the recipe to a version and does not change the contract.
|
|
92
92
|
- **A recipe name is at most 64 characters.** Depends on the install directory name staying readable.
|
|
93
|
-
- **A rule's command times out after 5000 ms, its stdout is cut at 4000 characters, summaries at 2000, and
|
|
93
|
+
- **A rule's command times out after 5000 ms, its stdout is cut at 4000 characters, summaries at 2000, and pi-loops drains for 3 seconds before it exits; output goes to `logs/pi-<pid>.log` or `host.log`.** Depends on log rotation. Changing any of these numbers leaves the event fields of 2.2 as they are.
|
|
94
94
|
- **Creating a job needs a pi (interactive, or `pi --mode rpc`).** `pi-loops recipe add` in a terminal only copies files and prints the `/cron add` lines. Depends on the decision that a job belongs to a session. The "create the jobs" of 2.3 is done by `/recipe add` inside pi.
|
|
95
|
-
- **
|
|
95
|
+
- **An always-on pi-loops runs every loop and rule check, whether or not a pi is open.** Depends on pi-loops starting on demand, and on the session lease. Events and findings have the same shape in both processes.
|
|
96
96
|
|
|
97
97
|
## 4. Not done
|
|
98
98
|
|
|
@@ -108,9 +108,9 @@ Three sentences each: what it is, what it depends on, and why the contract does
|
|
|
108
108
|
The scripts are in `test/contract/`. `sh test/contract/run.sh` runs all five; each also runs alone.
|
|
109
109
|
|
|
110
110
|
1. **Second implementation** `test/contract/1-second-implementation.sh`: `test/downstream/closed-loop.sh` (sh and jq install a recipe, let it run, find its finding by `run_id` and claim it, touching no private file), then python3 `tomllib` reads every packaged `recipe.toml` and checks the required keys of 2.2; then every packaged recipe is installed with `pi-loops recipe add` and its files are searched for the names of the loops directory's files. Pass: every step exits 0 and no playbook names a private file. A failure means private state has leaked into the contract, or a recipe depends on it.
|
|
111
|
-
2. **Stopped machine** `test/contract/2-stopped.sh`: with no pi and
|
|
112
|
-
3. **Cross-check** `test/contract/3-cross-check.sh`: four rules, each run through two implementations that share nothing, over the same samples; pass when the outputs are equal line for line. TOML: `src/toml.ts` against python3 `tomllib`, over every `recipe.toml` in the repository and `examples/mcp.toml`. `kind`: `src/protocol.ts` against `grep -E`, over every `<inbox>` text the repository has shown. Level line: `src/recipe.ts` against `awk`, over every playbook in `recipes/` and in this repository's `.agents/skills/`. Finding: `pi-loops inbox list --all --json` against `jq` reading the ten fields of 2.2 off each line of `inbox.jsonl` directly. Event: the same loop run once by
|
|
113
|
-
4. **Private state cleared** `test/contract/4-private-state.sh`: after one run, delete everything in the loops directory that 2.1 does not name as instance data and start
|
|
111
|
+
2. **Stopped machine** `test/contract/2-stopped.sh`: with no pi and pi-loops not running, write a `hooks.toml` rule, install a directory with `pi-loops recipe add`, and dismiss a finding with `pi-loops inbox dismiss`. Pass: all three succeed. A failure means a write depends on a mechanism.
|
|
112
|
+
3. **Cross-check** `test/contract/3-cross-check.sh`: four rules, each run through two implementations that share nothing, over the same samples; pass when the outputs are equal line for line. TOML: `src/toml.ts` against python3 `tomllib`, over every `recipe.toml` in the repository and `examples/mcp.toml`. `kind`: `src/protocol.ts` against `grep -E`, over every `<inbox>` text the repository has shown. Level line: `src/recipe.ts` against `awk`, over every playbook in `recipes/` and in this repository's `.agents/skills/`. Finding: `pi-loops inbox list --all --json` against `jq` reading the ten fields of 2.2 off each line of `inbox.jsonl` directly. Event: the same loop run once by pi-loops started directly and once by pi-loops started on demand by a pi driven over rpc; the two `run_end` payloads have the same fields and the same run values.
|
|
113
|
+
4. **Private state cleared** `test/contract/4-private-state.sh`: after one run, delete everything in the loops directory that 2.1 does not name as instance data and start pi-loops again. Pass: it runs the loop again and the earlier findings are unchanged field for field.
|
|
114
114
|
5. **Contract size** `test/contract/5-size.sh`: section 2 has no more non-empty lines than the count frozen in this file's header. Pass when it does not. A line added there must name, in the CHANGELOG, the mechanism it replaced.
|
|
115
115
|
|
|
116
116
|
## 6. Mapping of requirements and features
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ English · [中文](README.zh-CN.md)
|
|
|
5
5
|
An automation layer for [pi](https://github.com/earendil-works/pi), shipped as a plain pi extension.
|
|
6
6
|
Nothing in pi is patched.
|
|
7
7
|
|
|
8
|
-

|
|
9
9
|
|
|
10
10
|
A loop wakes up with the notes its last run left, does the work in a sub-agent that starts clean,
|
|
11
11
|
and files what it found. You read the findings when you want to, and claim the ones worth a turn.
|
|
@@ -31,8 +31,9 @@ reaches you is what changed rather than everything that is there.
|
|
|
31
31
|
|
|
32
32
|
Node ≥ 22.6 (pi loads the TypeScript sources directly) and pi ≥ 0.84.3 to load it into — 0.85 is
|
|
33
33
|
what this is tested against, and an older pi is refused at load with a line that names the version
|
|
34
|
-
it needs, rather than a link error naming one missing export.
|
|
35
|
-
|
|
34
|
+
it needs, rather than a link error naming one missing export. The process that runs your loops always
|
|
35
|
+
runs on node, also when pi runs under bun: it then uses the `node` on your PATH. Nothing else:
|
|
36
|
+
pi-loops has no runtime dependencies. What you do want first is a provider you can actually talk to — run `pi`, send
|
|
36
37
|
one message, make sure you get an answer. pi-loops runs sub-agents while you are not watching, and
|
|
37
38
|
the first sign of credentials that do not work should not be an empty inbox tomorrow morning.
|
|
38
39
|
|
|
@@ -60,36 +61,28 @@ when to reach for `cron_create`, `new_trigger` and the inbox on its own.
|
|
|
60
61
|
|
|
61
62
|
Every morning a fresh sub-agent runs with the notes it wrote last time, does the work, and hands
|
|
62
63
|
back two things: the notes for tomorrow, and any finding worth your attention. The notes go to a
|
|
63
|
-
Markdown file
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
Markdown file and the findings go to the inbox. (The run says which is which in tags that a program
|
|
65
|
+
reads rather than you — [docs/loops.md](docs/loops.md) has them.) Your conversation gets one line
|
|
66
|
+
for each of the loop's first three runs, so you see that it works, and one for any run that fails;
|
|
67
|
+
every other run is a line in the panel above the editor and in the page's loop activity strip. Here
|
|
68
|
+
are two runs of that loop, half an hour apart, as the panel lists them:
|
|
66
69
|
|
|
67
70
|
```text
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
+
Runs
|
|
72
|
+
09:30 todo · 4s · 1 finding
|
|
73
|
+
09:00 todo · 5s · 0 findings
|
|
71
74
|
```
|
|
72
75
|
|
|
73
|
-
The second
|
|
74
|
-
|
|
75
|
-
```text
|
|
76
|
-
cron todo · 4s · $0.000 · 1 finding · state updated
|
|
77
|
-
md5 changed (06ff2ec8… → fe00aaad…). One new unchecked item appeared.
|
|
78
|
-
• TODO.md: new unchecked item — cache the /search results for 60s (commit 395a124 "todo: cache search results")
|
|
79
|
-
/cron trace todo · /inbox
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
The first run found nothing worth saying and said nothing; the second noticed one change and
|
|
83
|
-
reported only that. That is what the notes between runs buy — without them every run reports the
|
|
76
|
+
The first run found nothing worth saying and filed nothing; the second noticed one change, a new
|
|
77
|
+
unchecked item in TODO.md, and filed only that. That is what the notes between runs buy — without them every run reports the
|
|
84
78
|
whole file every morning, and you stop reading it by Thursday. What they filed waits in the inbox:
|
|
85
79
|
|
|
86
80
|
```text
|
|
87
81
|
/inbox
|
|
88
82
|
Inbox (/tmp/acme-api, 3 new, times +00:00):
|
|
89
|
-
1. [inb-aae794d5] TODO: rate-limit the /search endpoint (unchecked, no owner) (acme-api, cron
|
|
90
|
-
2. [inb-3216a92d] TODO: retry the payment webhook on 5xx (unchecked, no owner) (acme-api, cron
|
|
91
|
-
3. [inb-74b73c72] TODO.md: new unchecked item — cache the /search results for 60s (commit 395a124 "todo: cache search results") (acme-api,
|
|
92
|
-
claim with /inbox claim <n>, dismiss with /inbox dismiss <n>
|
|
83
|
+
1. [inb-aae794d5] TODO: rate-limit the /search endpoint (unchecked, no owner) (acme-api, cron-b91def87, 2026-09-12 05:25)
|
|
84
|
+
2. [inb-3216a92d] TODO: retry the payment webhook on 5xx (unchecked, no owner) (acme-api, cron-b91def87, 2026-09-12 05:25)
|
|
85
|
+
3. [inb-74b73c72] TODO.md: new unchecked item — cache the /search results for 60s (commit 395a124 "todo: cache search results") (acme-api, todo, 2026-09-12 05:28)
|
|
93
86
|
```
|
|
94
87
|
|
|
95
88
|
The third finding is the run above; the two over it came from another loop in the same checkout,
|
|
@@ -175,9 +168,12 @@ Set a cap in `~/.pi/agent/loops/config.toml` before you rely on it:
|
|
|
175
168
|
daily_budget_usd = 5.0
|
|
176
169
|
```
|
|
177
170
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
171
|
+
A pi window starts pi-loops in the background when there is work to run. It runs every loop, rule
|
|
172
|
+
check and push, whether or not a window is open. It also runs the plain jobs of closed
|
|
173
|
+
conversations, in those conversations; each such run is a model turn. A window runs the plain jobs
|
|
174
|
+
of the conversation it holds. After a reboot, work resumes when a pi window opens, or at login if
|
|
175
|
+
you run `pi-loops start --foreground` from your own systemd unit ([docs/cli.md](docs/cli.md)).
|
|
176
|
+
`/pi-loops status` shows its status; `[host] auto = false` disables automatic starts.
|
|
181
177
|
|
|
182
178
|
## The browser window, and the command line
|
|
183
179
|
|
|
@@ -208,17 +204,17 @@ pi-loops --tui # the terminal one, when you want it
|
|
|
208
204
|
pi-loops --continue # pick up the newest session in this directory
|
|
209
205
|
```
|
|
210
206
|
|
|
211
|
-
`pi-loops sessions|inspect|export|import` and `pi-loops
|
|
212
|
-
open at all. They are for backups from cron or CI, restoring on a fresh machine, and
|
|
213
|
-
the
|
|
207
|
+
`pi-loops sessions|inspect|export|import` and `pi-loops start|stop|restart|status|abort` need no
|
|
208
|
+
pi session open at all. They are for backups from cron or CI, restoring on a fresh machine, and
|
|
209
|
+
looking in on pi-loops, which the page also shows as its Background view (`pi-loops status --url`). See [docs/cli.md](docs/cli.md).
|
|
214
210
|
|
|
215
|
-
At a local terminal the bare command opens the browser
|
|
211
|
+
At a local terminal the bare command opens the browser page; over ssh, or with no terminal at
|
|
216
212
|
all, it runs pi itself, because a browser on the far machine helps nobody. Pass `--web` or `--tui`
|
|
217
213
|
when that guess is wrong. Anything else you pass goes straight to pi (`pi-loops --model
|
|
218
214
|
anthropic/claude-opus-5 -e .`).
|
|
219
215
|
|
|
220
|
-
Both windows are complete pi sessions — the
|
|
221
|
-
session file, `--resume`, your models, tools and extensions are the same either way, and the model
|
|
216
|
+
Both windows are complete pi sessions — each conversation in the page is a `pi --mode rpc` that
|
|
217
|
+
pi-loops runs for it — so the session file, `--resume`, your models, tools and extensions are the same either way, and the model
|
|
222
218
|
and thinking level you last chose start the next session whichever window it opens in. The browser
|
|
223
219
|
one is a session and not a viewer: what a person can still do after the window changed is a release
|
|
224
220
|
gate, and [docs/web-ui-parity.md](docs/web-ui-parity.md) has it line by line.
|
|
@@ -228,15 +224,15 @@ earlier one in this project, **compact** summarises what is there and says what
|
|
|
228
224
|
or as `/clear`, `/new`, `/resume` and `/compact <what to keep>` in the composer. None of them
|
|
229
225
|
deletes anything; the session you leave is a file that `resume` lists.
|
|
230
226
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
227
|
+
The page is served by pi-loops in the background on **`http://127.0.0.1:4173/`**, so `pi-loops`
|
|
228
|
+
opens it and exits, and the page stays open when that terminal closes. Its token lives in a file, so
|
|
229
|
+
the address is the same one tomorrow and is worth bookmarking (`[page] port` in `config.toml` moves
|
|
230
|
+
it if 4173 is spoken for). The first visit leaves a cookie and you never see the token again. There
|
|
231
|
+
is no way to drop the token: the page is always on, so it is always behind it.
|
|
236
232
|
|
|
237
233
|
From a phone, the best route is `tailscale serve --bg 4173`: this server stays on loopback and the
|
|
238
|
-
tailnet does TLS and identity. On the same wifi, `
|
|
239
|
-
|
|
234
|
+
tailnet does TLS and identity. On the same wifi, `[page] host = "0.0.0.0"` in `config.toml` works
|
|
235
|
+
too. Press **add device** in a browser that is already signed in and point the phone at the
|
|
240
236
|
QR it shows: nothing to type, that device stays signed in, and the page can go on the home screen.
|
|
241
237
|
|
|
242
238
|
### Keeping it up to date
|
|
@@ -262,32 +258,62 @@ says what is in each one.
|
|
|
262
258
|
|
|
263
259
|
### Uninstall
|
|
264
260
|
|
|
261
|
+
Stop pi-loops before removing the extension. It keeps running in the background after the extension
|
|
262
|
+
is removed, and nothing else stops it:
|
|
263
|
+
|
|
265
264
|
```bash
|
|
265
|
+
pi-loops stop
|
|
266
266
|
pi remove npm:@alphacoder-v0/pi-loops # or git:github.com/alphacoder-v0/pi-loops, or the checkout path — whatever you installed
|
|
267
267
|
```
|
|
268
268
|
|
|
269
|
-
State stays in `~/.pi/agent/loops` until you delete it.
|
|
269
|
+
State stays in `~/.pi/agent/loops` until you delete it. The `pi-loops` launcher stays too. No
|
|
270
|
+
command removes it, so delete the file `install-launcher` wrote:
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
rm ~/.local/bin/pi-loops # or /usr/local/bin/pi-loops, if ~/.local/bin was not on your PATH
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
If you gave `install-launcher --dir <dir>`, the launcher is `<dir>/pi-loops`.
|
|
277
|
+
|
|
278
|
+
To go back to 0.22.8, stop pi-loops while this version is still installed, then install 0.22.8:
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
pi-loops stop
|
|
282
|
+
pi install npm:@alphacoder-v0/pi-loops@<version> # <version> is 0.22.8
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
0.22.8 reads your jobs and rules as they are. It does not run the plain jobs of closed
|
|
286
|
+
conversations, and it ignores the files that only later versions write, so nothing still waiting
|
|
287
|
+
for a conversation is delivered: rule results, plain-job prompts waiting for their conversation, claimed
|
|
288
|
+
findings, `/goal` continuations, recipe hand-offs, `inject_and_run` pushes and messages sent from
|
|
289
|
+
the browser. Let them drain first (`pi-loops status` lists them). It also ignores the built-in
|
|
290
|
+
tools later versions record on a loop, so a loop made in a chat with fewer of them runs with pi's
|
|
291
|
+
default tools; and it has no window guard, so a plain job's prompt in an open window runs as your
|
|
292
|
+
own message would, with no question before a dangerous command.
|
|
293
|
+
|
|
294
|
+
The stop is remembered in `~/.pi/agent/loops/ui.json`, and 0.22.8 keeps it. After you upgrade again,
|
|
295
|
+
pi-loops stays stopped until `pi-loops start`, or until `pi-loops` opens the browser page, which starts it.
|
|
270
296
|
|
|
271
297
|
## Commands
|
|
272
298
|
|
|
273
299
|
| Command | What it does |
|
|
274
300
|
|---|---|
|
|
275
|
-
| `/cron add [--stateful] [--verify] "<schedule>" <prompt>` | Schedule a job: a plain job belongs to this chat and injects its result here
|
|
301
|
+
| `/cron add [--stateful] [--verify] "<schedule>" <prompt>` | Schedule a job: a plain job belongs to this chat and injects its result here; `--stateful` makes a loop that runs in the background, with memory and inbox routing; `--verify` adds the checker |
|
|
276
302
|
| `/cron`, `/cron all` | This project's jobs, or every project on this machine |
|
|
277
303
|
| `/cron enable\|disable\|remove <ref>` | Pause, resume or delete one job — and write an audit entry into the session, as `add` does. `remove` takes a name of this project or the exact id of a job anywhere, so a name never reaches a checkout you are not in |
|
|
278
304
|
| `/cron disable --all` | Pause every job in this project (`--all-projects` for the machine); `/cron enable --all` resumes |
|
|
279
|
-
| `/cron run <ref>` |
|
|
305
|
+
| `/cron run <ref>` | Ask pi-loops to run a loop now; plain jobs run now only in the window holding their conversation |
|
|
280
306
|
| `/cron state <ref>` | The loop's notes — the Markdown it carries from one run to the next |
|
|
281
307
|
| `/cron runs [ref]` | The run log: when it fired, what it cost, what it found |
|
|
282
308
|
| `/cron trace <job> [k] [checker]` | The k-th latest run's full sub-agent transcript, maker or checker |
|
|
283
309
|
| `/cron set <job> …` | Change a job in place — `--prompt`, `--schedule`, model, thinking, timeout, name — keeping its id, and therefore its notes |
|
|
284
310
|
| `/cron cost [today\|7d\|all]` | What automation has cost, against `[limits] daily_budget_usd` |
|
|
285
|
-
| `/cron scheduler` |
|
|
286
|
-
| `/
|
|
311
|
+
| `/cron scheduler` | This window, its conversation, and pi-loops status |
|
|
312
|
+
| `/pi-loops start\|stop\|restart\|status` | Start, stop, restart or show pi-loops |
|
|
287
313
|
| `/cron clear <ref>` | Release a run marker left behind by a process that is gone |
|
|
288
314
|
| `/cron gc` | Remove plain jobs whose session was deleted (they are parked as disabled first) |
|
|
289
|
-
| `/cron panel on\|off` | The side panel above the editor:
|
|
290
|
-
| `/cron snapshot` | Write what only this process knows — connected MCP servers and their tools, active tools, hooks,
|
|
315
|
+
| `/cron panel on\|off` | The side panel above the editor: Rules, Loops, Runs, MCP |
|
|
316
|
+
| `/cron snapshot` | Write what only this process knows — connected MCP servers and their tools, active tools, hooks, whether pi-loops is running and its pid — into the session as a `pi_loops_snapshot` entry, for a front end that is not a terminal |
|
|
291
317
|
| `/inbox [list\|all\|claim <n>\|dismiss <n> [reason]\|clear] [--all]` | Triage findings from stateful loops. This project's by default, `--all` for every project — the same scoping `/cron` and `/triggers` use. A reason given with `dismiss` is shown to the loop's next run |
|
|
292
318
|
| `/goal [<condition>]`, `/goal pause\|resume\|clear` | Hold the session to a stop condition: after every turn an evaluator with no tools decides whether it is met, and sends the agent back to work if not (max 8 continuations). Bare `/goal` shows the one in force |
|
|
293
319
|
| `/new-trigger <natural language>` | Create a condition-based rule ("when ~/build.done exists, run cargo test") |
|
|
@@ -329,7 +355,7 @@ notifications, and lifecycle hooks — each has a page here.
|
|
|
329
355
|
- [docs/goal.md](docs/goal.md) — `/goal`: holding a session to a stop condition
|
|
330
356
|
- [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
|
|
331
357
|
- [docs/session-archive.md](docs/session-archive.md) — `/session-export`, `/session-import`
|
|
332
|
-
- [docs/cli.md](docs/cli.md) — the `pi-loops` command line: export, import, the inbox, and looking in on
|
|
358
|
+
- [docs/cli.md](docs/cli.md) — the `pi-loops` command line: export, import, the inbox, and looking in on pi-loops
|
|
333
359
|
- [docs/downstream.md](docs/downstream.md) — what a program may depend on: a recipe directory, `run_start` / `run_end`, `pi-loops inbox --json`; nothing else
|
|
334
360
|
- [docs/web-ui-parity.md](docs/web-ui-parity.md) — what the browser front end owes you, line by line
|
|
335
361
|
- [docs/configuration.md](docs/configuration.md) — paths, `config.toml`, flags, environment
|
|
@@ -349,9 +375,11 @@ notifications, and lifecycle hooks — each has a page here.
|
|
|
349
375
|
| `~/.pi/agent/loops/logs/pi-<pid>.log` | what each pi process's automation did |
|
|
350
376
|
| `~/.pi/agent/loops/triggers.json`, `triggers-audit.jsonl` | dynamic rules and trigger audit |
|
|
351
377
|
| `~/.pi/agent/loops/{config,mcp,hooks}.toml` | configuration |
|
|
352
|
-
| `~/.pi/agent/loops/
|
|
378
|
+
| `~/.pi/agent/loops/host-service.json`, `host.log` | pi-loops record and log |
|
|
379
|
+
| `~/.pi/agent/loops/leases/`, `requests/` | where each conversation is open, and what waits to be delivered to it |
|
|
380
|
+
| `~/.pi/agent/loops/pages/` | one record per conversation open in the browser page: its pi's pid, and when a tab last used it |
|
|
353
381
|
|
|
354
|
-
Set `PI_LOOPS_DIR` to relocate all of it. The notes are yours to edit when the agent got something
|
|
382
|
+
[docs/configuration.md](docs/configuration.md) lists every file. Set `PI_LOOPS_DIR` to relocate all of it. The notes are yours to edit when the agent got something
|
|
355
383
|
wrong, and `logs/pi-<pid>.log` is the file to read after an overnight failure.
|
|
356
384
|
|
|
357
385
|
## Automation outlives the window it was set up in
|
|
@@ -359,19 +387,19 @@ wrong, and `logs/pi-<pid>.log` is the file to read after an overnight failure.
|
|
|
359
387
|
Scheduled work is only worth trusting if it survives you closing the editor. So "pi was restarted"
|
|
360
388
|
is treated here as the normal case rather than the exception.
|
|
361
389
|
|
|
362
|
-
Jobs are machine-global and never expire.
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
390
|
+
Jobs are machine-global and never expire. A window starts pi-loops in the background when there is work to run. It runs stateful loops, rule checks and MCP pushes;
|
|
391
|
+
a window runs the plain jobs of the conversation open in it, and pi-loops runs those of
|
|
392
|
+
closed conversations. Rule results go to the conversation that created the rule; if it is closed,
|
|
393
|
+
pi-loops appends the result to it. The inbox gets a result only when that conversation cannot be
|
|
394
|
+
written. A tick a loop
|
|
395
|
+
missed while pi-loops was not running is caught up once, collapsed rather than replayed; a plain
|
|
396
|
+
job's is not, because its prompt was written for a conversation that has moved on (`--catchup` and
|
|
397
|
+
`--no-catchup` override either way).
|
|
398
|
+
|
|
399
|
+
Sub-agents are sessions opened through pi's SDK inside the process that runs them, not child
|
|
400
|
+
processes. Loops and rule checks run in the background, which has no window to share: their runs connect
|
|
401
|
+
their own MCP clients from `mcp.toml` and the project's own servers, so a server that is live only
|
|
402
|
+
because you had a window open is not live to a loop at 3am.
|
|
375
403
|
|
|
376
404
|
[docs/design.md](docs/design.md) has the architecture and the reasoning behind each of these.
|
|
377
405
|
|
package/README.zh-CN.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
给 [pi](https://github.com/earendil-works/pi) 的自动化层,做成一个 extension:cron 和有记忆的 loop、一个分诊用的 inbox、动态 trigger 与 MCP 推送通知、生命周期 hooks。pi 的代码一行没动。
|
|
6
6
|
|
|
7
|
-

|
|
8
8
|
|
|
9
9
|
一个 loop 醒来时带着上次留下的笔记,在干净上下文的子代理里干完活,把 finding 归档;你想看的时候去看,值得认真处理的那条再 claim 成真正的一轮。加上 `--verify`,第二个对抗式子代理会先把每条 finding 挑一遍,留下的才到你面前。关掉 pi 之后,这些照样跑。
|
|
10
10
|
|
|
@@ -28,7 +28,7 @@ pi-loops --continue # 接着这个目录里最新的那个会
|
|
|
28
28
|
pi-loops sessions [--all] # 这个目录(或整台机器)记下的会话
|
|
29
29
|
pi-loops inspect <file> # 别人发来的 .pisession,导入前先看清里面是什么
|
|
30
30
|
pi-loops upgrade # 装最新的发布版,当初从哪装就从哪装
|
|
31
|
-
pi-loops
|
|
31
|
+
pi-loops status # 看一眼没有 pi 开着时在跑的自动化
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
上面这几条都得先有 `pi-loops` 这个命令,它在 [上手](#上手) 里装。其余都是会话里的斜杠命令(`/cron`、`/inbox`、`/triggers`、`/goal`),装上扩展重启 pi 就能用。
|
|
@@ -39,7 +39,7 @@ pi-loops host status # 看一眼没有 pi 开着时在跑的自
|
|
|
39
39
|
|
|
40
40
|
Node ≥ 22.6(pi 直接加载 TypeScript 源码),pi ≥ 0.84.3(实际在 0.85 上测;更老的 pi 会在加载时被明确拒绝,报一句写明所需版本的话,而不是一个缺导出的链接错误),以及**一个真的能说上话的 provider**——先跑一次 `pi`,发一句,确认有回答。pi-loops 会在你不看着的时候替你跑子代理;凭据没配好,别等到第二天早上看见空 inbox 才知道。
|
|
41
41
|
|
|
42
|
-
pi-loops
|
|
42
|
+
pi-loops 自己没有任何运行时依赖。负责跑 loop 的 后台进程始终用 node 运行;pi 跑在 bun 下时,pi-loops 使用 PATH 上的 `node`。
|
|
43
43
|
|
|
44
44
|
### 2. 装上
|
|
45
45
|
|
|
@@ -86,9 +86,9 @@ pi-loops --model anthropic/claude-opus-5 -e .
|
|
|
86
86
|
|
|
87
87
|
重新开始不用回终端:**clear** 开一个新会话,**resume** 回到这个项目里早先的某个会话,**compact** 压缩当前上下文并告诉你压成了多少。三个都是按钮,也都可以在输入框里直接敲(`/clear` 或 `/new`、`/resume`、`/compact 保住 API 的形状`)。谁都不删东西:离开的那个会话就是磁盘上的一个文件,`resume` 里列的就是它,标题是当时说的第一句话。
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
浏览器那个页面由后台的 pi-loops 提供,地址是 **`http://127.0.0.1:4173/`**:`pi-loops` 把它打开就退出,关掉那个终端,页面照样开着。token 存在文件里,所以这个地址明天还是它,可以直接收藏(4173 被占了,用 `config.toml` 里的 `[page] port` 换一个)。第一次访问会留一个 cookie,之后再也看不到 token。token 去不掉:页面一直开着,所以一直挡在它后面。
|
|
90
90
|
|
|
91
|
-
手机上用:最好的路是 `tailscale serve --bg 4173`——服务器仍然只绑 127.0.0.1,TLS 和身份都交给 tailnet,手机打开 `https://<机器>.<tailnet>.ts.net/`。同一个 wifi
|
|
91
|
+
手机上用:最好的路是 `tailscale serve --bg 4173`——服务器仍然只绑 127.0.0.1,TLS 和身份都交给 tailnet,手机打开 `https://<机器>.<tailnet>.ts.net/`。同一个 wifi 里也可以在 `config.toml` 里写 `[page] host = "0.0.0.0"`,再 `pi-loops restart`。手机第一次进什么都不用输:在已经登录的浏览器里点 **add device**,拿手机扫那个二维码就进去了,之后一直是登录状态(不想扫也可以输下面那六位数)。加到主屏幕会以独立窗口打开(有 manifest,没有 service worker)。
|
|
92
92
|
|
|
93
93
|
### 4. 第一个 loop
|
|
94
94
|
|
|
@@ -99,7 +99,7 @@ pi-loops --model anthropic/claude-opus-5 -e .
|
|
|
99
99
|
每天早上一个干净上下文的子代理带着上次写的笔记跑一遍,回复末尾给出 `<loop-state>…</loop-state>`(留给明天的笔记)和 `<inbox>一行 finding</inbox>`:状态写进一个 Markdown 文件,finding 进 inbox,你的对话一个字都不动。下面是一个盯着 TODO 文件的 loop 相隔半小时的两次运行,第一次:
|
|
100
100
|
|
|
101
101
|
```text
|
|
102
|
-
|
|
102
|
+
todo · 5s · $0.000 · 0 findings · state updated
|
|
103
103
|
MD5 unchanged (`06ff2ec8af3668bb89ecc6580110ecad`), git rev still `15b6562`. No new unchecked items — nothing to report.
|
|
104
104
|
/cron trace todo · /inbox
|
|
105
105
|
```
|
|
@@ -107,7 +107,7 @@ MD5 unchanged (`06ff2ec8af3668bb89ecc6580110ecad`), git rev still `15b6562`. No
|
|
|
107
107
|
中间提交了一次之后,第二次:
|
|
108
108
|
|
|
109
109
|
```text
|
|
110
|
-
|
|
110
|
+
todo · 4s · $0.000 · 1 finding · state updated
|
|
111
111
|
md5 changed (06ff2ec8… → fe00aaad…). One new unchecked item appeared.
|
|
112
112
|
• TODO.md: new unchecked item — cache the /search results for 60s (commit 395a124 "todo: cache search results")
|
|
113
113
|
/cron trace todo · /inbox
|
|
@@ -120,7 +120,6 @@ md5 changed (06ff2ec8… → fe00aaad…). One new unchecked item appeared.
|
|
|
120
120
|
Inbox (acme-api, 3 new, times +00:00):
|
|
121
121
|
1. [inb-aae794d5] TODO: rate-limit the /search endpoint (acme-api, cron:todo, 2026-09-12 05:25)
|
|
122
122
|
2. [inb-74b73c72] TODO.md: new unchecked item — cache the /search results for 60s (acme-api, cron:todo, 2026-09-12 05:28)
|
|
123
|
-
claim with /inbox claim <n>, dismiss with /inbox dismiss <n>
|
|
124
123
|
```
|
|
125
124
|
|
|
126
125
|
```text
|
|
@@ -144,7 +143,7 @@ claim with /inbox claim <n>, dismiss with /inbox dismiss <n>
|
|
|
144
143
|
daily_budget_usd = 5.0
|
|
145
144
|
```
|
|
146
145
|
|
|
147
|
-
|
|
146
|
+
只要有启用的 loop、规则或待处理推送,pi 窗口就会启动后台运行的 pi-loops,所以关闭窗口后早上九点的任务仍会运行(`/pi-loops`、`pi-loops status`)。不想自动启动就在同一个文件里写 `[host] auto = false`。
|
|
148
147
|
|
|
149
148
|
## 升级
|
|
150
149
|
|
|
@@ -163,7 +162,10 @@ pi-loops upgrade --check # 只看看有没有新的
|
|
|
163
162
|
|
|
164
163
|
## 卸载
|
|
165
164
|
|
|
165
|
+
卸载扩展前先停止 pi-loops:
|
|
166
|
+
|
|
166
167
|
```bash
|
|
168
|
+
pi-loops stop
|
|
167
169
|
pi remove npm:@alphacoder-v0/pi-loops # 当初怎么装的就怎么删:npm: 包名、git: ref 或检出路径
|
|
168
170
|
pi remove git:github.com/alphacoder-v0/pi-loops
|
|
169
171
|
pi -e /path/to/pi-loops # 或者:只在这次启动试用,什么都不装
|
|
@@ -172,6 +174,15 @@ pi update --extensions # 对齐已安装的包
|
|
|
172
174
|
|
|
173
175
|
数据留在 `~/.pi/agent/loops`,想清就删目录。
|
|
174
176
|
|
|
177
|
+
要退回 0.22.8:趁 0.23 还装着,先停止 pi-loops,再安装 0.22.8:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
pi-loops stop
|
|
181
|
+
pi install npm:@alphacoder-v0/pi-loops@<version> # <version> 填 0.22.8
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
0.22.8 能直接读你现有的 job 和规则,但不会运行已关闭对话里的 plain job,也不理会只有 0.23 才写的那些文件,所以 `deliveries/` 里还在排队的规则结果不会再送达。
|
|
185
|
+
|
|
175
186
|
包里附带一个 skill(`skills/pi-loops`),让 agent 知道什么时候该用 `cron_create`、`new_trigger` 和 inbox。
|
|
176
187
|
|
|
177
188
|
[docs/](docs/) 里分成 loops、triggers、goal、mcp、hooks、session-archive、cli、configuration、design、troubleshooting、web-ui-parity 几篇;变更记录在 [CHANGELOG.md](CHANGELOG.md),贡献者说明在 [AGENTS.md](AGENTS.md)。
|
|
@@ -247,11 +258,11 @@ playbook 拷到 `.agents/skills/<name>/`,通过 `.git/info/exclude` 排除在
|
|
|
247
258
|
/cron state ci loop 的笔记(状态脊柱)
|
|
248
259
|
/cron runs [ci] 最近运行,最新在前
|
|
249
260
|
/cron trace ci 2 第 2 新那次运行的 transcript:prompt、工具调用、结果、回复
|
|
250
|
-
/cron scheduler
|
|
261
|
+
/cron scheduler 当前窗口、它的会话、pi-loops 状态(`/cron status` 是 `/cron` 的别名)
|
|
251
262
|
/cron cost [today|7d|all] 自动化花了多少,按任务分,对照 [limits] daily_budget_usd
|
|
252
263
|
/cron disable --all 本项目全停(--all-projects 停整台机器);/cron clear <ref> 清掉死进程留下的 running 标记
|
|
253
264
|
/cron gc 清掉会话已消失的任务(本项目;--all 才跨项目)
|
|
254
|
-
/
|
|
265
|
+
/pi-loops [start|stop] 查看、启动或停止后台运行的 pi-loops
|
|
255
266
|
/cron snapshot 把「只有这个进程知道的状态」写进会话:哪些 MCP 连上了、暴露了什么工具、
|
|
256
267
|
当前 active tools、hooks、谁拥有时钟。给非终端的前端读的
|
|
257
268
|
|
|
@@ -385,9 +396,9 @@ backup.pisession 无压缩 ustar,0600,拒绝覆盖已有文
|
|
|
385
396
|
- 结果默认只落在 TUI 卡片和 audit 里;规则带 `promote_to_chat` 时,它以 `[Trigger <trace_id>] …` 开头插进主对话,后面的 turn 看得见。
|
|
386
397
|
- 5 分钟 dedup 窗口,`listChanged` 类通知用稳定 key 折叠成最新一条,自定义通知必须带 `_meta.pi_dedup_key`,否则在源头丢弃并计数。
|
|
387
398
|
- 自然语言里出现「每小时 / daily / cron / 定时任务」之类时,`new_trigger` 会拒绝并让 agent 改用 `cron_create`。
|
|
388
|
-
- 工具:`new_trigger`(condition / action / spec / fire_once / promote_to_chat)、`list_triggers`、`remove_trigger`(id | all)、`set_trigger_state`,描述与返回文本固定。建规则、删规则、重新启用自动化这三件事都要你点头:工具把原因摆出来等你确认,没有 UI
|
|
399
|
+
- 工具:`new_trigger`(condition / action / spec / fire_once / promote_to_chat)、`list_triggers`、`remove_trigger`(id | all)、`set_trigger_state`,描述与返回文本固定。建规则、删规则、重新启用自动化这三件事都要你点头:工具把原因摆出来等你确认,没有 UI 可点的场合(子会话、后台进程)一律拒绝。
|
|
389
400
|
- 规则是机器全局的(带 cwd),`/triggers rules` 默认只看本项目;子代理进程里不注册这些工具,所以一条 trigger 的动作造不出新的 trigger。
|
|
390
|
-
-
|
|
401
|
+
- 轮询间隔:`config.toml` 的 `[triggers] poll_interval_secs`,pi-loops 每分钟重读一次。`--trigger-poll-secs` 已不起作用,传了会提示一句。
|
|
391
402
|
|
|
392
403
|
### MCP 推送作为 trigger 源
|
|
393
404
|
|
|
@@ -446,7 +457,7 @@ stdio 服务器崩溃后自动重连;`Mcp-Session-Id` 会话头;服务器发
|
|
|
446
457
|
- 单条规则写错只跳过那条并提示,其它照常加载。同一事件的规则按文件顺序串行执行,不阻塞 agent。
|
|
447
458
|
- 超时或 Ctrl-C 时杀掉 hook 的整棵进程树,不只是 `sh`。
|
|
448
459
|
- 项目级 `<repo>/.pi/hooks.toml` 默认忽略;用户 `hooks.toml` 顶层写 `allow_project_hooks = true`、或 `config.toml` 同名键、或 `PI_ALLOW_PROJECT_HOOKS=1`(`true` 也算,其它值都算关)才启用。**在无头宿主里还额外要求 pi 对那个任务的 cwd 有精确的信任记录**——`allow_project_hooks` 是对「你会打开的项目」的表态,不是对「模型顺手指过去的目录」的表态。
|
|
449
|
-
- hook 打到 stdout 的东西写进本进程日志(`logs/pi-<pid>.log
|
|
460
|
+
- hook 打到 stdout 的东西写进本进程日志(`logs/pi-<pid>.log`,后台日志写入 `host.log`),截到 4000 字符——「打印点东西再去看」这个最常用的调试手段现在是通的。
|
|
450
461
|
- 子代理进程里不触发 hooks,只有你交互的那个 pi 触发。
|
|
451
462
|
|
|
452
463
|
## 子代理每次收到的 prompt
|
|
@@ -473,7 +484,7 @@ Output protocol (mandatory):
|
|
|
473
484
|
| **状态脊柱** — 每个 loop 一份 ≤2000 字符的笔记,run N+1 读到 run N 写的 | `<loop-state>` 标签解析后写入 Markdown,下次运行拼进 prompt 头部 | `~/.pi/agent/loops/state/<id>.md` |
|
|
474
485
|
| **maker/checker** | `--verify`:第二个对抗式子代理逐条核实 findings,drop 的不进 inbox | `runs.jsonl` 的 `checker` 字段 |
|
|
475
486
|
| **路由层** — 产出既不打断你也不沉进日志 | `<inbox>` 标签 → JSONL 追加,`new → claimed/dismissed` 生命周期,状态栏 `Inbox: N new` 角标;条目 id `inb-<32hex>`、来源 `cron:<id或name>`、`/inbox` 与 `/inbox all` 的行格式、错误措辞都固定下来,已核实的多一个 `✓` 标记 | `~/.pi/agent/loops/inbox.jsonl` |
|
|
476
|
-
| stateful job 走 SubAgent、永不碰主对话 |
|
|
487
|
+
| stateful job 走 SubAgent、永不碰主对话 | 在后台运行的 pi-loops 里开的子会话(pi SDK),干净上下文;MCP 连接由 pi-loops 按 `mcp.toml` 和项目配置自己建立,不再借用某个窗口的连接;完整 transcript 保留在 `sessions/<id>/`,`/cron trace` 可看 | `~/.pi/agent/loops/sessions/<id>/*.jsonl` |
|
|
477
488
|
| trigger 的产出看得见、可追溯 | 每次运行结束在 transcript 里落一张卡片(耗时、成本、findings、摘要),run log 记退出码与用量 | `runs.jsonl` |
|
|
478
489
|
| 预览与 audit 一律脱敏 | `src/redact.ts` 一套 redactor,列表、卡片、run log、trace 全部过一遍 | — |
|
|
479
490
|
| 普通 cron 走 inject-and-run | 不加 `--stateful` 的任务到点用 `pi.sendUserMessage` 注入创建它的会话,消息带 `[Trigger <trace>] ` 前缀(空闲直接发,忙则 followUp 排队) | — |
|
|
@@ -486,18 +497,23 @@ Output protocol (mandatory):
|
|
|
486
497
|
## 自动化要活过开它的那个窗口
|
|
487
498
|
|
|
488
499
|
定时任务值不值得信,取决于你关掉编辑器之后会发生什么。所以这里把「pi 重启过」当成常态:
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
500
|
+
任务和规则保存在本机的 loops 目录。
|
|
501
|
+
|
|
502
|
+
一个 loops 目录最多运行一个后台运行的 pi-loops。pi 窗口在会话开始、每 30 秒一次的 tick,以及新建 loop、规则后,
|
|
503
|
+
只要存在启用的 loop、规则或有待处理推送的 MCP 服务器,或者某个已关闭的会话有普通任务或排队的规则结果,
|
|
504
|
+
就会按需启动 pi-loops。pi-loops 运行所有 stateful loop、动态规则检查和 MCP 推送,也在已关闭的会话里运行它们的
|
|
505
|
+
普通任务,每次都是一轮模型调用;窗口运行在它里面打开的那个会话的普通任务。规则结果回到创建规则的
|
|
506
|
+
会话:窗口持有该会话时送入会话,其他进程判定时排队给该窗口;会话已关闭时由 pi-loops 追加进会话。只有会话
|
|
507
|
+
文件无法写入时,结果才进入 inbox。
|
|
508
|
+
由 shell 创建且没有会话的规则,结果进入 inbox。
|
|
509
|
+
|
|
510
|
+
pi-loops 不会因窗口打开或关闭而退出。`/pi-loops` 显示 pid、pi-loops 版本、启动时间、正在运行的工作、窗口
|
|
511
|
+
持有的会话和日志;`/pi-loops stop` 会记录手动停止,在 `/pi-loops start` 前窗口不会重新启动它。
|
|
512
|
+
`[host] auto = false` 或单进程环境变量 `PI_LOOPS_HOST=off` 禁止窗口自动启动。无法启动时会在 1、5、30
|
|
513
|
+
分钟后重试,状态行显示暂停原因,`/pi-loops status` 和 `host.log` 提供详情。窗口连续三次探测不到控制 socket
|
|
514
|
+
会替换失联的 pi-loops:先发 SIGTERM,10 秒后再发 SIGKILL;仍在启动的 pi-loops 不会因此被杀掉。新版窗口会替换旧版
|
|
515
|
+
pi-loops,旧版窗口不会替换新版 pi-loops。升级期间,pi-loops 0.22 窗口让后台进程运行 loop、自己待命,但仍会运行自己持有
|
|
516
|
+
会话的普通任务;重启这些旧窗口才能让普通任务也采用新行为。机器重启后,要等 pi 窗口打开才会恢复运行。pi-loops 还有一个只读状态页,`pi-loops status --url` 打印带令牌的地址。
|
|
501
517
|
|
|
502
518
|
## 对 pi 的无侵入性
|
|
503
519
|
|
|
@@ -517,13 +533,17 @@ Output protocol (mandatory):
|
|
|
517
533
|
| `~/.pi/agent/loops/spend.json` | 轮转掉的那部分花费按天留一份,预算上限不会因为 run log 被截断而失效 |
|
|
518
534
|
| `~/.pi/agent/loops/logs/pi-<pid>.log` | 每个 pi 进程的自动化诊断,超 2 MB 保留后半,只留最近五个进程(进程还活着的那份不算) |
|
|
519
535
|
| `~/.pi/agent/loops/sessions/<id>/*.jsonl` | 子代理完整 transcript,每个 loop 保留最近 20 份 |
|
|
520
|
-
| `~/.pi/agent/loops/
|
|
536
|
+
| `~/.pi/agent/loops/host-service.json`、`host.log` | 后台运行的 pi-loops 记录与日志 |
|
|
537
|
+
| `~/.pi/agent/loops/leases/`、`requests/` | 每个会话在哪里打开,以及等着送进会话的内容 |
|
|
538
|
+
| `~/.pi/agent/loops/pages/` | 浏览器页面里开着的每个会话一条记录:它的 pi 的 pid,以及标签页最近一次用它的时间 |
|
|
521
539
|
| `~/.pi/agent/loops/triggers.json` | 动态 trigger 规则(全局,带 cwd) |
|
|
522
540
|
| `~/.pi/agent/loops/triggers-audit.jsonl` | trigger audit,超 2 MB 保留后半 |
|
|
523
541
|
| `~/.pi/agent/loops/sessions/triggers-<项目>-<哈希>/*.jsonl` | 动态检查子代理的 transcript,每个项目一个目录(目录名带项目路径的哈希,同名的两个项目不会共用),保留最近 40 份 |
|
|
524
542
|
| `~/.pi/agent/loops/config.toml` | `[triggers] poll_interval_secs`、`allow_project_hooks` |
|
|
525
543
|
| `~/.pi/agent/loops/mcp.toml` · `hooks.toml` | MCP 推送源、生命周期 hooks |
|
|
526
544
|
|
|
545
|
+
完整的文件列表见 [docs/configuration.md](docs/configuration.md)。
|
|
546
|
+
|
|
527
547
|
跨进程写入靠 `mkdir` 锁 + 原子改名,没有原生依赖。
|
|
528
548
|
|
|
529
549
|
## 代码结构
|
|
@@ -531,12 +551,12 @@ Output protocol (mandatory):
|
|
|
531
551
|
```
|
|
532
552
|
src/extension-entry.ts pi 加载的入口:先查 pi 版本(src/pi-floor.ts),够了再导入 src/pi-loops.ts
|
|
533
553
|
src/pi-loops.ts 扩展本体:命令、工具、生命周期、状态栏角标、面板
|
|
534
|
-
src/cli.ts `pi-loops`:会话入口(网页或终端)与 sessions / inspect / export / import /
|
|
554
|
+
src/cli.ts `pi-loops`:会话入口(网页或终端)与 sessions / inspect / export / import / start / stop / status
|
|
535
555
|
src/cli-entry.mjs bin
|
|
536
556
|
src/ts-entry.mjs .mjs 入口怎么加载本包的 .ts:Node 自己剥类型,装在 node_modules 下就借 pi 的 jiti
|
|
537
557
|
|
|
538
558
|
流水线
|
|
539
|
-
src/scheduler.ts tick
|
|
559
|
+
src/scheduler.ts tick 循环、到期判定、错过补发、并发/重叠控制、子会话执行、写回;pi-loops 在后台运行 loops,窗口跑其会话的普通任务
|
|
540
560
|
src/trigger-runtime.ts 动态规则运行时:admit、投递(sub_agent / inject_summary / inject_and_run)、fire-once、promote、audit
|
|
541
561
|
src/triggers.ts 规则本身:解析、prompt、id 提取、存储、dedup 窗口
|
|
542
562
|
src/goal.ts /goal:停止条件状态机、评估器 prompt、续跑预算
|
|
@@ -557,12 +577,12 @@ src/subagent-guard.ts 把该策略装进每个子会话的合成扩展
|
|
|
557
577
|
src/transcript.ts 把子代理 session 文件压成可读的几十行
|
|
558
578
|
|
|
559
579
|
没有 pi 开着的时候
|
|
560
|
-
src/host.ts
|
|
561
|
-
src/host-entry.mjs
|
|
562
|
-
src/host-control.ts
|
|
563
|
-
src/host-control-channel.ts
|
|
564
|
-
src/host-runtime.ts
|
|
565
|
-
src/presence.ts
|
|
580
|
+
src/host.ts 后台运行的 pi-loops:认领、自检、0.22 兼容边界
|
|
581
|
+
src/host-entry.mjs pi-loops 的启动入口,负责加载 node_modules 下的 TypeScript
|
|
582
|
+
src/host-control.ts 服务记录、启动、退出与恢复决策
|
|
583
|
+
src/host-control-channel.ts 控制 socket:状态、中止、停止、运行与升级
|
|
584
|
+
src/host-runtime.ts 后台运行逻辑(调度器、triggers、按请求提供的工具)
|
|
585
|
+
src/presence.ts pi-loops 0.22 窗口写入;升级时只读
|
|
566
586
|
src/register-pi.mjs 在 pi 之外解析 pi 包的 node --import 钩子(宿主、测试用)
|
|
567
587
|
src/pi-resolver.mjs 这个钩子去哪里找
|
|
568
588
|
|
|
@@ -606,13 +626,13 @@ TypeScript 的类型信息判断。
|
|
|
606
626
|
|
|
607
627
|
- 子会话继承父会话的 model/thinking(任务固定了模型则用固定的);子会话里不再加载这个扩展本身,不会递归。
|
|
608
628
|
- 子会话没有 UI 就没有审批弹窗:需要确认的工具在子会话里 **fail-closed 拒绝**。没人能点「同意」的时候,默认答案只能是「不」。要收紧就用 `--tools read,grep,ls`。
|
|
609
|
-
- 状态栏角标最多五段,按需出现,之间用 ` · ` 隔开:`Inbox: N new`、`N
|
|
629
|
+
- 状态栏角标最多五段,按需出现,之间用 ` · ` 隔开:`Inbox: N new`、`N loops failing (<name> ×K)`、`running: <loop>`(多个用逗号连起来,trigger 检查在这儿显示成 `trigger-check`)、`mcp: 1 source down` / `mcp: 2 sources down`、`loops paused: the pi-loops could not start`。
|
|
610
630
|
- 同一任务上一轮还在跑时新 tick 直接跳过并计数(`overlap-skipped`),不排队。
|
|
611
631
|
- 同时最多 3 个子代理在跑(`[cron] max_concurrent_runs`)——loop 运行、trigger 检查、`/goal` 评估器**共用这一个池子**(`src/slots.ts`),三条路加起来一共 3 个。`/goal` 评估器和 `/cron run` 占槽但永不被拒(你直接要的东西,机器悄悄不做和从没设过是一样的),所以 `/triggers running` 有可能显示 `4 of 3 sub-agent slot(s) in use`。
|
|
612
632
|
- `[limits] daily_budget_usd` 不只挡派发,也会**停掉正在跑的运行**:算的是运行日志里已落账的花费加上本进程在飞的(并行的兄弟运行,以及 `--verify` 那对共用 runId 的 maker/checker)。被预算停掉记为 aborted 而不是 failed,所以时隙还欠着、失败连击不累加。
|
|
613
633
|
- inbox 的状态改写是「最后写者赢」:两个进程同时处理同一条时,后写的那个说了算,不做冲突检测。
|
|
614
|
-
- 常驻面板是编辑器上方的 widget:`Triggers` 规则最多 5 条 + `Polling` 最近一次检查、`Inbox N new`、`Cron` 启停统计与任务最多 5 条、`MCP`
|
|
615
|
-
- 跨设备访问不放 broker 在中间:让你从别处够到那个还待在原地的前端——`tailscale serve` 在 tailnet 上终结 TLS、代理到本机 loopback
|
|
634
|
+
- 常驻面板是编辑器上方的 widget:`Triggers` 规则最多 5 条 + `Polling` 最近一次检查、`Inbox N new`、`Cron` 启停统计与任务最多 5 条、`MCP` 各服务器连接状态与工具数、`Hooks` cli_hooks 规则数与事件——这几段各自没内容时不显示。规则、任务、新 findings、MCP 服务器全都没有时整个面板不显示;`/cron panel off` 或 `/triggers panel off` 关闭,偏好存在 `ui.json`。
|
|
635
|
+
- 跨设备访问不放 broker 在中间:让你从别处够到那个还待在原地的前端——`tailscale serve` 在 tailnet 上终结 TLS、代理到本机 loopback,手机就能开,中间不经过任何第三方;同一网段则在 `config.toml` 里设 `[page] host`。手机第一次进用配对码和二维码([docs/cli.md](docs/cli.md#from-a-phone))。真正没覆盖的是「手机两个网络都不在」,那种情况才需要中继。
|
|
616
636
|
- 这是一个自动化层,不是一个 agent:写代码、读网页、管 skill 都是 pi 自己的本事,这里只决定什么时候、在哪里、带着什么上下文去跑它们。
|
|
617
637
|
|
|
618
638
|
## 致谢
|