@alphacoder-v0/pi-loops 0.19.3 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +164 -0
- package/CONTRACT.md +139 -0
- package/README.md +12 -7
- package/README.zh-CN.md +6 -4
- package/docs/cli.md +19 -1
- package/docs/downstream.md +60 -0
- package/docs/goal.md +4 -2
- package/docs/loops.md +55 -9
- package/docs/recipes.md +140 -18
- package/docs/session-archive.md +8 -3
- package/docs/web-ui-parity.md +11 -1
- package/package.json +6 -2
- package/recipes/_tracker/issue-tracker-local.md +3 -1
- package/recipes/autoresearch/experiment.md +30 -6
- package/recipes/autoresearch/recipe.toml +2 -0
- package/recipes/changelog-draft/recipe.toml +6 -1
- package/recipes/changelog-draft/release.md +25 -5
- package/recipes/ci-sweeper/README.md +9 -0
- package/recipes/ci-sweeper/recipe.toml +16 -0
- package/recipes/ci-sweeper/sweep.md +71 -0
- package/recipes/daily-digest/README.md +10 -0
- package/recipes/daily-digest/digest.md +65 -0
- package/recipes/daily-digest/recipe.toml +14 -0
- package/recipes/deps-sweeper/README.md +11 -0
- package/recipes/deps-sweeper/audit.md +99 -0
- package/recipes/deps-sweeper/recipe.toml +17 -0
- package/recipes/ecosystem/README.md +9 -0
- package/recipes/ecosystem/recipe.toml +15 -0
- package/recipes/ecosystem/scan.md +59 -0
- package/recipes/issue-loop/implement.md +17 -4
- package/recipes/issue-loop/labels.sh +18 -0
- package/recipes/issue-loop/recipe.toml +2 -0
- package/recipes/issue-loop/triage.md +16 -2
- package/recipes/pr-watch/README.md +9 -0
- package/recipes/pr-watch/recipe.toml +15 -0
- package/recipes/pr-watch/watch.md +70 -0
- package/skills/pi-loops/SKILL.md +3 -2
- package/src/archive.ts +97 -2
- package/src/cli.ts +103 -16
- package/src/goal.ts +11 -0
- package/src/hooks.ts +25 -1
- package/src/host-runtime.ts +4 -8
- package/src/inbox.ts +118 -55
- package/src/job-signal.ts +78 -0
- package/src/pi-loops.ts +177 -109
- package/src/presence.ts +5 -0
- package/src/protocol.ts +45 -2
- package/src/recipe.ts +158 -2
- package/src/scheduler.ts +6 -2
- package/src/store.ts +8 -3
- package/src/tools.ts +17 -11
- package/src/triggers.ts +8 -3
- package/src/web.mjs +73 -7
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,170 @@
|
|
|
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
|
+
## [Unreleased]
|
|
7
|
+
|
|
8
|
+
## [0.22.0] - 2026-09-16
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **One shape for a finding.** A line of `inbox.jsonl` is now the very object `pi-loops inbox
|
|
12
|
+
--json` prints — the ten fields of [docs/downstream.md](docs/downstream.md) §3, in their order,
|
|
13
|
+
under their names — instead of a third spelling of the same record (`trace_id` for the run id,
|
|
14
|
+
camelCase in memory, keys left out when they were empty). The entry in memory has those names
|
|
15
|
+
too, so both translations are a copy and a subset: nothing is renamed on the way in or out, and
|
|
16
|
+
a field that has to be added is added in one place. Lines written by earlier versions are still
|
|
17
|
+
read, and rewritten in the new shape the next time a claim or a dismiss touches them. `kind` is
|
|
18
|
+
now on every finding (`news` when the text is not a checkpoint) rather than only on checkpoints.
|
|
19
|
+
Keeping pi-loops' own fields — `job_id`, `session_id`, `claimed_by`, `verified_reason`,
|
|
20
|
+
`dismissed_at` — on the same line after the ten is this version's choice, not something the
|
|
21
|
+
interface asks for: they are private state and a program that reads them is reading what it was
|
|
22
|
+
told not to.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
- **The contract is written once.** `CONTRACT.md` §2 fixes what a program may depend on — the
|
|
26
|
+
instance, the shapes of a recipe, a playbook, a finding and a run event, the permitted actions,
|
|
27
|
+
the three interfaces — and [docs/downstream.md](docs/downstream.md) is now the same three in
|
|
28
|
+
use: an example of each and a pointer to the rule, instead of a second wording of it. The page
|
|
29
|
+
ships with the package alongside `CONTRACT.md`, and `test/downstream-doc.test.ts` reads both:
|
|
30
|
+
every name §2.2 fixes exists in the source, and the page's JSON example is `FINDING_FIELDS`
|
|
31
|
+
exactly.
|
|
32
|
+
- **A run's two hook events are built once.** `run_start` and `run_end` were assembled twice,
|
|
33
|
+
near-verbatim, in the interactive extension and in the headless host — so which process held the
|
|
34
|
+
clock could change what a rule saw, and nothing but a comment said it should not. Both now call
|
|
35
|
+
`runStartEvent` / `runEndEvent` in `src/hooks.ts`, and the `run_*` fields
|
|
36
|
+
[docs/downstream.md](docs/downstream.md) §2 names are pinned by a test there.
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
- **A number is refused where no list was printed, by the resolver itself.** `pi-loops inbox claim 1`
|
|
40
|
+
was refused by one line in the CLI while the resolver underneath still resolved `1` to the first
|
|
41
|
+
finding, and the model-facing tools guarded jobs and rules the same way, each on its own.
|
|
42
|
+
`resolveInboxRef`, `resolveJobRef` and `resolveRuleRef` now resolve an all-digit reference to
|
|
43
|
+
nothing unless the caller says `ordinals: true`, which only `/inbox`, `/cron` and `/triggers`
|
|
44
|
+
do — they number what they show. The CLI and the tools no longer carry their own check, and the
|
|
45
|
+
messages a person sees are unchanged.
|
|
46
|
+
- **daily-digest reads nothing under the loops directory.** Its playbook told a run to read
|
|
47
|
+
`runs.jsonl` and `jobs.json`, two files [docs/downstream.md](docs/downstream.md) says change
|
|
48
|
+
without notice; it now asks the `cron_list` tool, which lists each job's `last_error` and, new,
|
|
49
|
+
its `consecutive_failures`, and `pi-loops inbox list --json` for the findings still waiting. The
|
|
50
|
+
contract check that installs every packaged recipe and searches its files for the names of the
|
|
51
|
+
private files (`test/contract/1-second-implementation.sh`) is what caught it, and what keeps the
|
|
52
|
+
next recipe from doing the same.
|
|
53
|
+
- **One archive, one set of answers.** `/session-export` and `pi-loops export`, and
|
|
54
|
+
`/session-import` and `pi-loops import`, decided the same three things twice and had drifted
|
|
55
|
+
apart on all three; each decision now lives once, in `src/archive.ts`. What changes:
|
|
56
|
+
`pi-loops export` follows the session that created a job or rule, the way the slash command
|
|
57
|
+
always has, instead of archiving every loop in the project — including a colleague's, and
|
|
58
|
+
including none of your own reached through a worktree or a symlinked path, which its
|
|
59
|
+
string comparison of directories missed. `/session-import` takes `--activate-triggers=ask` and
|
|
60
|
+
the space-separated `--activate-triggers ask`, both of which it used to refuse. `pi-loops import`
|
|
61
|
+
defaults to `ask` like the slash command (it defaulted to `off`), asks after the import rather
|
|
62
|
+
than before it, with the counts, and rolls a half-written import back — a failed store write
|
|
63
|
+
used to leave the jobs in, the loop state on disk, and a session file nothing pointed at.
|
|
64
|
+
|
|
65
|
+
## [0.21.0] - 2026-09-16
|
|
66
|
+
|
|
67
|
+
### Added
|
|
68
|
+
- **What a program may depend on.** [docs/downstream.md](docs/downstream.md) is the whole list of
|
|
69
|
+
what a program that is not pi-loops may rely on: a recipe directory, `run_start` / `run_end` in
|
|
70
|
+
`hooks.toml` with their `PI_RUN_*` variables, and `pi-loops inbox list | claim <id> | dismiss <id>
|
|
71
|
+
[--reason <text>] --json` — a new command that does what `/inbox` does with no pi open — and it
|
|
72
|
+
says that nothing else is one: no file under `~/.pi/agent/loops`, no slash command's wording,
|
|
73
|
+
not the browser page. The list is small on purpose: every name on it is a promise never to
|
|
74
|
+
rename or remove, so it holds only what a closed loop needs. That the list is *enough* is
|
|
75
|
+
proved rather than asserted: `test/downstream/fixture-recipe/` is a recipe that uses only it —
|
|
76
|
+
installed from its directory, its run commits a file and files a checkpoint, and its `run_end`
|
|
77
|
+
hook finds that finding by `PI_RUN_ID` and claims it — and `test/downstream/closed-loop.sh`
|
|
78
|
+
checks the file, the commit and the claim from the outside with `sh` and `jq`, importing none
|
|
79
|
+
of this package's code; the model is a loopback stand-in (`npm run check:downstream`, part of
|
|
80
|
+
`npm run ci`). The third item was added for that proof: with only the first two, a hook could
|
|
81
|
+
count a run's findings but neither read nor mark one. A test keeps the page honest the other
|
|
82
|
+
way: every `PI_RUN_*` variable, manifest key and finding field it names must exist in `src/`.
|
|
83
|
+
- **A dismiss can say why, and the loop hears it.** `/inbox dismiss <n> <reason>` keeps the
|
|
84
|
+
reason on the entry (`dismiss_reason`, with `dismissed_at`) and puts it in front of the next run
|
|
85
|
+
of the loop that reported the finding, in a `[dismissed]` block between its notes and its task:
|
|
86
|
+
do not report this again unless what it describes has changed, and carry into your notes what
|
|
87
|
+
you need to remember that. Shown to that one run only — the notes are the loop's only memory,
|
|
88
|
+
and a channel the notes cap does not bound would be one it could not edit. Up to eight per run,
|
|
89
|
+
newest kept. A bare dismiss and `/inbox clear` stay silent as before; `/inbox all` shows the
|
|
90
|
+
reason on the dismissed line. Until now the only answer to a finding was to claim it or to say
|
|
91
|
+
nothing, and a loop that misjudged what counts as a finding went on misjudging it every night.
|
|
92
|
+
- **`deps-sweeper` recipe.** Mondays at eight: the project's audit and outdated commands, and one
|
|
93
|
+
finding per advisory or major-version gap not already in its notes. Under `propose`, patch and
|
|
94
|
+
minor updates are applied in a worktree, the project's check command run, and a pull request
|
|
95
|
+
opened — one per run, never a major, never a package on the playbook's deny list, and the same
|
|
96
|
+
package attempted twice without a green check is a stop. Eight recipes now ship.
|
|
97
|
+
- **Was it worth running.** `/cron` shows, per loop, the last thirty days of findings filed and
|
|
98
|
+
what a person did with them (`30d: 6 findings · 6 dismissed (4 with a reason)`), and marks
|
|
99
|
+
`[quiet ×N]` once the N newest runs found nothing. `/cron cost` puts the same counts beside each
|
|
100
|
+
job's spend. `cron_list` gives the model both (`signal_30d`, `quiet_streak`). Computed from the
|
|
101
|
+
run log and the inbox (`src/job-signal.ts`); nothing is stored and no schedule is changed — a
|
|
102
|
+
quiet watch is supposed to be quiet, and the number is what to read before making it hourly.
|
|
103
|
+
- **Every checkpoint says three things.** A finding that asks a person to decide now carries, in
|
|
104
|
+
every packaged playbook, the decision, what waits on it, and what happens if nobody acts
|
|
105
|
+
(`… · waits: your merge · if not: the branch stays; later runs build on it`). docs/recipes.md
|
|
106
|
+
has the shape. News — a merged pull request, a red check — stays as it was.
|
|
107
|
+
- **autoresearch looks back.** Five retired experiments in a row since the last kept one is a
|
|
108
|
+
finding asking whether the contract is wrong, and the loop stays out of the idea classes it has
|
|
109
|
+
exhausted; ten in a row stops it until `RESEARCH.md` changes. Before this an hourly loop in the
|
|
110
|
+
wrong search space ran until the budget cap.
|
|
111
|
+
- **What a recipe may never do, before it is installed.** `/recipe show` and the install
|
|
112
|
+
confirmation print each playbook's `## Never` section — the safety envelope of an unattended
|
|
113
|
+
run, which until now was read after the install, if at all — with a `tier` line and the
|
|
114
|
+
situations the recipe is for (`useful_when` in the manifest). `/recipe list` shows the eight in
|
|
115
|
+
two groups: three starters that only read and file findings (`daily-digest`, `pr-watch`,
|
|
116
|
+
`changelog-draft`), and the advanced ones that write to a worktree, a tracker or a pull request.
|
|
117
|
+
The four playbooks that kept their prohibitions in the body (`triage`, `implement`, `release`,
|
|
118
|
+
`experiment`) gain the section, with nothing new in it; `/recipe update` carries it into
|
|
119
|
+
installed copies.
|
|
120
|
+
- **A recipe is checked against the project before it is installed.** The confirmation ends
|
|
121
|
+
with *Before the first run*: whether `gh` is there and logged in, whether there is an `origin`,
|
|
122
|
+
a CI workflow, a lockfile, whether the tracker is GitHub — each a line with ✓ or ✗ and what the
|
|
123
|
+
miss means for the runs. Nothing blocks the install. A manifest names its checks (`needs`,
|
|
124
|
+
`needs_propose`); a recipe that reads the tracker is checked for `gh` only when the tracker
|
|
125
|
+
description uses it, so a local Markdown tracker is asked for nothing. `/recipe show` prints
|
|
126
|
+
the same lines. Until now the first sign of a missing login was an empty inbox the next morning.
|
|
127
|
+
- **The inbox tells a decision from news.** A finding in the checkpoint shape
|
|
128
|
+
(` · waits: … · if not: …`) is stored with `kind: "checkpoint"` when the run's findings are
|
|
129
|
+
appended. `/inbox` lists checkpoints first, marks them `⚑`, and says `3 new, 1 needs a decision`
|
|
130
|
+
in its header; the footer badge and the browser panel say `(1 decision)`. Claiming one tells the
|
|
131
|
+
turn that the claim is the person's approval of the decision the finding recommends, so it is
|
|
132
|
+
carried out rather than investigated again. Entries written before this read as news.
|
|
133
|
+
- **Esc pauses the goal.** Stopping a turn with Esc while a `/goal` is pursued pauses the goal —
|
|
134
|
+
appended to the session, so `--resume` finds it paused — until `/goal resume`. Until now the
|
|
135
|
+
aborted turn was merely not judged, and the next message ended with the evaluator sending the
|
|
136
|
+
agent back to work. Esc during an evaluation stopped it before and still does.
|
|
137
|
+
|
|
138
|
+
### Fixed
|
|
139
|
+
- **issue-loop installs on a local Markdown tracker.** Its setup script ran `gh label create`
|
|
140
|
+
whatever the tracker was, so a project with `.scratch/issues/` and no `gh` stopped at "setup
|
|
141
|
+
script failed; no jobs were created". The script now reads `docs/agents/issue-tracker.md`: a
|
|
142
|
+
local tracker has no labels to create and it says so; a GitHub tracker without `gh`, or with
|
|
143
|
+
`gh` not logged in, is told what to run by hand and the install goes on — the first run reports
|
|
144
|
+
what it cannot do. The local tracker description gains the two operations it never named:
|
|
145
|
+
requesting changes (a comment and the status back to `agent-working`) and merging (`git merge`,
|
|
146
|
+
status `done`, file to `closed/`), and the implement playbook reads review feedback from the
|
|
147
|
+
item's comments where there is no pull request.
|
|
148
|
+
|
|
149
|
+
## [0.20.0] - 2026-09-14
|
|
150
|
+
|
|
151
|
+
### Added
|
|
152
|
+
- **Five more recipes.** `daily-digest` files one finding a morning saying what needs a look —
|
|
153
|
+
open items, a red default branch, a loop that failed its last two runs — or none on a quiet day.
|
|
154
|
+
`pr-watch` gives every open pull request one state and reports only what changed, or a stall
|
|
155
|
+
that lasted another day; under `propose` it may leave one comment per stall. `ci-sweeper` turns
|
|
156
|
+
a red default branch into a finding, or under `propose` a fix in a worktree and a pull request,
|
|
157
|
+
with the failure signature as memory and a stop after two failed attempts. `ecosystem` finds who
|
|
158
|
+
uses or forks the project and drafts the one reply or invitation worth sending, every draft a
|
|
159
|
+
finding a person sends by claiming it, its only level `propose`. `changelog-draft` gains
|
|
160
|
+
`propose`: the entry and the version bump in a release pull request. None of these touch
|
|
161
|
+
`src/`; `test/recipe.test.ts` holds every packaged manifest and playbook to the same rules.
|
|
162
|
+
|
|
163
|
+
### Fixed
|
|
164
|
+
- `@earendil-works/pi-ai` is listed in `peerDependencies`: two files import a type from it, and
|
|
165
|
+
pi's packaging rules ask that every bundled package imported be listed.
|
|
166
|
+
- The "escape hatch" front-end test read the fake pi's log unconditionally and failed on runs
|
|
167
|
+
where nothing had reached pi yet — which is the fact it was asserting. A missing log now reads
|
|
168
|
+
as empty.
|
|
169
|
+
|
|
6
170
|
## [0.19.3] - 2026-09-14
|
|
7
171
|
|
|
8
172
|
### Fixed
|
package/CONTRACT.md
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# pi-loops — CONTRACT
|
|
2
|
+
|
|
3
|
+
Version 0.22 · 2026-09-16 · Status: draft · Section 2 frozen at 29 lines
|
|
4
|
+
|
|
5
|
+
This file does one thing. It says which things never change (the contract), which things can be
|
|
6
|
+
replaced at any time (the mechanisms), and how to prove that a mechanism has not quietly become
|
|
7
|
+
part of the contract (the refinement checks). Section 2 says what the project is. Every other
|
|
8
|
+
section can be deleted or rewritten without changing the project.
|
|
9
|
+
|
|
10
|
+
## 0. One sentence
|
|
11
|
+
|
|
12
|
+
The loops directory on one machine holds jobs, findings and each loop's notes. pi-loops runs the
|
|
13
|
+
jobs on a clock, queues the findings, and emits an event before and after each run. A program
|
|
14
|
+
talks to it only through a recipe directory, `hooks.toml`, and the `pi-loops inbox` command.
|
|
15
|
+
|
|
16
|
+
## 1. Terms
|
|
17
|
+
|
|
18
|
+
- **Loops directory**: the directory pi-loops reads and writes on one machine (default
|
|
19
|
+
`~/.pi/agent/loops`, overridden by `PI_LOOPS_DIR`).
|
|
20
|
+
- **Project**: the directory a job runs in (`cwd`).
|
|
21
|
+
- **Job**: one schedule and one prompt, belonging to one project.
|
|
22
|
+
- **Run**: one execution of a job, with one `run_id`.
|
|
23
|
+
- **Finding**: one line a run leaves for a person, with a status `new`, `claimed` or `dismissed`.
|
|
24
|
+
- **Notes**: the Markdown a loop's run leaves for its next run.
|
|
25
|
+
- **Recipe**: a directory: `recipe.toml` and the playbooks it names.
|
|
26
|
+
- **Playbook**: the Markdown a run reads, copied into the project at install.
|
|
27
|
+
- **Level**: the line `Autonomy: report | propose | act` in a playbook.
|
|
28
|
+
- **Event**: the payload a `hooks.toml` rule sees when a run starts and when it ends.
|
|
29
|
+
|
|
30
|
+
## 2. Contract (MUST)
|
|
31
|
+
|
|
32
|
+
### 2.1 Instance
|
|
33
|
+
|
|
34
|
+
- Instance data in the loops directory: the jobs, the trigger rules, the findings, the notes, the run records (including each run's transcript), `hooks.toml`, `config.toml`, `mcp.toml`. Every other file and directory there is private state and can be deleted at any time.
|
|
35
|
+
- Instance data in a project: the playbooks and recipe files installed under `.agents/skills/<recipe>/`, and, when a recipe declares `needs_tracker`, `docs/agents/issue-tracker.md`.
|
|
36
|
+
- Tool code does not enter an instance. Instance data does not enter the tool.
|
|
37
|
+
|
|
38
|
+
### 2.2 Unit formats
|
|
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`.
|
|
41
|
+
- **Playbook**: Markdown with pi's skill frontmatter (`name`, `description`), one level line, and one `## Never` section.
|
|
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
|
+
- **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`.
|
|
44
|
+
- **Rule**: a `[[hook]]` in `hooks.toml` with `event` and either `command` or `webhook`.
|
|
45
|
+
|
|
46
|
+
### 2.3 Permitted actions
|
|
47
|
+
|
|
48
|
+
- **Install a recipe**: copy the playbooks into the project with the level line written, and create the jobs the manifest declares. A manifest that breaks a rule is refused whole; no file is copied.
|
|
49
|
+
- **Run**: emit one `run_start` and one `run_end` with the same `run_id`. The run's findings enter the queue with `status = new` and `run_id` = that run.
|
|
50
|
+
- **Claim** and **dismiss**: take a finding's `id` or a unique prefix of it, and succeed once each, only on a `new` finding. Dismiss can carry a `reason`, written to `dismiss_reason`.
|
|
51
|
+
|
|
52
|
+
### 2.4 Ledger
|
|
53
|
+
|
|
54
|
+
The events are the ledger of runs: each run has exactly one `run_start` / `run_end` pair. A finding's `status` is its own ledger: `new → claimed` or `new → dismissed`, never back.
|
|
55
|
+
|
|
56
|
+
### 2.5 Write conditions
|
|
57
|
+
|
|
58
|
+
A finding is written only by a run, and its `text` is what the run said. `status` changes only through claim or dismiss. A rule that fails or hangs does not change the run's result. An install is complete or does not happen.
|
|
59
|
+
|
|
60
|
+
### 2.6 Deterministic rules
|
|
61
|
+
|
|
62
|
+
- `kind`: `text` contains `· waits:` (preceded by the start of the text or whitespace, case-insensitive) → `checkpoint`; otherwise `news`.
|
|
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
|
+
- Manifest validity: the required keys and values of 2.2. TOML is parsed as standard TOML.
|
|
65
|
+
|
|
66
|
+
### 2.7 Downstream interface
|
|
67
|
+
|
|
68
|
+
1. A recipe is a directory. `/recipe add <dir>` and `pi-loops recipe add <dir>` install it.
|
|
69
|
+
2. A `hooks.toml` rule with `event = "run_start" | "run_end"` receives the event of 2.2.
|
|
70
|
+
3. `pi-loops inbox list [--all] [--cwd <dir>] --json`, `inbox claim <id> --json`, `inbox dismiss <id> [--reason <text>] --json`: exit 0 and one JSON object (`{"findings": [...]}` or `{"finding": {...}}`); on failure exit 1 and `{"error": "<why>"}`.
|
|
71
|
+
|
|
72
|
+
Any file, field, path or command not listed above is not an interface.
|
|
73
|
+
|
|
74
|
+
### 2.8 Boundary
|
|
75
|
+
|
|
76
|
+
That is all. This section fits on one screen.
|
|
77
|
+
|
|
78
|
+
## 3. Mechanisms (SHOULD, replaceable)
|
|
79
|
+
|
|
80
|
+
Three sentences each: what it is, what it depends on, and why the contract does not move when it is replaced.
|
|
81
|
+
|
|
82
|
+
- **Findings are stored in `inbox.jsonl`, one object of 2.2 per line.** Depends on line-atomic appends. Another container keeps the object; `inbox` returns it as before.
|
|
83
|
+
- **An id is `inb-` and 32 hex characters; a run_id is `run-` and the same.** Depends on `randomBytes(16)`. The contract asks only that ids be unique and resolvable by prefix.
|
|
84
|
+
- **`created_at` carries the local UTC offset.** Depends on `stamp()` in `schedule.ts`. The contract asks only for RFC 3339.
|
|
85
|
+
- **`source` is `cron:<job name or id>`.** Depends on the scheduler. The contract asks only for a string.
|
|
86
|
+
- **`inbox list` puts checkpoints first, then news, each oldest first.** Depends on the sort in `inbox.ts`. The contract asks only for the right set.
|
|
87
|
+
- **A finding's `text` is at most 500 characters, a run files at most 16, notes are at most 2000 characters, a prompt at most 8 KB.** Depends on the model's context and the queue staying readable. Changing a number does not touch 2.2.
|
|
88
|
+
- **`inbox.jsonl` drops its oldest handled entries past 1 MB.** Depends on disk and list length. The contract only requires that `new` findings can be listed.
|
|
89
|
+
- **Jobs live in `jobs.json` (`version: 2`), notes in `state/<id>.md`, run records in `runs.jsonl`.** Depends on the current store. A job's full description is its `/cron add` line; reinstalling the recipe recreates it.
|
|
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
|
+
- **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
|
+
- **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 the host 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
|
+
- **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
|
+
- **A headless host runs the jobs while no pi is open.** Depends on the process hand-off. Events and findings have the same shape in both processes.
|
|
96
|
+
|
|
97
|
+
## 4. Not done
|
|
98
|
+
|
|
99
|
+
- No public format for `jobs.json`: a job is created by a recipe install or by a person in pi, and a reinstall recreates it (2.3 + mechanism "jobs live in `jobs.json`").
|
|
100
|
+
- No second on-disk shape for a finding (such as another tool's field names): a line is the object of 2.2, and another tool reads it as it is (2.2 + mechanism "`inbox.jsonl`").
|
|
101
|
+
- Playbooks do not read files in the loops directory: a run learns its own health from the `cron_list` tool and the findings from `pi-loops inbox` (2.7).
|
|
102
|
+
- `mcp.toml` is not a program interface: it is a person's configuration file (instance data in 2.1, not in 2.7).
|
|
103
|
+
- No command that creates a job from a terminal: `pi --mode rpc` already runs `/recipe add` without a terminal (mechanism "creating a job needs a pi").
|
|
104
|
+
- No multi-machine support: one loops directory belongs to one machine (design.md, decision 19).
|
|
105
|
+
|
|
106
|
+
## 5. Refinement checks
|
|
107
|
+
|
|
108
|
+
The scripts are in `test/contract/`. `sh test/contract/run.sh` runs all five; each also runs alone.
|
|
109
|
+
|
|
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 no host 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 the host and once 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 the host again. Pass: it runs the loop again and the earlier findings are unchanged field for field.
|
|
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
|
+
|
|
116
|
+
## 6. Mapping of requirements and features
|
|
117
|
+
|
|
118
|
+
| Requirement or feature | Where it lands |
|
|
119
|
+
|---|---|
|
|
120
|
+
| `docs/downstream.md` | examples of 2.2 / 2.3 / 2.7; the rule is here |
|
|
121
|
+
| `docs/downstream.md` §1, the recipe directory | Contract 2.2 / 2.7.1 |
|
|
122
|
+
| `docs/downstream.md` §2, the hook events | Contract 2.2 / 2.7.2 |
|
|
123
|
+
| `docs/downstream.md` §3, the inbox command and the finding object | Contract 2.2 / 2.3 / 2.7.3 |
|
|
124
|
+
| `inb-<32 hex>`, 500 characters and the list order in `docs/downstream.md` | Mechanism |
|
|
125
|
+
| `docs/recipes.md`, "Writing a recipe" | Contract 2.2; `.orig/`, `.recipe.json`, exclude, preflight names → mechanism |
|
|
126
|
+
| Timeouts, truncation and log paths in `docs/hooks.md` | Mechanism |
|
|
127
|
+
| The on-disk line shape (`trace_id`) that `docs/loops.md` used to document | Removed, unreleased: a line is the object of 2.2 (CHANGELOG, "One shape for a finding") |
|
|
128
|
+
| The four caps in `docs/design.md`, decision 11 | Mechanism |
|
|
129
|
+
| `docs/design.md`, decisions 15–18 (recipes are files, copied into the project, deterministic wizard, the level is a sentence) | Contract 2.1 / 2.2 / 2.3; details → mechanism |
|
|
130
|
+
| `recipes/daily-digest` reads `runs.jsonl` and `jobs.json` | Not done, item 3 |
|
|
131
|
+
| `examples/mcp.toml` | Not done, item 4 |
|
|
132
|
+
| `test/downstream/` | Part of refinement check 1 |
|
|
133
|
+
|
|
134
|
+
## 7. Revision rules
|
|
135
|
+
|
|
136
|
+
- The required fields and the actions of section 2 are only added, never changed or removed.
|
|
137
|
+
- No number derived from a mechanism appears in the contract.
|
|
138
|
+
- A mechanism paragraph can be replaced whole; replacing one only requires rerunning section 5.
|
|
139
|
+
- This file is part of the repository; a change to it goes through the ledger.
|
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.22.0 # 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
|
```
|
|
@@ -103,6 +103,7 @@ watching this repo lands in the same list and you triage it in one pass.
|
|
|
103
103
|
/cron run 1 # do not wait until 9am — run it now and watch
|
|
104
104
|
/inbox claim 1 # hand finding #1 to the agent as a real turn
|
|
105
105
|
/inbox dismiss 2 # not interesting
|
|
106
|
+
/inbox dismiss 3 that file is generated, ignore it # the loop is told why on its next run
|
|
106
107
|
```
|
|
107
108
|
|
|
108
109
|
## Loops worth stealing
|
|
@@ -138,10 +139,13 @@ A job records its directory and model at creation, so it is not tied to the wind
|
|
|
138
139
|
`--cwd` runs it in another checkout (absolute, or relative to this project — no shell, so nothing
|
|
139
140
|
expands `~`), and `--model` pins it whatever this session is on (`/cron set <ref> --model -` unpins).
|
|
140
141
|
|
|
141
|
-
And
|
|
142
|
-
that two loops turn, `/recipe add autoresearch` runs one experiment per run against a
|
|
143
|
-
|
|
144
|
-
|
|
142
|
+
And eight you do not write at all: `/recipe add issue-loop` runs the issue tracker as a state
|
|
143
|
+
machine that two loops turn, `/recipe add autoresearch` runs one experiment per run against a
|
|
144
|
+
contract you wrote, and `daily-digest`, `pr-watch`, `ci-sweeper`, `deps-sweeper`,
|
|
145
|
+
`changelog-draft` and `ecosystem` do what their names say — each a directory of playbooks copied
|
|
146
|
+
into the project, installed with one question and one confirmation, and `/recipe show` says
|
|
147
|
+
what its runs may never do before you say yes ([docs/recipes.md](docs/recipes.md)). Three are
|
|
148
|
+
starters that only read and file findings: `daily-digest`, `pr-watch`, `changelog-draft`.
|
|
145
149
|
|
|
146
150
|
Two that are not loops:
|
|
147
151
|
|
|
@@ -284,7 +288,7 @@ State stays in `~/.pi/agent/loops` until you delete it.
|
|
|
284
288
|
| `/cron gc` | Remove plain jobs whose session was deleted (they are parked as disabled first) |
|
|
285
289
|
| `/cron panel on\|off` | The side panel above the editor: Triggers, Inbox, Cron, MCP |
|
|
286
290
|
| `/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 |
|
|
287
|
-
| `/inbox [list\|all\|claim <n>\|dismiss <n
|
|
291
|
+
| `/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 |
|
|
288
292
|
| `/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 |
|
|
289
293
|
| `/new-trigger <natural language>` | Create a condition-based rule ("when ~/build.done exists, run cargo test") |
|
|
290
294
|
| `/triggers [status\|rules\|enable\|disable\|remove\|running\|audit [N]\|abort]` | Dynamic rules: what exists, what is running, what happened |
|
|
@@ -325,7 +329,8 @@ notifications, and lifecycle hooks — each has a page here.
|
|
|
325
329
|
- [docs/goal.md](docs/goal.md) — `/goal`: holding a session to a stop condition
|
|
326
330
|
- [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
|
|
327
331
|
- [docs/session-archive.md](docs/session-archive.md) — `/session-export`, `/session-import`
|
|
328
|
-
- [docs/cli.md](docs/cli.md) — the `pi-loops` command line: export, import, and looking in on the host
|
|
332
|
+
- [docs/cli.md](docs/cli.md) — the `pi-loops` command line: export, import, the inbox, and looking in on the host
|
|
333
|
+
- [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
|
|
329
334
|
- [docs/web-ui-parity.md](docs/web-ui-parity.md) — what the browser front end owes you, line by line
|
|
330
335
|
- [docs/configuration.md](docs/configuration.md) — paths, `config.toml`, flags, environment
|
|
331
336
|
- [docs/design.md](docs/design.md) — architecture: what each piece is built out of, and the decisions behind it
|
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.22.0 # 或者从 GitHub 装,钉住一个 tag
|
|
49
49
|
pi install /path/to/pi-loops # 或者本地检出;本仓库里就是 pi install .
|
|
50
50
|
```
|
|
51
51
|
|
|
@@ -130,6 +130,7 @@ claim with /inbox claim <n>, dismiss with /inbox dismiss <n>
|
|
|
130
130
|
/cron run 1 # 不用等到早上九点,现在就跑一次看看
|
|
131
131
|
/inbox claim 1 # 把第 1 条作为真实的一轮交给 agent
|
|
132
132
|
/inbox dismiss 2 # 不感兴趣
|
|
133
|
+
/inbox dismiss 3 那个文件是生成的,别管它 # 理由会放进这个 loop 下一次 run 的 prompt 里
|
|
133
134
|
```
|
|
134
135
|
|
|
135
136
|
### 5. 让它整夜跑之前
|
|
@@ -221,6 +222,7 @@ pi update --extensions # 对齐已安装的包
|
|
|
221
222
|
/recipe 有哪些打包好的 recipe,这个项目装了哪些
|
|
222
223
|
/recipe add issue-loop 把 issue tracker 跑成状态机:分诊成 agent brief、在 worktree 里实现、开 PR;推进和合并留给人
|
|
223
224
|
/recipe add autoresearch 按你写的研究合同每次跑一个实验,账本记下每次尝试,只凭 held-out 数据提议晋升
|
|
225
|
+
/recipe add deps-sweeper 每周一跑 audit 和 outdated:新的安全通告和落后一个大版本的依赖各报一条;propose 级在 worktree 里做小版本升级并开 PR
|
|
224
226
|
```
|
|
225
227
|
|
|
226
228
|
一个 recipe 是一组 loop 加上它们各自每次 run 都会读的 playbook。安装只问一个问题(自治级别
|
|
@@ -263,7 +265,7 @@ playbook 拷到 `.agents/skills/<name>/`,通过 `.git/info/exclude` 排除在
|
|
|
263
265
|
|
|
264
266
|
/inbox 本项目的新 findings(每行标出项目;`--all` 看全部项目,与 /cron、/triggers 同一套作用域)
|
|
265
267
|
/inbox claim 1 标记 claimed,并把它作为一个真实 user turn 交给主会话的 agent
|
|
266
|
-
/inbox dismiss 2 · /inbox clear · /inbox all
|
|
268
|
+
/inbox dismiss 2 [理由] · /inbox clear · /inbox all 写了理由,报这条的 loop 下次 run 会看到;不写则沉默
|
|
267
269
|
```
|
|
268
270
|
|
|
269
271
|
`/crontab` 和 `/loop` 是 `/cron` 的别名。
|
|
@@ -341,7 +343,7 @@ pi 内置的 `/export` 只导 HTML/JSONL 对话,`/import` 只导回对话—
|
|
|
341
343
|
|
|
342
344
|
```text
|
|
343
345
|
/session-export [path] [--exclude-triggers] 默认 ./pi-session-<id前16位>.pisession
|
|
344
|
-
/session-import <path> [--activate-triggers=on
|
|
346
|
+
/session-import <path> [--activate-triggers=off|ask|on] [--cwd <dir>] [--resume]
|
|
345
347
|
```
|
|
346
348
|
|
|
347
349
|
```text
|
|
@@ -356,7 +358,7 @@ backup.pisession 无压缩 ustar,0600,拒绝覆盖已有文
|
|
|
356
358
|
导入的时候会重写这些:
|
|
357
359
|
|
|
358
360
|
- session 换新 id,cwd 改成目标目录,header 里记下 `importedFrom` 来源。
|
|
359
|
-
- 任务和规则默认全部 disabled。`--activate-triggers
|
|
361
|
+
- 任务和规则默认全部 disabled。`--activate-triggers` 默认 `ask`:导入完之后弹一次确认,问你要不要把源会话里原本 enabled 的那些重新打开;`off` 不问,`on` 直接打开。
|
|
360
362
|
- 运行标记、错误、重叠计数清零。
|
|
361
363
|
- id 撞上本机已有的就重新生成,loop 状态文件跟着新 id 走。
|
|
362
364
|
- 非 stateful 任务重新绑定到导入的 session。
|
package/docs/cli.md
CHANGED
|
@@ -209,6 +209,8 @@ pi-loops inspect <file>
|
|
|
209
209
|
pi-loops export [--session <id>] [--cwd <dir>] [--output <file>] [--exclude-triggers]
|
|
210
210
|
pi-loops import <file> [--cwd <dir>] [--activate-triggers=off|ask|on]
|
|
211
211
|
pi-loops host status | abort <run-id|trace-id> | stop
|
|
212
|
+
pi-loops recipe list | show <name|path> | add <name|path> [--cwd <dir>] [--level report|propose|act] [--overwrite]
|
|
213
|
+
pi-loops inbox list [--all] [--cwd <dir>] | claim <id> | dismiss <id> [--reason <text>] [--json]
|
|
212
214
|
```
|
|
213
215
|
|
|
214
216
|
## sessions, inspect
|
|
@@ -239,7 +241,10 @@ pi-loops export --session 01a084a0 --output ~/backups/api.pisession
|
|
|
239
241
|
Restores an archive into `--cwd` (default: the current directory), rewriting ids, project and
|
|
240
242
|
machine so the automation runs where it landed.
|
|
241
243
|
|
|
242
|
-
|
|
244
|
+
`--activate-triggers=off|ask|on` decides what happens to the automation the archive carries, and
|
|
245
|
+
means the same thing here as in `/session-import`. The default is `ask`: the import leaves it
|
|
246
|
+
disabled and then asks once, where there is a terminal or a UI to ask in, whether to enable what
|
|
247
|
+
was enabled in the source. `off` never asks; `on` activates it as part of the import.
|
|
243
248
|
Importing the same archive twice adds nothing the second time.
|
|
244
249
|
|
|
245
250
|
## host
|
|
@@ -280,3 +285,16 @@ record — and stops. The setup script and the jobs need a pi open in the projec
|
|
|
280
285
|
them after you say yes. Without `--level` the lowest level the recipe supports is written; an edited
|
|
281
286
|
copy is kept unless `--overwrite` says otherwise.
|
|
282
287
|
|
|
288
|
+
## inbox
|
|
289
|
+
|
|
290
|
+
```sh
|
|
291
|
+
pi-loops inbox list --json # this project's new findings, checkpoints first
|
|
292
|
+
pi-loops inbox claim inb-3f2a… --json # mark it claimed; acting on it is yours
|
|
293
|
+
pi-loops inbox dismiss inb-3f2a… --reason "that file is generated" --json
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
`/inbox` with no pi open ([loops.md](loops.md#the-inbox)). `claim` and `dismiss` take an id or a
|
|
297
|
+
unique prefix of one and change the entry the way the slash command does; a claim here cannot start
|
|
298
|
+
a turn, so it marks the finding and leaves the acting to whoever asked. Without `--json` the same,
|
|
299
|
+
one line per finding, for a person. The JSON is fixed in [CONTRACT.md](../CONTRACT.md) §2.2 and shown in [downstream.md](downstream.md), which is
|
|
300
|
+
the page a program should be written against.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# What a program may depend on
|
|
2
|
+
|
|
3
|
+
Three things, and this page is the whole list. A program that is not pi-loops and does not import
|
|
4
|
+
its source may rely on these and on nothing else.
|
|
5
|
+
|
|
6
|
+
**Any file under `~/.pi/agent/loops/` is not an interface.** `jobs.json`, `inbox.jsonl`,
|
|
7
|
+
`runs.jsonl`, `state/`, `host.json`, the logs: private, and they change without notice. The one
|
|
8
|
+
file there a program writes is `hooks.toml`, and only for the two events below. The same goes for
|
|
9
|
+
every command, field and path this page does not name, the wording of the slash commands, and the
|
|
10
|
+
browser page.
|
|
11
|
+
|
|
12
|
+
The three are named in [CONTRACT.md](../CONTRACT.md) §2.7 and their shapes in §2.2 — that is where
|
|
13
|
+
the rule lives, and the text that moves when one of them changes. This page is the three in use.
|
|
14
|
+
|
|
15
|
+
## 1. A recipe is a directory
|
|
16
|
+
|
|
17
|
+
`recipe.toml` and the Markdown playbooks it names, side by side; `/recipe add ./dir` and
|
|
18
|
+
`pi-loops recipe add ./dir` install it from a local path. What the manifest may say — every key it
|
|
19
|
+
requires and every one it allows — is [CONTRACT.md](../CONTRACT.md) §2.2; what a playbook is, and
|
|
20
|
+
how one is written, is one section of [recipes.md](recipes.md#writing-a-recipe). A manifest that
|
|
21
|
+
breaks a rule is refused before anything is copied.
|
|
22
|
+
|
|
23
|
+
## 2. `run_start` and `run_end` in `hooks.toml`
|
|
24
|
+
|
|
25
|
+
A `[[hook]]` rule with `event = "run_start"` or `event = "run_end"` fires around every scheduled
|
|
26
|
+
run, in a pi window and in the headless host alike; what the payload carries is
|
|
27
|
+
[CONTRACT.md](../CONTRACT.md) §2.2, and the file, the rule keys and when the two fire are in
|
|
28
|
+
[hooks.md](hooks.md#exactly-when-hooks-fire). Each field reaches the rule's command as a `PI_*`
|
|
29
|
+
variable, unset where the payload has `null`, and all of them together as JSON in the file at
|
|
30
|
+
`PI_HOOK_PAYLOAD`; [hooks.md](hooks.md) shows a rule. A hook that fails or hangs never fails the run.
|
|
31
|
+
|
|
32
|
+
## 3. `pi-loops inbox list | claim <id> | dismiss <id> --json`
|
|
33
|
+
|
|
34
|
+
No pi needs to be open. Exit 0 and one JSON object on stdout; on failure exit 1 and `{"error": "<why>"}`.
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
pi-loops inbox list [--all] [--cwd <dir>] --json {"findings": [<finding>, …]}
|
|
38
|
+
pi-loops inbox claim <id> --json {"finding": <finding>} now "claimed"
|
|
39
|
+
pi-loops inbox dismiss <id> [--reason <text>] --json {"finding": <finding>} now "dismissed"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`list` is what `/inbox` shows: the new findings of the project at `--cwd` (default: the current
|
|
43
|
+
directory), checkpoints first, then news, each oldest first; `--all` is every project. `claim` and
|
|
44
|
+
`dismiss` take a finding's `id` or a unique prefix of it (never a number), among the new findings
|
|
45
|
+
machine-wide, and do to the entry what `/inbox claim` and `/inbox dismiss` do — the two actions of
|
|
46
|
+
[CONTRACT.md](../CONTRACT.md) §2.3: `claim` marks it and stops, since there is no session to hand
|
|
47
|
+
it to; a `--reason` is shown to the loop's next run. The object they print is the one of §2.2,
|
|
48
|
+
field for field:
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{"id": "inb-<32 hex>", "created_at": "2026-09-16T09:00:00.000+08:00", "status": "new",
|
|
52
|
+
"kind": "checkpoint", "source": "cron:nightly-audit", "run_id": "run-<32 hex>",
|
|
53
|
+
"cwd": "/path/to/project", "text": "one line, at most 500 characters", "verified": null, "dismiss_reason": null}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Fields may be added; none of these will be renamed, removed or change type.
|
|
57
|
+
|
|
58
|
+
Not listed here means private. `test/downstream/` is the proof this list is enough: a recipe that
|
|
59
|
+
uses only it, checked from the outside with `sh` and `jq`. `test/contract/` holds the checks of
|
|
60
|
+
CONTRACT.md itself.
|
package/docs/goal.md
CHANGED
|
@@ -31,10 +31,12 @@ is told to answer `{"ok": false, "reason": "insufficient evidence in transcript"
|
|
|
31
31
|
| Evaluator failure | Pauses with the reason; it never loops on an evaluator that cannot decide |
|
|
32
32
|
| Transcript | 40 000 characters, truncated from the front so the newest evidence always survives |
|
|
33
33
|
| Tools | None. The evaluator reads, it does not act |
|
|
34
|
-
| Interruption |
|
|
34
|
+
| Interruption | Esc on a turn pauses the goal — it stays paused across `--resume` until `/goal resume` — and Esc during an evaluation stops that evaluation the same way |
|
|
35
35
|
| Its own timeout | 2 minutes; it is one read, not a piece of work |
|
|
36
36
|
|
|
37
|
-
`/goal resume` after a budget limit starts the allowance again
|
|
37
|
+
`/goal resume` after a budget limit starts the allowance again, and after an Esc starts
|
|
38
|
+
evaluating again: stopping a turn is taken as stopping the goal, since the next message you sent
|
|
39
|
+
would otherwise end with the evaluator sending the agent back to work. `pause`, `resume` and `clear` are
|
|
38
40
|
matched as whole words, so `/goal clear the type errors first` sets that as the condition rather
|
|
39
41
|
than dropping the goal.
|
|
40
42
|
|