@alphacoder-v0/pi-loops 0.17.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/CHANGELOG.md +1989 -0
  2. package/LICENSE +21 -0
  3. package/README.md +370 -0
  4. package/README.zh-CN.md +606 -0
  5. package/docs/cli.md +252 -0
  6. package/docs/configuration.md +110 -0
  7. package/docs/design.md +155 -0
  8. package/docs/goal.md +59 -0
  9. package/docs/hooks.md +110 -0
  10. package/docs/loops.md +315 -0
  11. package/docs/mcp.md +90 -0
  12. package/docs/screenshot.png +0 -0
  13. package/docs/session-archive.md +64 -0
  14. package/docs/triggers.md +140 -0
  15. package/docs/troubleshooting.md +111 -0
  16. package/docs/web-ui-parity.md +305 -0
  17. package/examples/README.md +18 -0
  18. package/examples/mcp-notify-server.mjs +35 -0
  19. package/examples/mcp.toml +8 -0
  20. package/package.json +71 -0
  21. package/skills/pi-loops/SKILL.md +50 -0
  22. package/src/archive.ts +476 -0
  23. package/src/args.ts +217 -0
  24. package/src/cli-entry.mjs +23 -0
  25. package/src/cli.ts +610 -0
  26. package/src/config.ts +103 -0
  27. package/src/danger.ts +128 -0
  28. package/src/goal.ts +178 -0
  29. package/src/hooks.ts +436 -0
  30. package/src/host-control-channel.ts +243 -0
  31. package/src/host-control.ts +203 -0
  32. package/src/host-entry.mjs +14 -0
  33. package/src/host-runtime.ts +222 -0
  34. package/src/host.ts +329 -0
  35. package/src/inbox.ts +232 -0
  36. package/src/job-edit.ts +113 -0
  37. package/src/job-health.ts +29 -0
  38. package/src/lock.ts +193 -0
  39. package/src/log.ts +108 -0
  40. package/src/mcp-pool.ts +141 -0
  41. package/src/mcp.ts +885 -0
  42. package/src/paths.ts +33 -0
  43. package/src/pi-loops.ts +2292 -0
  44. package/src/pi-resolver.mjs +49 -0
  45. package/src/presence.ts +143 -0
  46. package/src/protocol.ts +198 -0
  47. package/src/redact.ts +61 -0
  48. package/src/register-pi.mjs +3 -0
  49. package/src/runner.ts +149 -0
  50. package/src/schedule.ts +431 -0
  51. package/src/scheduler.ts +981 -0
  52. package/src/sdk-runner.ts +536 -0
  53. package/src/share.ts +121 -0
  54. package/src/slots.ts +76 -0
  55. package/src/snapshot.ts +40 -0
  56. package/src/store.ts +599 -0
  57. package/src/subagent-guard.ts +47 -0
  58. package/src/thinking.ts +30 -0
  59. package/src/toml.ts +244 -0
  60. package/src/tools.ts +455 -0
  61. package/src/transcript.ts +84 -0
  62. package/src/trigger-runtime.ts +711 -0
  63. package/src/triggers.ts +558 -0
  64. package/src/trust.ts +47 -0
  65. package/src/ts-entry.mjs +67 -0
  66. package/src/ui-prefs.ts +84 -0
  67. package/src/version.ts +14 -0
  68. package/src/web.mjs +4484 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 pi-loops authors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,370 @@
1
+ # pi-loops
2
+
3
+ English · [中文](README.zh-CN.md)
4
+
5
+ An automation layer for [pi](https://github.com/earendil-works/pi), shipped as a plain pi extension.
6
+ Nothing in pi is patched.
7
+
8
+ ![Two runs of the same loop in pi's browser window: the first ends "0 findings — nothing to report", the second reports one new TODO item, and /inbox lists what is waiting](docs/screenshot.png)
9
+
10
+ A loop wakes up with the notes its last run left, does the work in a sub-agent that starts clean,
11
+ and files what it found. You read the findings when you want to, and claim the ones worth a turn.
12
+ It all keeps running after you close pi.
13
+
14
+ ## Why this exists
15
+
16
+ Some work should happen while nobody is watching: the issues opened overnight, the dependency that
17
+ picked up a CVE, the test that started failing on main. An agent can do any of it, but only when you
18
+ sit down and ask — so the asking is the work. And the two obvious ways to automate it put the result
19
+ in the wrong place: a prompt on a timer interrupts you with something you did not want *now*, and a
20
+ script writes to a log nobody opens.
21
+
22
+ > "Stop prompting the agent. Build loops that prompt the agent for you."
23
+ > — Addy Osmani, *Loop Engineering*
24
+
25
+ So the run happens in a sub-agent that never touches your conversation — nothing lands while you are
26
+ mid-thought. What it finds waits in an inbox rather than a log: an inbox line is one you can claim, a
27
+ log line is one you have to go looking for. And it starts from what the last run wrote down, so what
28
+ reaches you is what changed rather than everything that is there.
29
+
30
+ ## Install
31
+
32
+ Node ≥ 22.6 (pi loads the TypeScript sources directly) and a pi to load it into — 0.85 is what this
33
+ is tested against, and older ones are untested rather than refused. Nothing else: pi-loops has no
34
+ runtime dependencies. What you do want first is a provider you can actually talk to — run `pi`, send
35
+ one message, make sure you get an answer. pi-loops runs sub-agents while you are not watching, and
36
+ the first sign of credentials that do not work should not be an empty inbox tomorrow morning.
37
+
38
+ ```bash
39
+ pi install git:github.com/alphacoder-v0/pi-loops@v0.17.3 # from GitHub, at a pinned tag
40
+ pi install /path/to/pi-loops # or a local checkout — `pi install .` in this repo
41
+ pi -e /path/to/pi-loops # or none of them: try it for one run, installing nothing
42
+ ```
43
+
44
+ Install one of them, not two. Two copies register the same tools, and pi refuses to load the
45
+ second — `Tool "cron_create" conflicts with …`, and it exits. If you are working on the code, the
46
+ checkout is the one to keep.
47
+
48
+ Then restart pi, and that is the whole install: `/cron`, `/inbox`, `/triggers` and `/goal` are
49
+ registered by the extension itself, so they work with nothing on your `PATH` and no launcher. The
50
+ `pi-loops` command is a separate thing, needed only for the browser window and the shell
51
+ subcommands — [The browser window, and the command line](#the-browser-window-and-the-command-line)
52
+ sets it up when you want it. The package also ships a skill (`skills/pi-loops`), so the agent knows
53
+ when to reach for `cron_create`, `new_trigger` and the inbox on its own.
54
+
55
+ ## Your first loop
56
+
57
+ ```text
58
+ /cron add --stateful --name todo "0 9 * * *" read TODO.md and report any unchecked item that was not in your notes last run
59
+ ```
60
+
61
+ Every morning a fresh sub-agent runs with the notes it wrote last time, does the work, and hands
62
+ back two things: the notes for tomorrow, and any finding worth your attention. The notes go to a
63
+ Markdown file, the findings go to the inbox, and your conversation is never touched. (The run says
64
+ which is which in tags that a program reads rather than you — [docs/loops.md](docs/loops.md) has
65
+ them.) Here are two runs of that loop, half an hour apart — the first:
66
+
67
+ ```text
68
+ cron todo · 5s · $0.000 · 0 findings · state updated
69
+ md5 unchanged (`06ff2ec8af3668bb89ecc6580110ecad`), git rev still `15b6562`. No new unchecked items — nothing to report.
70
+ /cron trace todo · /inbox
71
+ ```
72
+
73
+ The second, after one commit:
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
84
+ whole file every morning, and you stop reading it by Thursday. What they filed waits in the inbox:
85
+
86
+ ```text
87
+ /inbox
88
+ 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:cron-b91def87, 2026-09-12 05:25)
90
+ 2. [inb-3216a92d] TODO: retry the payment webhook on 5xx (unchecked, no owner) (acme-api, cron:cron-b91def87, 2026-09-12 05:25)
91
+ 3. [inb-74b73c72] TODO.md: new unchecked item — cache the /search results for 60s (commit 395a124 "todo: cache search results") (acme-api, cron:todo, 2026-09-12 05:28)
92
+ claim with /inbox claim <n>, dismiss with /inbox dismiss <n>
93
+ ```
94
+
95
+ The third finding is the run above; the two over it came from another loop in the same checkout,
96
+ three minutes earlier. The inbox is one queue per project rather than one per job, so everything
97
+ watching this repo lands in the same list and you triage it in one pass.
98
+
99
+ ```text
100
+ /cron # what is scheduled here, and when it next runs
101
+ /cron run 1 # do not wait until 9am — run it now and watch
102
+ /inbox claim 1 # hand finding #1 to the agent as a real turn
103
+ /inbox dismiss 2 # not interesting
104
+ ```
105
+
106
+ ## Loops worth stealing
107
+
108
+ ```text
109
+ /cron add --stateful --name main-watch "0 9 * * *" read the commits on main since the revision in your notes, report anything that changes the public API, and record the new head revision
110
+ ```
111
+
112
+ The others are variations on this shape. The notes carry a revision, and only what changed since it
113
+ earns a line in your inbox.
114
+
115
+ ```text
116
+ /cron add --stateful --name deps "0 8 * * 1" run npm audit and report advisories whose id is not already in your notes; append every id you report to that list
117
+ ```
118
+
119
+ The watermark here is a list rather than a revision, and the loop appends to it as it reports. The
120
+ notes are plain Markdown you can read and correct (`/cron state deps`).
121
+
122
+ ```text
123
+ /cron add --verify --name ci every 30m run the test suite and report only tests that changed status since your notes
124
+ ```
125
+
126
+ `--verify` implies `--stateful` and puts a second, adversarial sub-agent between the findings and
127
+ you: a flake that failed once is exactly what should be stopped there, with the reason in
128
+ `/cron trace ci 1 checker`. It fails open — if the checker itself breaks, the findings still reach
129
+ the inbox, marked unverified, because a broken checker must not silence the loop.
130
+
131
+ ```text
132
+ /cron add --stateful --cwd /srv/acme-api --model openai/gpt-5.5 "0 7 * * *" summarize what changed in this repo since your notes
133
+ ```
134
+
135
+ A job records its directory and model at creation, so it is not tied to the window it was typed in:
136
+ `--cwd` runs it in another checkout (absolute, or relative to this project — no shell, so nothing
137
+ expands `~`), and `--model` pins it whatever this session is on (`/cron set <ref> --model -` unpins).
138
+
139
+ Two that are not loops:
140
+
141
+ ```text
142
+ /new-trigger when ~/build.done exists, run cargo test and show me the result
143
+ ```
144
+
145
+ A condition instead of a clock: a sub-agent re-checks it every `[triggers] poll_interval_secs` (600
146
+ by default) and acts when it holds — once, unless you ask for a repeat.
147
+
148
+ ```text
149
+ /cron add in 45m remind me to look at the deploy
150
+ ```
151
+
152
+ No `--stateful`, so this is a plain job: in 45 minutes the prompt lands in *this* conversation and
153
+ is answered there rather than filed — a reminder belongs in the chat, a nightly report does not.
154
+
155
+ ## Before you leave it running overnight
156
+
157
+ ```text
158
+ /cron cost # what automation has spent today
159
+ /cron disable --all # pause every job in this project
160
+ ```
161
+
162
+ Set a cap in `~/.pi/agent/loops/config.toml` before you rely on it:
163
+
164
+ ```toml
165
+ [limits]
166
+ daily_budget_usd = 5.0
167
+ ```
168
+
169
+ When the last pi quits, a headless host takes over the clock so the 9am run happens whether or not
170
+ you are at the machine, and hands it back the next time you open pi (`/cron host`,
171
+ `pi-loops host status`). If you would rather it did not, put `[host] auto = false` in the same file.
172
+
173
+ ## The browser window, and the command line
174
+
175
+ `pi-loops` is the part that needs a launcher: `pi install` puts the package under pi's managed
176
+ directory rather than on your `PATH`, so the command does not exist yet — the one thing
177
+ `install-launcher` cannot do for itself. Either way round works:
178
+
179
+ ```text
180
+ /pi-loops install-launcher # from inside pi, where the extension is already loaded
181
+ ```
182
+
183
+ ```bash
184
+ # or from a shell, in the directory pi installed the package into
185
+ cd ~/.pi/agent/git/github.com/alphacoder-v0/pi-loops
186
+ node src/cli-entry.mjs install-launcher
187
+ ```
188
+
189
+ Either writes a launcher into the first of `~/.local/bin` and `/usr/local/bin` that is already on
190
+ your `PATH`, and refuses rather than guessing if neither is — pass `--dir <dir>` to say where.
191
+ After that, `pi-loops` works from anywhere:
192
+
193
+ ```bash
194
+ pi-loops # start a session — browser here, terminal over ssh
195
+ pi-loops --tui # the terminal one, when you want it
196
+ pi-loops --continue # pick up the newest session in this directory
197
+ ```
198
+
199
+ `pi-loops sessions|inspect|export|import` and `pi-loops host status|abort|stop` need no pi session
200
+ open at all. They are for backups from cron or CI, restoring on a fresh machine, and looking in on
201
+ the headless host. See [docs/cli.md](docs/cli.md).
202
+
203
+ At a local terminal the bare command opens the browser front end; over ssh, or with no terminal at
204
+ all, it runs pi itself, because a browser on the far machine helps nobody. Pass `--web` or `--tui`
205
+ when that guess is wrong. Anything else you pass goes straight to pi (`pi-loops --model
206
+ anthropic/claude-opus-5 -e .`).
207
+
208
+ Both windows are complete pi sessions — the browser one runs `pi --mode rpc` behind a page — so the
209
+ session file, `--resume`, your models, tools and extensions are the same either way, and the model
210
+ and thinking level you last chose start the next session whichever window it opens in. The browser
211
+ one is a session and not a viewer: what a person can still do after the window changed is a release
212
+ gate, and [docs/web-ui-parity.md](docs/web-ui-parity.md) has it line by line.
213
+
214
+ Even starting over stays in the window — **clear** begins a new session, **resume** goes back to an
215
+ earlier one in this project, **compact** summarises what is there and says what it did, as buttons
216
+ or as `/clear`, `/new`, `/resume` and `/compact <what to keep>` in the composer. None of them
217
+ deletes anything; the session you leave is a file that `resume` lists.
218
+
219
+ It serves **`http://127.0.0.1:4173/`** by default, with a token that lives in a file, so the address
220
+ is the same one tomorrow and is worth bookmarking (`--port` moves it if 4173 is spoken for). The
221
+ first visit leaves a cookie and you never see the token again. Running `pi-loops` while one is
222
+ already up opens that window instead of failing on the port; `--no-auth` drops even that, on a
223
+ machine only you use.
224
+
225
+ From a phone, the best route is `tailscale serve --bg 4173`: this server stays on loopback and the
226
+ tailnet does TLS and identity. On the same wifi, `pi-loops --host 0.0.0.0` works too (and refuses
227
+ `--no-auth`). Press **add device** in a browser that is already signed in and point the phone at the
228
+ QR it shows: nothing to type, that device stays signed in, and the page can go on the home screen.
229
+
230
+ ### Keeping it up to date
231
+
232
+ ```bash
233
+ pi-loops upgrade # take the newest release
234
+ pi-loops upgrade --check # just say whether there is one
235
+ ```
236
+
237
+ It reads the release tags from the repository this copy came from, compares them with what you are
238
+ running, and installs the newest — which `pi update --extensions` will not do, because pi pins the
239
+ ref you asked for and keeps the clone on *that* ref ([docs/cli.md](docs/cli.md)).
240
+
241
+ Restart pi (or run `pi-loops` again) to load it. The launcher does not need reinstalling: pi keeps
242
+ each git package at `~/.pi/agent/git/<host>/<owner>/<repo>`, so a version change keeps the path.
243
+
244
+ Releases are tags on GitHub; [CHANGELOG.md](CHANGELOG.md) says what is in each one.
245
+
246
+ ### Uninstall
247
+
248
+ ```bash
249
+ pi remove git:github.com/alphacoder-v0/pi-loops # or the checkout path — whatever you installed
250
+ ```
251
+
252
+ State stays in `~/.pi/agent/loops` until you delete it.
253
+
254
+ ## Commands
255
+
256
+ | Command | What it does |
257
+ |---|---|
258
+ | `/cron add [--stateful] [--verify] "<schedule>" <prompt>` | Schedule a job: plain jobs inject their result into this chat, `--stateful` makes a loop with memory and inbox routing, `--verify` adds the checker |
259
+ | `/cron`, `/cron all` | This project's jobs, or every project on this machine |
260
+ | `/cron enable\|disable\|remove <ref>` | Pause, resume or delete one job — and write an audit entry into the session, as `add` does |
261
+ | `/cron disable --all` | Pause every job in this project (`--all-projects` for the machine); `/cron enable --all` resumes |
262
+ | `/cron run <ref>` | Fire one job now instead of waiting for its schedule |
263
+ | `/cron state <ref>` | The loop's notes — the Markdown it carries from one run to the next |
264
+ | `/cron runs [ref]` | The run log: when it fired, what it cost, what it found |
265
+ | `/cron trace <job> [k] [checker]` | The k-th latest run's full sub-agent transcript, maker or checker |
266
+ | `/cron set <job> …` | Change a job in place — `--prompt`, `--schedule`, model, thinking, timeout, name — keeping its id, and therefore its notes |
267
+ | `/cron cost [today\|7d\|all]` | What automation has cost, against `[limits] daily_budget_usd` |
268
+ | `/cron scheduler` | Which pi process currently owns the timer |
269
+ | `/cron host [start\|stop]` | The headless host that keeps the clock after the last pi quits |
270
+ | `/cron clear <ref>` | Release a run marker left behind by a process that is gone |
271
+ | `/cron gc` | Remove plain jobs whose session was deleted (they are parked as disabled first) |
272
+ | `/cron panel on\|off` | The side panel above the editor: Triggers, Inbox, Cron, MCP |
273
+ | `/cron snapshot` | Write what only this process knows — connected MCP servers and their tools, active tools, hooks, who owns the clock — into the session as a `pi_loops_snapshot` entry, for a front end that is not a terminal |
274
+ | `/inbox [list\|all\|claim <n>\|dismiss <n>\|clear] [--all]` | Triage findings from stateful loops. This project's by default, `--all` for every project — the same scoping `/cron` and `/triggers` use |
275
+ | `/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 |
276
+ | `/new-trigger <natural language>` | Create a condition-based rule ("when ~/build.done exists, run cargo test") |
277
+ | `/triggers [status\|rules\|enable\|disable\|remove\|running\|audit [N]\|abort]` | Dynamic rules: what exists, what is running, what happened |
278
+ | `/triggers run <id>` | Check one rule now instead of waiting for its poll slot |
279
+ | `/triggers set <id> --model\|--thinking\|--timeout\|--host` | Change what a rule runs with — the settings that decide how an unattended action behaves |
280
+ | `/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 |
281
+ | `/triggers panel [on\|off]` | The same panel, toggled from the trigger side |
282
+ | `/session-export [path]`, `/session-import <path>` | Portable `.pisession` archive: transcript + jobs + rules + loop state |
283
+ | `/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)) |
284
+ | `/pi-loops [install-launcher]` | Version and paths; `install-launcher` puts the `pi-loops` command on your `PATH` |
285
+
286
+ A schedule is a 5-field cron expression, or one of `hourly` / `daily` / `weekly` (also `每天`), or
287
+ `every 30m`, `in 10m`, `at <ISO>`. The `@hourly` / `@daily` / `@weekly` / `@monthly` spellings work
288
+ too, and they are not the same thing: `daily` is 09:00, `@daily` is midnight. All of it runs on this
289
+ machine's clock ([which clock, and what the twice-yearly change does to
290
+ it](docs/loops.md#time-and-which-clock-it-is)). A `<ref>` is the number on screen, the job id, an
291
+ unambiguous prefix of it, or the `--name` you gave it — and `/crontab` and `/loop` are aliases of
292
+ `/cron`.
293
+
294
+ Tools for the model: `cron_create`, `cron_list`, `cron_remove`, `set_cron_job_state`,
295
+ `new_trigger`, `list_triggers`, `remove_trigger`, `set_trigger_state`, plus every tool of every
296
+ configured MCP server. Five of them stop and ask you: creating or removing a trigger, removing a
297
+ cron job — `cron_remove` twice over, a preview the model must show you before the approval itself —
298
+ and any state change that turns automation *on*, or touches another project's. They are the
299
+ operations that decide what runs while nobody is watching.
300
+
301
+ ## Documentation
302
+
303
+ Loops and the inbox are the centre of this. On the same clock there are also plain cron jobs that
304
+ inject a prompt into this chat, triggers that wait for a condition instead of a time, MCP push
305
+ notifications, and lifecycle hooks — each has a page here.
306
+
307
+ - [docs/loops.md](docs/loops.md) — cron jobs, stateful loops, the inbox, maker/checker
308
+ - [docs/triggers.md](docs/triggers.md) — dynamic triggers and the trigger runtime
309
+ - [docs/mcp.md](docs/mcp.md) — MCP notification sources and tool registration (`mcp.toml`)
310
+ - [docs/hooks.md](docs/hooks.md) — lifecycle hooks (`hooks.toml`), including `run_start` / `run_end` for scheduled runs
311
+ - [docs/goal.md](docs/goal.md) — `/goal`: holding a session to a stop condition
312
+ - [docs/session-archive.md](docs/session-archive.md) — `/session-export`, `/session-import`
313
+ - [docs/cli.md](docs/cli.md) — the `pi-loops` command line: export, import, and looking in on the host
314
+ - [docs/web-ui-parity.md](docs/web-ui-parity.md) — what the browser front end owes you, line by line
315
+ - [docs/configuration.md](docs/configuration.md) — paths, `config.toml`, flags, environment
316
+ - [docs/design.md](docs/design.md) — architecture: what each piece is built out of, and the decisions behind it
317
+ - [docs/troubleshooting.md](docs/troubleshooting.md) — symptom first: nothing fired, a run failed, findings never arrived, the window stopped answering
318
+ - [examples/](examples/README.md) — a dependency-free MCP push server to try notifications with
319
+ - [CHANGELOG.md](CHANGELOG.md), [AGENTS.md](AGENTS.md) for contributors
320
+
321
+ ## Where things live
322
+
323
+ | Path | What |
324
+ |---|---|
325
+ | `~/.pi/agent/loops/jobs.json` | cron jobs (machine-global, each with its `cwd`) |
326
+ | `~/.pi/agent/loops/state/<id>.md` | loop notes — plain Markdown |
327
+ | `~/.pi/agent/loops/inbox.jsonl` | the inbox |
328
+ | `~/.pi/agent/loops/runs.jsonl`, `sessions/<id>/` | run log and full sub-agent transcripts |
329
+ | `~/.pi/agent/loops/logs/pi-<pid>.log` | what each pi process's automation did |
330
+ | `~/.pi/agent/loops/triggers.json`, `triggers-audit.jsonl` | dynamic rules and trigger audit |
331
+ | `~/.pi/agent/loops/{config,mcp,hooks}.toml` | configuration |
332
+ | `~/.pi/agent/loops/scheduler.<host>.json` | which pi process currently owns the timer |
333
+
334
+ Set `PI_LOOPS_DIR` to relocate all of it. The notes are yours to edit when the agent got something
335
+ wrong, and `logs/pi-<pid>.log` is the file to read after an overnight failure.
336
+
337
+ ## Automation outlives the window it was set up in
338
+
339
+ Scheduled work is only worth trusting if it survives you closing the editor. So "pi was restarted"
340
+ is treated here as the normal case rather than the exception.
341
+
342
+ Jobs are machine-global, recorded per host, and never expire. Any open pi can own the timer:
343
+ leadership is a file with a heartbeat, and when the process holding it exits or dies, the next tick
344
+ in another window picks it up. A project's trigger checks run in a pi that is open in that project,
345
+ so a result that belongs in a conversation lands in the right one. A tick a loop missed while
346
+ nothing was running is caught up once, collapsed rather than replayed; a plain job's is not, because
347
+ its prompt was written for a conversation that is gone (`--catchup` and `--no-catchup` override
348
+ either way).
349
+
350
+ Sub-agents are sessions opened inside the interactive pi through its SDK, not child processes: they
351
+ share its live MCP servers — the browser tab that is already logged in, the database session that is
352
+ already open — along with its extensions, model and thinking level. The headless host has no parent
353
+ session to share, so its runs connect their own MCP clients from `mcp.toml`: a server that was live
354
+ only because you had a window open is not live at 3am.
355
+
356
+ [docs/design.md](docs/design.md) has the architecture and the reasoning behind each of these.
357
+
358
+ ## Non-invasive by construction
359
+
360
+ Only pi's public extension API is used. `find <pi install> -newer package.json` is empty after
361
+ installing pi-loops; `~/.pi/agent` gains one `packages` entry and the `loops/` directory.
362
+ Uninstalling is `pi remove`.
363
+
364
+ ## Acknowledgements
365
+
366
+ Inspired by, and rewritten from, [pie](https://github.com/c4pt0r/pie).
367
+
368
+ ## License
369
+
370
+ MIT