@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 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. The non-assistant constraint is pi's `turn_end` projection check, not the request (register
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`.** Disabling takes effect on the next request, without
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
- The one failure this suite cannot see is pi changing how a `display: false` `custom_message` projects,
282
- or tightening `canContinue`. Drafts are committed before the continuation is validated, so that change
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
- pi -e . -p "create two todos 'a' and 'b', then reply ok" --tools todo
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
- and read the pi changelog for `custom_message`, `display`, `canContinue`, plus rpiv-todo's for
295
- `reminder` (upstream PR #190 would put a second task block in the request).
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`, which carries no task state), so the list stays the agent's to maintain and the
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 takes a reason, stores `{reason, anchor}` in memory
56
- and stops continuations for that message. The human is notified, `/todoloop` shows
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
- (`todo_loop_block`, a few hundred characters of description and schema) that pi puts in every
163
- request. Neither carries task state, and there is no per-request block. Whatever the model needs it reads with the `todo` tool, and that result
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
@@ -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.** Removing it or creating the file
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dumbmice/pi-todo-loop",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Keep pi working while a todo list still has open tasks, and stop when the agent asks for a person.",
5
5
  "type": "module",
6
6
  "license": "MIT",
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: {