@dumbmice/pi-todo-loop 0.2.0 → 0.2.1
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/AGENTS.md +25 -7
- package/CHANGELOG.md +10 -0
- package/README.md +3 -3
- package/docs/behaviour.md +9 -5
- package/docs/pi-api-alignment.md +4 -1
- package/package.json +1 -1
- package/src/index.ts +7 -0
package/AGENTS.md
CHANGED
|
@@ -84,7 +84,11 @@ adding a `CHANGELOG.md` line — all in the same commit.
|
|
|
84
84
|
- **The only *variable* agent-facing text is the nudge.** One of two fixed sentences, appended once
|
|
85
85
|
per continuation. No state block, no reminder, no per-request rewrite: the list is the agent's to
|
|
86
86
|
read with the todo tool. The tool declaration (`todo_loop_block`) is constant and carries no task
|
|
87
|
-
state, but it is in every request — count it in cost claims
|
|
87
|
+
state, but it is in every request — count it in cost claims: the declaration plus the one-line
|
|
88
|
+
`<tools>` snippet and the `<rules>` guideline it earns (a few hundred characters together). It
|
|
89
|
+
declares a `promptSnippet` and a guideline that names it, because pi only shows a tool in the
|
|
90
|
+
prompt's `<tools>` list when a snippet exists; without one the exit is in the tool array but absent
|
|
91
|
+
from that summary. The non-assistant constraint is pi's `turn_end` projection check, not the request (register
|
|
88
92
|
item 3) — do not re-derive a coupling between the two.
|
|
89
93
|
- **One entry per continuation is a floor, not a choice.** pi has no `resume()` that appends nothing.
|
|
90
94
|
Do not try to reach zero entries; keep the single `custom_message` with `display: false`.
|
|
@@ -123,7 +127,9 @@ adding a `CHANGELOG.md` line — all in the same commit.
|
|
|
123
127
|
continues, but the loop would then stop silently — inert is better than loud.
|
|
124
128
|
- **Nothing is written.** No files, no network, no process execution. The only reads are the optional
|
|
125
129
|
config, the two kill-switch paths and the session branch.
|
|
126
|
-
- **The kill switch is checked in both hooks and in `todo_loop_block`'s `execute`.**
|
|
130
|
+
- **The kill switch is checked in both hooks and in `todo_loop_block`'s `execute`.** It cannot remove
|
|
131
|
+
the tool from the prompt: pi builds `<tools>`/`<rules>` from the registration, so a disabled session
|
|
132
|
+
still advertises an exit that will be refused. Disabling takes effect on the next request, without
|
|
127
133
|
`/reload`.
|
|
128
134
|
- **No pi type import; `pi: any` at the registration boundary.** Contexts the file reads are typed
|
|
129
135
|
structurally (`SessionContext`). Adding the pi types would make a dependency out of a package that
|
|
@@ -169,6 +175,7 @@ introduces an edge.
|
|
|
169
175
|
| the block's tool turn costs no round, and no marker is written after it | `state machine: the block call's tool turn costs no round, and the settle after it stops` |
|
|
170
176
|
| a model-authored reason is flattened and bounded | `a reason is flattened to one line and bounded` |
|
|
171
177
|
| the kill switch silences the block tool too | `the kill switch also silences todo_loop_block` |
|
|
178
|
+
| the block tool declares a prompt snippet and a self-naming guideline, so pi lists it in `<tools>`/`<rules>` | `the block tool declares a prompt snippet and a self-naming guideline` |
|
|
172
179
|
| a paused loop refuses the block tool and stays silent | `a paused loop refuses todo_loop_block and stays silent` |
|
|
173
180
|
| a stall clause that lands on the cap round is reported, not dropped | `state machine: cap stays shut across a repeated settle under the same message`, `a stale list stops on the cap with a reason that says so` |
|
|
174
181
|
| the next human message clears the block and the stale `last:` | `the human's next message clears the block` |
|
|
@@ -278,8 +285,10 @@ grep -c 'todo-loop:continue' ~/.pi/agent/sessions/<project>/*.jsonl # one per
|
|
|
278
285
|
|
|
279
286
|
## Release canary
|
|
280
287
|
|
|
281
|
-
|
|
282
|
-
or tightening `canContinue
|
|
288
|
+
Two failures this suite cannot see. The first is pi changing how a `display: false` `custom_message`
|
|
289
|
+
projects, or tightening `canContinue`; the second is how pi renders a custom tool into the system
|
|
290
|
+
prompt, since the harness stub never builds one — a tool without a `promptSnippet` was silently dropped
|
|
291
|
+
from `<tools>` in pi 1.5.0. Drafts are committed before the continuation is validated, so that change
|
|
283
292
|
would persist a marker, drop the continuation, and leave `/todoloop` reporting `continuing: round n/12`
|
|
284
293
|
until it reports `round cap` with zero continuations — while every test here stays green, because the
|
|
285
294
|
harness drives a stub `pi`.
|
|
@@ -287,12 +296,21 @@ harness drives a stub `pi`.
|
|
|
287
296
|
Before every version bump, on the *new* pi, run the documented live check once:
|
|
288
297
|
|
|
289
298
|
```bash
|
|
290
|
-
|
|
299
|
+
# `-ne` because the released copy registers the same tool name: `pi -e .` next to the installed
|
|
300
|
+
# version fails with `Tool "todo_loop_block" conflicts with …`.
|
|
301
|
+
pi -ne -e . -p "create two todos 'a' and 'b', then reply ok" --tools todo
|
|
291
302
|
grep -c 'todo-loop:continue' ~/.pi/agent/sessions/--<project>--/*.jsonl # 0 is a failure
|
|
292
303
|
```
|
|
293
304
|
|
|
294
|
-
|
|
295
|
-
|
|
305
|
+
For the prompt half, run with `--mode json` and confirm the tool reached the system prompt section:
|
|
306
|
+
|
|
307
|
+
```bash
|
|
308
|
+
pi -ne -e . --mode json -p "reply ok" | grep -o 'todo_loop_block: Stop the todo loop'
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
and read the pi changelog for `custom_message`, `display`, `canContinue`, `promptSnippet`,
|
|
312
|
+
`promptGuidelines`, plus rpiv-todo's for `reminder` (upstream PR #190 would put a second task block in
|
|
313
|
+
the request).
|
|
296
314
|
|
|
297
315
|
## Known limitations to revisit
|
|
298
316
|
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,16 @@ All notable changes to this project are documented here. The format follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project adheres to
|
|
5
5
|
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.2.1] - 2026-11-05
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- `todo_loop_block` now declares a `promptSnippet` and a `promptGuidelines` bullet, so pi lists it in the
|
|
12
|
+
system prompt's `<tools>` section and its guideline in `<rules>`. Before this the tool was in the
|
|
13
|
+
request's tool array but absent from the prompt summary, so an agent could only find it through the
|
|
14
|
+
nudge. The guideline also tells the model to pick a reasonable option and state its assumption when a
|
|
15
|
+
choice is its own, rather than asking a person.
|
|
16
|
+
|
|
7
17
|
## [0.2.0] - 2026-11-05
|
|
8
18
|
|
|
9
19
|
### Added
|
package/README.md
CHANGED
|
@@ -77,9 +77,9 @@ placeholders, and a list that is never updated looks the same as one that is fin
|
|
|
77
77
|
is the only thing that stops it, so the list has to mean something to you.
|
|
78
78
|
|
|
79
79
|
The extension never edits your prompt. The only *variable* text it gives the model is a one-line nudge —
|
|
80
|
-
*continue, and read or update the list with the todo tool* — plus one constant tool declaration
|
|
81
|
-
(`todo_loop_block
|
|
82
|
-
conversation stays yours.
|
|
80
|
+
*continue, and read or update the list with the todo tool* — plus one constant tool declaration and the
|
|
81
|
+
prompt entry pi derives from it (`todo_loop_block`; neither carries task state), so the list stays the
|
|
82
|
+
agent's to maintain and the conversation stays yours.
|
|
83
83
|
|
|
84
84
|
## Configuration
|
|
85
85
|
|
package/docs/behaviour.md
CHANGED
|
@@ -52,8 +52,10 @@ outside — and one of them must not be punished.
|
|
|
52
52
|
`todo_loop_block` with the reason."* It is offered **once per stall**; tool work re-arms it, so a
|
|
53
53
|
second, separate stall after real progress is a new question.
|
|
54
54
|
- A **tool turn** clears `stalled`. Work is progress even when the list was not touched.
|
|
55
|
-
- **`todo_loop_block`** is the agent's own exit. It
|
|
56
|
-
and
|
|
55
|
+
- **`todo_loop_block`** is the agent's own exit. It is advertised in the prompt from the first turn —
|
|
56
|
+
pi lists it in `<tools>` and carries its guideline in `<rules>`, because the tool declares a
|
|
57
|
+
`promptSnippet` — and the nudge is the timed offer, not the only way to learn it exists. It takes a
|
|
58
|
+
reason, stores `{reason, anchor}` in memory and stops continuations for that message. The human is notified, `/todoloop` shows
|
|
57
59
|
`waiting for you: <reason>`, and the human's next message is a new anchor, so the block clears itself
|
|
58
60
|
— there is no unblock call.
|
|
59
61
|
- A block with an empty reason is refused, and the loop keeps running: the reason is the whole point.
|
|
@@ -158,9 +160,11 @@ clamped so a typo cannot defeat the cap it configures: `maxRounds` ≤ 1000.
|
|
|
158
160
|
number of continuations × session size. `maxRounds` caps continuations, not turns — tool turns are
|
|
159
161
|
pi's own and unbounded, so a long tool-heavy stretch can be many turns for one round. That is the
|
|
160
162
|
dominant term.
|
|
161
|
-
- The extension adds one short marker per continuation (~40 tokens) plus one constant tool declaration
|
|
162
|
-
|
|
163
|
-
|
|
163
|
+
- The extension adds one short marker per continuation (~40 tokens), plus one constant tool declaration
|
|
164
|
+
and the prompt entries pi derives from it — `<tools>` line, `<rules>` guideline, description and
|
|
165
|
+
schema, roughly 700 characters together, in every request. None of it carries task state, and there
|
|
166
|
+
is no per-request block. Neither the snippet nor the guideline is gated by the kill switch or
|
|
167
|
+
`/todoloop pause`; only the hooks and the call itself are. Whatever the model needs it reads with the `todo` tool, and that result
|
|
164
168
|
is part of its own conversation rather than a parallel copy.
|
|
165
169
|
- `context` walks the branch and returns `undefined` unless a marker actually has to be removed, so on
|
|
166
170
|
a normal turn it costs one projection pass. `turn_end` reads the config, probes the kill switches and
|
package/docs/pi-api-alignment.md
CHANGED
|
@@ -71,7 +71,10 @@ full. Items that produce no signal in the transcript are marked _(silent)_.
|
|
|
71
71
|
9. **`sendUserMessage` and `Agent.prompt` are deliberately not used.** `sendUserMessage` would add a
|
|
72
72
|
visible user-role message per continuation and pollute the transcript; calling the agent directly
|
|
73
73
|
bypasses `AgentSession` lifecycle handling (busy state, queuing, retry, abort).
|
|
74
|
-
10. **The kill switch is checked in both hooks and in the block tool, not once at load.**
|
|
74
|
+
10. **The kill switch is checked in both hooks and in the block tool, not once at load.** It cannot
|
|
75
|
+
withdraw the tool's prompt entry: pi renders `<tools>`/`<rules>` from the registration, so a
|
|
76
|
+
disabled session still shows an exit whose call is then refused with "not running". Removing it
|
|
77
|
+
would need `pi.setActiveTools` from a hook — a new coupling we did not take for this. Removing it or creating the file
|
|
75
78
|
takes effect on the next request, without `/reload`.
|
|
76
79
|
11. **No dependency on `@earendil-works/pi-coding-agent`, and no peer declaration for it.** Nothing
|
|
77
80
|
here imports the package: the boundary is typed `pi: any` and contexts are typed structurally
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -127,6 +127,13 @@ export default function todoLoop(pi: any, options: TodoLoopOptions = {}): void {
|
|
|
127
127
|
label: "Block the todo loop",
|
|
128
128
|
description:
|
|
129
129
|
"Stop the automatic continuation loop for the rest of this message because you need a person: a decision, a review, a credential or information only the human has. The human is shown your reason and their next message resumes the loop. Use it only when you cannot proceed on the open tasks without them.",
|
|
130
|
+
// Without a snippet pi leaves the tool out of the prompt's `<tools>` summary; it is
|
|
131
|
+
// still in the request's tool array, but a model reading the prompt summary does not
|
|
132
|
+
// see the exit until the nudge names it. The exit must not be hidden that way.
|
|
133
|
+
promptSnippet: "Stop the todo loop for this message and ask the human for what you need",
|
|
134
|
+
promptGuidelines: [
|
|
135
|
+
"Use todo_loop_block when you cannot proceed on the open todo tasks without a person — a decision, a review, a credential or information only they have. If the choice is yours to make, pick the reasonable option and say what you assumed instead. Do not use it to end work you could finish yourself.",
|
|
136
|
+
],
|
|
130
137
|
parameters: {
|
|
131
138
|
type: "object",
|
|
132
139
|
properties: {
|