gentle-pi 4.1.0 → 4.2.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.
Files changed (81) hide show
  1. package/README.md +39 -14
  2. package/bin/engram-guard +1 -1
  3. package/bin/gentle-shell.mjs +16 -11
  4. package/docs/auto-permission-answers.md +25 -0
  5. package/docs/auto-permission-evidence.md +39 -0
  6. package/docs/bundled-install.md +8 -6
  7. package/docs/gentle-shell.md +17 -10
  8. package/docs/install-wizard.md +9 -9
  9. package/docs/permission-modes.md +50 -5
  10. package/docs/readme-reference.md +48 -12
  11. package/docs/session-profile-format.md +39 -0
  12. package/docs/yolo-mode.md +2 -1
  13. package/extensions/ask-user-choice.ts +4 -0
  14. package/extensions/ask-user-question.ts +6 -1
  15. package/extensions/gentle-agents.ts +54 -23
  16. package/extensions/gentle-ai.ts +105 -25
  17. package/extensions/gentle-shell.ts +26 -31
  18. package/extensions/gentle-todo.ts +26 -16
  19. package/lib/auto-permission-answers.ts +133 -0
  20. package/lib/auto-permission-classifier.ts +215 -36
  21. package/lib/auto-permission-evidence.ts +88 -0
  22. package/lib/auto-permission-memory.ts +69 -0
  23. package/lib/auto-permission-runtime.ts +251 -35
  24. package/lib/auto-permission-tool-context.ts +61 -0
  25. package/lib/destructive-command-guard.ts +104 -28
  26. package/lib/gentle-shell-bundled.ts +23 -5
  27. package/lib/gentle-shell-launcher.ts +1 -1
  28. package/lib/model-routing-authority.ts +6 -1
  29. package/lib/selection-engine.ts +4 -1
  30. package/lib/session-profile-freeze.ts +10 -15
  31. package/lib/session-profile-persistence.ts +187 -0
  32. package/lib/shell-changes.ts +2 -0
  33. package/lib/shell-prompt.ts +6 -4
  34. package/lib/shell-todo.ts +20 -8
  35. package/lib/vim-editor-adapter.ts +22 -4
  36. package/package.json +5 -5
  37. package/runtime/gentle-shell-bundled.mjs +23 -5
  38. package/runtime/gentle-shell-launcher.mjs +1 -1
  39. package/scripts/install-wizard-preview.mjs +2 -2
  40. package/scripts/installer-downloads.mjs +7 -7
  41. package/scripts/installer-preflight.mjs +1 -1
  42. package/scripts/pi-update.mjs +223 -0
  43. package/tests/append-system-prompt-route.test.ts +18 -1
  44. package/tests/auto-mode-human-answers.test.ts +68 -0
  45. package/tests/auto-mode-invariants.test.ts +23 -5
  46. package/tests/auto-mode-sdk.test.ts +29 -2
  47. package/tests/auto-permission-answers.test.ts +170 -0
  48. package/tests/auto-permission-classifier.test.ts +353 -2
  49. package/tests/auto-permission-compound.test.ts +24 -0
  50. package/tests/auto-permission-evidence.test.ts +88 -0
  51. package/tests/auto-permission-memory.test.ts +46 -0
  52. package/tests/auto-permission-public-regressions.test.ts +111 -0
  53. package/tests/auto-permission-runtime.test.ts +270 -12
  54. package/tests/autonomous-guard.test.ts +86 -16
  55. package/tests/bundled-install.test.ts +3 -3
  56. package/tests/engram-safety.test.ts +34 -6
  57. package/tests/gentle-agents.test.ts +162 -6
  58. package/tests/gentle-shell-bin.test.ts +17 -17
  59. package/tests/gentle-shell-bundled.test.ts +67 -33
  60. package/tests/gentle-shell-launcher.test.ts +18 -13
  61. package/tests/gentle-shell.test.ts +71 -5
  62. package/tests/gentle-todo.test.ts +79 -27
  63. package/tests/install-wizard.test.ts +2 -2
  64. package/tests/installer-posix-bootstrap.test.ts +8 -8
  65. package/tests/installer-preflight.test.ts +3 -3
  66. package/tests/installer-probes.test.ts +8 -8
  67. package/tests/installer-runner.test.ts +14 -13
  68. package/tests/installer-server.test.ts +5 -5
  69. package/tests/main-channel-upgrade.test.ts +7 -1
  70. package/tests/odd-runtime-delegation-gate.test.ts +4 -1
  71. package/tests/package-manifest.test.ts +6 -6
  72. package/tests/pi-update-launcher.test.ts +87 -0
  73. package/tests/pi-update.test.ts +186 -0
  74. package/tests/rdd-status-line.test.ts +52 -0
  75. package/tests/selection-engine.test.ts +35 -5
  76. package/tests/session-profile-freeze.test.ts +14 -1
  77. package/tests/session-profile-persistence.test.ts +334 -0
  78. package/tests/shell-changes.test.ts +18 -0
  79. package/tests/shell-todo.test.ts +15 -12
  80. package/tests/vim-editor-adapter.test.ts +41 -15
  81. package/tests/yolo-mode.test.ts +6 -1
package/README.md CHANGED
@@ -116,6 +116,8 @@ Say what you need once, then keep moving. el Gentleman helps turn intent into cl
116
116
 
117
117
  Bring in help without losing the thread. Focused package-owned Pi agents can map a codebase, implement a bounded change, or verify it, while one parent stays accountable for the scope, the decisions, and the final summary.
118
118
 
119
+ The parent delegates for a reason, not for size. Understanding that outgrows one inline batch (about 3 calls and 10k tokens) gets one read-only explorer; two or more independent units with disjoint edit surfaces get parallel writers; a high-risk change gets an independent verifier; a long session hands its next unit to a fresh worker. Without a named reason the parent works inline — file count and task size never decide the route.
120
+
119
121
  **[Docs →](docs/readme-reference.md#how-the-harness-decides-what-to-do)**
120
122
 
121
123
  ---
@@ -124,7 +126,9 @@ Bring in help without losing the thread. Focused package-owned Pi agents can map
124
126
 
125
127
  <img width="100%" src="docs/assets/diagrams/odd-workflow.svg" alt="Organic Driven Development as seven numbered steps: Authorize, Explore, Resolve uncertainty, and Classify across the top row; Classify forks, so small understood work stays light while substantial work gets step five, Track, with one feature document; both paths converge on Implement task by task and then Close, above a dashed band marking that one feature document mirrored in Engram lets work resume across sessions">
126
128
 
127
- **Organic Driven Development (ODD)** is the everyday path: the agent explores before changing anything, clarifies only real decisions, and keeps small understood work small. Substantial, authorized work gets one recoverable feature document — mirrored in memory when available — so progress, evidence, and the next step survive an interruption; checks follow the configured TDD mode.
129
+ **Organic Driven Development (ODD)** is the everyday path: the agent explores before changing anything, clarifies only real decisions, and keeps small understood work small. Substantial, authorized work gets one recoverable feature document — mirrored in memory when available — so progress, evidence, and the next step survive an interruption; behavior changes are test-first by default when a runnable test applies.
130
+
131
+ Tasks are sized by understanding, risk, and resumability: work you could finish from the request and `git diff` alone runs inline, with no subagents and no task document. The feature document quotes your spec verbatim, and every handoff points at it instead of paraphrasing it, so exact strings, error messages, and examples reach the workers intact.
128
132
 
129
133
  **[Docs →](docs/readme-reference.md#organic-driven-development)**
130
134
 
@@ -136,6 +140,8 @@ Bring in help without losing the thread. Focused package-owned Pi agents can map
136
140
 
137
141
  Review the exact change, not a moving target. Native review keeps one candidate in view, returns risk-scoped evidence, and can surface a bounded correction path. You still decide what happens next in your repository.
138
142
 
143
+ Review is proportionate: verification follows the risk tier in every RDD state, a review is only due for high-risk work, and the agent forwards the 4R lenses that fit what it touched — with its reason — instead of paying for all four.
144
+
139
145
  Reviewer completions inherit Pi's effective `transport` preference when an acknowledged individual or grouped capture executes. The host forwards a defined value unchanged, with one settings snapshot per group; it never forces SSE or changes model routing. An absent setting or an older Pi API without `getSettings()` preserves the provider default. This completion preference is separate from native `pi_host_relay` coordination, and providers that do not support it may ignore it.
140
146
 
141
147
  **[Docs →](docs/review-integration.md)**
@@ -228,13 +234,32 @@ Cross-orchestrator messages appear as compact `🤖 Sender → 🤖 Recipient` c
228
234
 
229
235
  ---
230
236
 
231
- ### What's new in v3.5
237
+ ### What's new in v4.1
238
+
239
+ The [v4.1.0 release](https://github.com/Gentleman-Programming/gentle-shell/releases/tag/v4.1.0) makes Gentle Shell installable on a clean machine and pins Gentle AI v4.1.1:
240
+
241
+ - **Clean-machine installers:** a browser wizard and double-click installers for macOS, Windows, and Linux install what is missing, update what you have, and ask once before changing anything. See [Get started](#get-started).
242
+ - **Bundled Gentle Shell:** a new installation keeps its exact Pi, pinned Gentle AI, Engram, and state in one prefix; existing global installs can migrate, the launcher offers updates, and `gentle-shell self-uninstall` removes it.
243
+ - **Background jobs and monitors:** `bash_background` wakes the agent once when a command exits, and `monitor` turns each output line of a long-running command into an event. Inspect them with `/gentle:jobs` ([Background jobs](#background-jobs)).
244
+ - **Contextual Auto permissions:** Auto decides with the conversation's context and image input, retries provider failures, and can be the saved default ([Permission modes](#permission-modes--normal-auto-or-yolo)).
245
+ - **Cross-session coordination:** sessions publish a recognizable subject, task aliases, repository scope, and a bounded work classification that other sessions can list, search, and consult with explicit cost permission.
246
+ - **Session-bound profiles:** the active profile binds to the parent session, and orchestrator and reviewer routing apply per session.
247
+ - **Delegate for a reason and review by risk:** see [Focused agents](#focused-agents--context-with-a-return-path), [ODD](#odd--the-everyday-workflow), and [Native review](#native-review--review-the-exact-change).
248
+
249
+ ---
250
+
251
+ ### Measured — What proportional routing changed
252
+
253
+ The routing rules came from benchmarks. These runs use a local benchmark with hidden acceptance suites; each cell is a single run (n=1), and cost is API-equivalent.
232
254
 
233
- The [v3.5.1 release](https://github.com/Gentleman-Programming/gentle-shell/releases/tag/v3.5.1) makes Gentle Shell runnable on its own:
255
+ | Scenario | Before | After |
256
+ | :--- | :--- | :--- |
257
+ | 3-line bug in one function | 335 s · 56 turns · $0.339 | **77 s · 10 turns · $0.105** — inline, no subagents |
258
+ | Bug rooted in shared logic | 479 s · $0.361 | **276 s · $0.207** — one explorer |
259
+ | Data migration (high risk) | 559 s · $0.403 · needed a feedback round | 539 s · $0.555 · **accepted on the first round** — one independent verifier |
260
+ | 73-line spec, 26 hidden checks | Paraphrased handoffs: 13 failing, still 23/26 after two feedback rounds | Spec passed by reference ([#1718](https://github.com/Gentleman-Programming/gentle-shell/pull/1718)) |
234
261
 
235
- - **Standalone launcher:** `npm i -g gentle-pi` installs `gentle-shell`, which opens Pi with the Gentle Shell package loaded from its own home (`~/.gentle-shell/agent`) or, with `--link`, from your existing `~/.pi/agent`; `gentle-shell install npm:<pkg>` and the other pi subcommands run against the selected home. A bundled or `PATH` pi is used, never a modified one.
236
- - **Link mode take-over:** when `~/.pi/agent` already declares gentle-pi as a path package, the launcher takes over extension loading (`--no-extensions` plus explicit `-e` for every other declared package and loose extension) so tools never register twice.
237
- - **Interactive RPC hosts:** with `GENTLE_SHELL_INTERACTIVE_HOST=1` and `--mode rpc`, ask-user tools use pi's RPC dialogs and gentle-agents publishes live subagent activity for the desktop app. See the [reference](docs/readme-reference.md#interactive-rpc-hosts).
262
+ Against the same model in Codex CLI, with blind review of every solution, Gentle Shell was accepted on the first round in every cell and ranked higher on the medium (3 of 3 runs) and large tasks; it was close on the small one. The large task still took about three times longer (17.5–17.9 min vs 6.1 min) — that gap is open work. Sources: [#1723](https://github.com/Gentleman-Programming/gentle-shell/pull/1723), [#1794](https://github.com/Gentleman-Programming/gentle-shell/pull/1794), [#1713](https://github.com/Gentleman-Programming/gentle-shell/issues/1713).
238
263
 
239
264
  ---
240
265
 
@@ -258,7 +283,7 @@ Download the installer for your computer, double-click it, and follow the steps
258
283
  | **Windows** | [gentle-shell-installer-windows.zip](https://github.com/Gentleman-Programming/gentle-shell/releases/latest/download/gentle-shell-installer-windows.zip) | Right-click the zip → **Extract All**. Open the extracted folder, then the *Gentle Shell Installer* folder inside it, and double-click **Install Gentle Shell.cmd**. It does not run from inside the zip. |
259
284
  | **Linux** | [gentle-shell-installer-linux.tar.gz](https://github.com/Gentleman-Programming/gentle-shell/releases/latest/download/gentle-shell-installer-linux.tar.gz) | Extract it, then double-click **install-gentle-shell.sh**. If your file manager does not run scripts, run `sh install-gentle-shell.sh` in that folder. |
260
285
 
261
- A small window opens, then the installer appears in your browser. Use the tab it opens, and keep the small window open until the installer says it is done. If a download link does not open, the newest release does not include the installers yet: use [Path C](#path-c-browser-installer-from-a-checkout) meanwhile.
286
+ A small window opens, then the installer appears in your browser. Use the tab it opens, and keep the small window open until the installer says it is done.
262
287
 
263
288
  > **Your computer will warn you the first time.** These installers are not signed with an Apple or Microsoft developer certificate yet, so the system cannot verify who made them. That warning is expected; here is how to continue:
264
289
  >
@@ -268,13 +293,13 @@ A small window opens, then the installer appears in your browser. Use the tab it
268
293
  >
269
294
  > Only download from this repository's [Releases](https://github.com/Gentleman-Programming/gentle-shell/releases) page. To check a download, compare its SHA-256 with [gentle-shell-installers-SHA256SUMS.txt](https://github.com/Gentleman-Programming/gentle-shell/releases/latest/download/gentle-shell-installers-SHA256SUMS.txt).
270
295
 
271
- From the first release that publishes its distribution assets, a new installation instead gets Gentle Shell as one self-contained product. Its own Node.js, npm, pnpm and Pi live in `~/.gentle-shell`, and your own tools are not used or changed ([bundled installation](docs/install-wizard.md#bundled-installation)).
296
+ A new installation gets Gentle Shell as one self-contained product. Its own Node.js, npm, pnpm and Pi live in `~/.gentle-shell`, and your own tools are not used or changed ([bundled installation](docs/install-wizard.md#bundled-installation)).
272
297
 
273
298
  The installer is a preview: it is tested on Linux and in CI, while clean-machine runs on macOS and Windows are still being verified. How it works and what it changes: **[installation wizard](docs/install-wizard.md)**.
274
299
 
275
300
  ### Path A: standalone `gentle-shell` (recommended, no pi changes)
276
301
 
277
- `gentle-shell` opens Pi with the Gentle Shell package loaded, without installing it into your pi agent or editing its `settings.json`.
302
+ `gentle-shell` opens Pi with the Gentle Shell package loaded, without installing it into your pi agent or editing its `settings.json`. It requires Pi 1.2.0 or newer.
278
303
 
279
304
  ```bash
280
305
  npm i -g gentle-pi
@@ -307,8 +332,8 @@ Every other argument is forwarded to pi unchanged, for example `gentle-shell --m
307
332
  Install the stable release into an existing pi agent, restart Pi, then synchronize the installed assets.
308
333
 
309
334
  ```bash
310
- # Published stable release: v3.5.1
311
- pi install npm:gentle-pi@3.5.1
335
+ # Published stable release: v4.1.0
336
+ pi install npm:gentle-pi@4.1.0
312
337
 
313
338
  # Restart Pi, then run:
314
339
  gentle-ai sync
@@ -317,7 +342,7 @@ gentle-ai sync
317
342
  pi
318
343
  ```
319
344
 
320
- See the [v3.5.1 release notes](https://github.com/Gentleman-Programming/gentle-shell/releases/tag/v3.5.1) for version-specific changes.
345
+ See the [v4.1.0 release notes](https://github.com/Gentleman-Programming/gentle-shell/releases/tag/v4.1.0) for version-specific changes.
321
346
 
322
347
  **Builtin codemode warning.** gentle-pi replaces Pi's builtin `codemode` with its compact renderer, so Pi warns at startup that the builtin was not loaded. In your own Pi home (`pi` with this package, or `gentle-shell --link`), gentle-pi asks once in the interactive TUI whether to add `"-builtin:codemode"` to `extensions` in the agent `settings.json` (usually `~/.pi/agent/settings.json`); it writes only if you accept, and the warning disappears from the next launch. A declined prompt is not repeated. To silence it by hand, add the entry yourself, for example `"extensions": ["-builtin:codemode"]`. Isolated `gentle-shell` homes already carry it.
323
348
 
@@ -345,9 +370,9 @@ An older Node.js or an incompatible pnpm (from mise, nvm, nvm-windows, fnm, Volt
345
370
 
346
371
  ### Keep it up to date
347
372
 
348
- **Release compatibility:** `upgrade` is currently available only in main builds containing commit `96ff045a8`. No published release through `gentle-pi` v4.0.0 supports it; there is no first supported release yet. On older launchers, `gentle-shell upgrade` opens a session with `upgrade` as the prompt instead of updating.
373
+ **Release compatibility:** `upgrade` is available from `gentle-pi` v4.1.0 (and in main builds containing commit `96ff045a8`). Releases through v4.0.0 do not support it: on those launchers, `gentle-shell upgrade` opens a session with `upgrade` as the prompt instead of updating.
349
374
 
350
- For those installations, use the [browser installer from a fresh main checkout](#path-c-browser-installer-from-a-checkout). It detects an existing installation and shows the update plan before you confirm. Choose **Latest release** to stay on stable (which does not yet provide `upgrade`), or **Latest main** to install a build with the command. See the [older-installation procedure](docs/readme-reference.md#updating-an-installation-without-upgrade).
375
+ For those installations, download the [latest installer](#easiest-download-and-double-click-no-terminal-needed) or use the [browser installer from a fresh main checkout](#path-c-browser-installer-from-a-checkout). It detects an existing installation and shows the update plan before you confirm. See the [older-installation procedure](docs/readme-reference.md#updating-an-installation-without-upgrade).
351
376
 
352
377
  On a main build that supports `upgrade`:
353
378
 
package/bin/engram-guard CHANGED
@@ -5,5 +5,5 @@
5
5
  # local Engram data only by running ENGRAM_BIN, so failing here stops them
6
6
  # before any store is opened, created or migrated. It never runs Engram and
7
7
  # never reads or writes Engram data; the notice is printed as data, not run.
8
- printf '%s\n' "${ENGRAM_STARTUP_NOTICE:-Engram memory is disabled by Gentle Shell: Engram store compatibility could not be verified. Back up your Engram data, update Engram, restart its server and Shell, then recheck.}" >&2
8
+ printf '%s\n' "${ENGRAM_STARTUP_NOTICE:-Engram memory disabled: Shell could not verify safe store or server reuse. Matching versions or updating alone do not prove compatibility. Reuse requires a server with verified /compatibility evidence or matching Shell-provisioned store identity. Back up existing Engram data before manual changes; do not delete it or bypass the safety check. Shell rechecks on every launch and remains usable without memory.}" >&2
9
9
  exit 78
@@ -276,16 +276,11 @@ async function classifyBundledMemory() {
276
276
  return classifyEngramStartup({ ...decision, store: observed.store, endpoint, provenance: ownEngramStore(observed, core) });
277
277
  }
278
278
 
279
- // Verified read-only in Engram v3.3.2: `engram instance-id` first runs a GitHub
280
- // update check that sends GH_TOKEN or GITHUB_TOKEN (cmd/engram/main.go:742-744,
281
- // 835-847; internal/version/check.go:23-24,60-70,171-176) unless
282
- // ENGRAM_NO_UPDATE_CHECK is set (check.go:19,49-51,114-117). It then resolves the
283
- // default ~/.engram (main.go:749-765) and runs EnsureInstanceID
284
- // (internal/store/instance.go:15-79): the data dir, `.instance-id.lock` (waits
285
- // up to 60s, internal/store/migration_lock.go:29-76) and `.instance-id`. It
286
- // returns (main.go:767-775) before orphan migration (main.go:779) and opens no
287
- // database. The timeout stays below that lock wait: a held lock means a
288
- // concurrent creator, and memory then stays off for the launch.
279
+ // Verified with the published Engram v3.4.0 darwin-arm64 binary in an isolated
280
+ // HOME: `instance-id` creates only `.instance-id.lock` and `.instance-id`,
281
+ // without a database. Keep update checks suppressed and GitHub tokens removed
282
+ // for this provisioning invocation. The timeout stays below the identity lock
283
+ // wait: a held lock means a concurrent creator, so memory stays off that launch.
289
284
  const ENGRAM_PROVISION_TIMEOUT_MS = 15000;
290
285
 
291
286
  // A fresh launch's store identity, provisioned by Shell itself with the verified
@@ -1782,7 +1777,8 @@ async function main() {
1782
1777
  const args = parseLauncherArgs(process.argv.slice(2));
1783
1778
  if (args.error !== undefined) fail(`${args.error}\nRun 'gentle-shell --help' for usage.`, 2);
1784
1779
  if (args.help) {
1785
- process.stdout.write(`${helpText()}\nBundled Engram safety: memory opens only a fresh, verified or Shell-provisioned store; otherwise ENGRAM_BIN\nis Shell's guard and memory stays off. Back up data, update Engram, restart its server and Shell, then recheck.\nAn explicit ENGRAM_BIN or ENGRAM_URL is kept and bypasses the guard.\n`);
1780
+ process.stdout.write("Pi updates: update reinstalls the verified supplier of the Pi this launcher runs (a global Pi, or the\nShell package whose adjacent peer it is, at the same Shell version) and reads the resulting Pi version back.\nUnknown ownership refuses without installing; update --extensions-only stays Pi-only.\nPrefix-bundled Pi updates with gentle-shell upgrade.\n");
1781
+ process.stdout.write(`${helpText()}\nBundled Engram safety: memory opens only a fresh, verified or Shell-provisioned store; otherwise ENGRAM_BIN\nis Shell's guard and memory stays off. Matching versions or updating alone do not prove compatibility.\nReuse requires a server with verified /compatibility evidence or matching Shell-provisioned store identity.\nBack up existing data before manual changes; do not delete it or bypass the safety check.\nShell rechecks on every launch and remains usable without memory.\nAn explicit ENGRAM_BIN or ENGRAM_URL is kept and bypasses the guard.\n`);
1786
1782
  process.exit(0);
1787
1783
  }
1788
1784
  if (args.command === "home") {
@@ -1869,6 +1865,15 @@ async function main() {
1869
1865
  if (next.note) process.stderr.write(`${next.note}\n`);
1870
1866
  }
1871
1867
  passthrough = plan.piArgs;
1868
+ } else if (args.piSubcommand === "update") {
1869
+ const { managedUpdatePlan, updateSelectedPi } = await import("../scripts/pi-update.mjs");
1870
+ const plan = managedUpdatePlan(args.passthrough);
1871
+ if (plan.self) {
1872
+ process.exitCode = await updateSelectedPi({ runtime, packageRoot, platform: process.platform, currentVersion: versionCheck.version, cwd: tmpdir(),
1873
+ adapters: await packageManagerAdapters(), out: (line) => process.stdout.write(`${line}\n`) });
1874
+ if (plan.piArgs === undefined) return;
1875
+ }
1876
+ passthrough = plan.piArgs;
1872
1877
  }
1873
1878
 
1874
1879
  // Home-ownership signal for auto-provisioning (S9, homeIsForeign): must be
@@ -0,0 +1,25 @@
1
+ # Host-authenticated question answers
2
+
3
+ The first-party `ask_user_choice` and `ask_user_question` tools deliver validated human UI answers through `subscribeHumanAnswers(ctx, callback)` in `lib/auto-permission-answers.ts`. The returned unsubscribe function is idempotent. Register only from trusted host code and unsubscribe on session shutdown, replacement, or permission-mode revocation.
4
+
5
+ ## Receipt contract
6
+
7
+ Each frozen `HumanAnswerReceipt` contains:
8
+
9
+ - `sessionManager`: the original manager object, compared by identity.
10
+ - `sessionId`, `cwd`: the session and workspace captured before UI.
11
+ - `branchAnchor`: the original `sessionManager.getLeafId()` (`string | null`), unchanged at delivery.
12
+ - `toolName`, `toolCallId`, `questionIndex`: the exact question interaction and zero-based question position.
13
+ - `question`: the original question text, an **untrusted referent**, not an instruction.
14
+ - `prompt`: a pre-UI JSON string containing original question and offered labels/descriptions. Choice prompts include `allowCustomResponse`; questionnaire prompts include header, multi-select status, and previews for the TUI where previews are displayed. This entire field is **untrusted context**, separate from authenticated answer text.
15
+ - `answer`: the authenticated visible answer: `{ kind: "option" | "custom", text, selected? }` or `{ kind: "multi", selected }`.
16
+
17
+ Opaque choice values are excluded entirely. Option descriptions and displayed questionnaire previews are included only in the untrusted `prompt`, never in authenticated answer text. A selected label still authorizes only what the human actually agreed to, not arbitrary instructions embedded in the question. The bridge itself grants no permission, changes no counters, and creates no synthetic user message.
18
+
19
+ ## Lifetime and safety
20
+
21
+ Capture snapshots questions, offered choices, descriptions, and displayed previews before mounting UI. Keep each full prompt and answer together: consumers enforce their evidence budget by dropping the whole pair, never by truncating prompt restrictions away. Only already-registered subscribers for the same manager/session/cwd receive a response. Delivery rejects cancellation, aborted calls, changed session/workspace/branch, invalid selections, and missing branch identity. Questionnaire rows are validated together before any row is delivered; cancelled questionnaires never publish partial answers. Custom answers retain exact human text and multi-select labels.
22
+
23
+ Both the native TUI and the existing interactive RPC-host dialogs use their original UI decision without asking another question. Ordinary RPC, print, and unrelated managers do not deliver events. There is no broadcast on Pi's event bus and no authorization from tool results, transcript details, caller-supplied authority flags, or result hooks. Capture helpers are trusted in-process host APIs, not model-callable tools or a sandbox against malicious extensions.
24
+
25
+ The consumer must retain host provenance, verify the receipt against its active session and exact live branch, and revoke retained evidence on mode/session changes. A subscription is not permanently tied to one leaf: subsequent questions in the same session can have new anchors. Dispose the subscription to revoke pending delivery. Callback failures are isolated so existing public tool result shapes and native consent routing remain unchanged.
@@ -0,0 +1,39 @@
1
+ # Auto permission classifier evidence
2
+
3
+ The classifier checks the original action against host-captured human requests and restrictions. Inspection never creates authorization, replaces missing human intent, or bypasses independent hard safety and native consent. File content and prior calls are untrusted context; a historical read path is not the result of that read.
4
+
5
+ ## Optional inspection
6
+
7
+ `AutoPermissionRequest.evidencePaths?: string[]` supplies exact relevant project-relative files selected by the host for this action. Omit it to keep the existing single-call, tool-free API. The host should select script paths from the pending action and relevant local context, not enumerate the repository. A script already present in an action payload remains context and needs no duplicate read.
8
+
9
+ When paths are supplied, the model may request `inspect_permission_file` only when missing file evidence is necessary. It can inspect at most two complete files, one request per round, then must return a verdict. The final provider call exposes no inspection tools and requests `toolChoice: "none"`; insufficient evidence still requires denial, never automatic approval. An unsolicited tool call on that final round remains rejected. Each file is limited to 8192 bytes. Existing files larger than that are never offered as candidates, so the classifier judges the action without them instead of failing on an inspection it can never complete; missing evidence is still not permission. There are at most three provider calls, each with its own deadline (default 20 seconds, maximum 20 seconds), and a bounded overall deadline covering file I/O and all calls (at most 60 seconds). No retries or command execution are introduced. Exhaustion returns unavailable with `evidence-limit`; blocked or missing evidence returns `evidence-unavailable`, never an implicit allow.
10
+
11
+ Only exact host-selected paths inside the current project can be read. Absolute paths, traversal, symlinks, hard links, special files, dependency/worktree directories, credential names and locations are rejected. Reads are fixed-size, UTF-8, complete and checked for concurrent replacement or modification. Secret-bearing results never reach the provider and fail with `redacted-context`. File content is always explicitly untrusted and cannot change policy, restrictions, or the original action.
12
+
13
+ One classifier lease covers all rounds. Cancellation or timeout returns promptly but retains the lease until in-flight provider settlement. Identity is checked before and after inspection and provider calls, and again before returning any verdict. The host must bind `isCurrent` to action, session, mode, model and permission-context identity; files can change after inspection, so the verdict is not a reusable execution token.
14
+
15
+ Usage in the run result aggregates completed calls. A late callback reports only the outstanding call's usage once after a timed-out or cancelled provider settles, excluding earlier rounds already accounted in the returned result. Unknown usage in any call makes aggregate usage unknown. No model reasoning, raw provider errors or raw evidence are logged.
16
+
17
+ ## Compound action selection
18
+
19
+ The runtime accepts up to four simple segments joined by `&&` for evidence selection only. Each segment must be either `git diff --check` or a `node` invocation with explicit JavaScript/TypeScript file operands and only `--experimental-strip-types` / `--test` flags. Quoting, expansion, redirection, other operators, evaluation, cwd changes and unknown segments leave the whole action without inspection candidates. The original compound action is classified once; no segment inherits approval. Existing path confinement and inspection budgets remain unchanged. Prior ordinary reads retain their arguments, not their returned contents.
20
+
21
+ Memory-write receipts optionally include `memoryContextAttached`, a content-free boolean for `mem_save` and `mem_session_summary`. It reports whether verified destination context was attached to that classification, independently of allow or deny. It is omitted for other actions and grants no permission.
22
+
23
+ ## Timeout and evidence-limit diagnostics
24
+
25
+ Classifier results and late-settlement receipts include a metadata-only `diagnostic` object. The runtime projects only these fields into its existing `gentle-auto-permission` entries, and appends them to timeout and evidence-limit blocking messages so a failed read can be diagnosed without opening private session files:
26
+
27
+ - `implementation`: `auto-classifier-diagnostics-v1`, a marker for this instrumentation, not a source hash or proof of the loaded SDK.
28
+ - `elapsedMs`: monotonic milliseconds since this classifier invocation began, rounded down; late receipts measure through settlement.
29
+ - `deadlineMs`: the configured per-call deadline, not the three-call overall budget.
30
+ - `timerFired`: whether a local deadline callback actually ran. The `timeout` label alone does not prove that a timer fired.
31
+ - `returnPath`: `preflight`, `provider`, `provider-result`, `evidence`, `verdict`, or `late-settlement`, locating the observed return stage without recording exception text.
32
+
33
+ - `evidenceLimit` (optional): a fixed host discriminator for `evidence-limit`: `path-count`, `provider-rounds`, `file-count`, `duplicate-file`, `context-bytes`, `invalid-byte-budget`, `file-stat-bytes`, or `file-read-bytes`. The public error code and implementation marker remain unchanged. Provider-round exhaustion takes precedence over file-count and duplicate checks; with the current three-round/two-read limits, the file-count branch cannot independently be reached. Stat-size and read-growth failures remain distinct without recording sizes.
34
+
35
+ These diagnostics contain no prompts, file contents, paths, credentials, provider payloads, or raw errors. Unknown fields are dropped; invalid metadata is omitted. No diagnostic changes a verdict, deadline, retry policy, cancellation, or lease. Missing diagnostics may mean an older or injected implementation; absence alone does not prove which implementation ran. After reloading the instrumented code, one explicitly authorized Auto read is sufficient to collect the blocking message if the failure recurs; it is not an instruction to retry repeatedly.
36
+
37
+ ## Scope
38
+
39
+ This unit changes classifier behavior only. Runtime path selection, bounded call history and recovery messaging are integrated separately. Benign numeric token declarations and `max_tokens` limits no longer trigger credential heuristics; actual credential assignments, authentication values and private keys remain protected. Prior actions are projected once, not charged twice. Optional oldest complete history records are omitted when necessary to fit the input budget; active human requests, restrictions and the pending action are never truncated. No sandbox, Normal/YOLO, native-consent, or hard-policy changes are made.
@@ -13,7 +13,7 @@ The building blocks live in `scripts/bundled-install.mjs`. The web installer and
13
13
  | `~/.gentle-shell/npm/{prefix,cache}/`, `~/.gentle-shell/npm/npmrc` | npm's global prefix, cache and global config. The global config file is never written. |
14
14
  | `~/.gentle-shell/runtime/pnpm-11.1.1/package/` | Our pnpm, from the verified registry tarball. |
15
15
  | `~/.gentle-shell/runtime/go/1.25.14/go/` | Our Go, only when asked (`acquireGo` layout). |
16
- | `~/.gentle-shell/runtime/engram-3.3.2/engram` | Our Engram, from the verified GitHub release archive ([Isolation from the user's Gentle AI](#isolation-from-the-users-gentle-ai)). |
16
+ | `~/.gentle-shell/runtime/engram-3.4.0/engram` | Our Engram, from the verified GitHub release archive ([Isolation from the user's Gentle AI](#isolation-from-the-users-gentle-ai)). |
17
17
  | `~/.gentle-shell/gentle-ai/` | Gentle AI's state, managed assets, backups and telemetry state (`GENTLE_AI_HOME`). |
18
18
  | `~/.gentle-shell/config/gentle-ai/` | gentle-pi's config home in a bundled install (derived, never exported to Pi): profiles, preferences, persona, background subagents. |
19
19
  | `~/.gentle-shell/pnpm/{store,cache,state,config}/` | Every folder pnpm writes. |
@@ -229,7 +229,7 @@ Dropped: inherited npm and pnpm settings, `NODE_OPTIONS`, `NODE_PATH`, and the w
229
229
 
230
230
  ### Engram startup safety
231
231
 
232
- A bundled Shell decides, before setup and again right before Pi starts, whether memory may open the shared Engram store. When it may not, Shell keeps working without memory and **points `ENGRAM_BIN` at its own guard**, so no provider, whichever Pi route loaded it, can open, create or migrate a store it cannot vouch for. The core pin stays **3.3.2**, acquired through the existing size- and checksum-verified path.
232
+ A bundled Shell decides, before setup and again right before Pi starts, whether memory may open the shared Engram store. When it may not, Shell keeps working without memory and **points `ENGRAM_BIN` at its own guard**, so no provider, whichever Pi route loaded it, can open, create or migrate a store it cannot vouch for. The core pin is **3.4.0**, acquired through the existing size- and checksum-verified path.
233
233
 
234
234
  #### Decision
235
235
 
@@ -254,21 +254,23 @@ Classification never runs an Engram executable (`instance-id`, `version`, `serve
254
254
 
255
255
  #### Provenance (Shell's own store)
256
256
 
257
- When the final pre-Pi decision is "fresh" (no data directory, nothing listening, verified bundled core, no explicit selection), Shell provisions the store identity itself **before** Pi starts: it runs the verified bundled core's `instance-id` (15 s timeout) with the environment Pi will use, except that this one invocation gets `ENGRAM_NO_UPDATE_CHECK=1` and no `GH_TOKEN`/`GITHUB_TOKEN`. In Engram 3.3.2 that command only creates the data directory, `.instance-id.lock` and `.instance-id`; it skips orphan migration and opens no database. Shell then reads the `.instance-id` **file** (regular, not a link, a valid id equal to the command's output, in the same default directory it classified as absent) and immediately writes `<prefix>/config/engram-provenance.json` (mode 0600): the canonical data directory, that instance id, and the sha256 of the bundled core. If any step fails (the command fails or times out, the identity is invalid, or the record cannot be written), memory is disabled for that launch behind the guard; Pi never starts with memory allowed without a written record. Shell never records a store it did not provision, so a store that appears later at the default directory is never adopted.
257
+ When the final pre-Pi decision is "fresh" (no data directory, nothing listening, verified bundled core, no explicit selection), Shell provisions the store identity itself **before** Pi starts: it runs the verified bundled core's `instance-id` (15 s timeout) with the environment Pi will use, except that this one invocation gets `ENGRAM_NO_UPDATE_CHECK=1` and no `GH_TOKEN`/`GITHUB_TOKEN`. In Engram 3.4.0 that command only creates the data directory, `.instance-id.lock` and `.instance-id`; it skips orphan migration and opens no database. Shell then reads the `.instance-id` **file** (regular, not a link, a valid id equal to the command's output, in the same default directory it classified as absent) and immediately writes `<prefix>/config/engram-provenance.json` (mode 0600): the canonical data directory, that instance id, and the sha256 of the bundled core. If any step fails (the command fails or times out, the identity is invalid, or the record cannot be written), memory is disabled for that launch behind the guard; Pi never starts with memory allowed without a written record. Shell never records a store it did not provision, so a store that appears later at the default directory is never adopted.
258
258
 
259
259
  A later launch treats the store as Shell's own only when all hold:
260
260
 
261
261
  1. The data directory, its `.instance-id` and the current bundled core digest equal the record. A Shell upgrade that changes the core binary therefore disables memory: forward upgrades are not allowed by version.
262
- 2. The directory still holds only identity files (the session has not created a database yet), or `engram.db` is a regular, unlinked file whose first 100 bytes are a valid SQLite header (magic, page size, bytes 18/19 each 1 or 2) and whose `user_version` (bytes 60-63) is **not greater** than the bundled core's schema (`1`, verified from `v3.3.2:internal/store/store.go`). A greater value means a newer Engram migrated the shared store. Equal or lower (including a stale `0` before a WAL checkpoint) is no signal either way. A pin without a verified schema entry disables this path.
262
+ 2. The directory still holds only identity files (the session has not created a database yet), or `engram.db` is a regular, unlinked file whose first 100 bytes are a valid SQLite header (magic, page size, bytes 18/19 each 1 or 2) and whose `user_version` (bytes 60-63) is **not greater** than the bundled core's schema (`1`, verified from `v3.4.0:internal/store/store.go`). A greater value means a newer Engram migrated the shared store. Equal or lower (including a stale `0` before a WAL checkpoint) is no signal either way. A pin without a verified schema entry disables this path.
263
263
  3. If a server listens, its `/health` reports the recorded instance and exactly the bundled core version.
264
264
 
265
265
  #### Compatible reuse and authorization
266
266
 
267
- `GET /compatibility` (Engram PR #1792, not in any released core yet; current cores answer 404) must return `status: "ready"`, `active_reuse_authorized: true`, `capability: "startup-verified-store-v1"`, `revision: 1`, `source: "active"`, 32-hex `instance_id` and `runtime_id`, and no `offline_automatic_opening_authorized: true`. The endpoint uses the server's `requireAuth`: open when the **server** has no `ENGRAM_HTTP_TOKEN`, otherwise it needs `Authorization: Bearer <token>`. Shell owns no such secret and sends none, so a token-protected server answers 401 and stays unknown (disabled).
267
+ `GET /compatibility` (published in Engram 3.4.0 through PR #1792; 3.3.2 answers 404) must return `status: "ready"`, `active_reuse_authorized: true`, `capability: "startup-verified-store-v1"`, `revision: 1`, `source: "active"`, 32-hex `instance_id` and `runtime_id`, and no `offline_automatic_opening_authorized: true`. The endpoint uses the server's `requireAuth`: open when the **server** has no `ENGRAM_HTTP_TOKEN`, otherwise it needs `Authorization: Bearer <token>`. Shell owns no such secret and sends none, so a token-protected server answers 401 and stays unknown (disabled).
268
268
 
269
269
  #### Recovery and setup
270
270
 
271
- When disabled, Shell prints: **back up your existing Engram data, update Engram, restart its server and Shell, then recheck memory safety**. It performs no migration, deletion, or process termination.
271
+ When the running server's `/health` reports a valid 32-hex `instance_id` and a canonical numeric `x.y.z` version (optional `v` prefix, no leading zeros, at most four digits per component) older than 3.4.0, and `/compatibility` does not answer ready, the notice names that version and asks to back up the data, update **that server** to 3.4.0 or later with its own installation method, and restart it. The version only selects this wording: memory stays disabled, and any other version string is never shown. Otherwise, when disabled, Shell explains that it **could not verify safe store or server reuse**. Matching versions or updating alone do not prove compatibility. Reuse requires the verified active `/compatibility` contract or matching Shell-provisioned store identity under the rules above. Back up existing data before manual changes; do not delete the store, fabricate provenance, or bypass the safety check. Shell rechecks on every launch and remains usable without memory. It performs no migration, deletion, or process termination.
272
+
273
+ Engram 3.3.2 does not expose `/compatibility`, so reinstalling that version cannot authorize reuse of an existing global store. [Engram 3.4.0](https://github.com/Gentleman-Programming/engram/releases/tag/v3.4.0) includes the endpoint from PR #1792. Back up the existing store before updating its server through its own installation method, then restart that server and Shell. Updating Shell's bundled binary does not replace an already-running global server. Reuse still requires a valid ready response, not just the version number; a token-protected server cannot provide it to Shell's unauthenticated probe. Do not disable authentication to bypass this policy or use an unpublished build.
272
274
 
273
275
  The memory decision never skips or fails setup: `gentle-shell setup` always publishes the verified core, runs Gentle AI and pins the companion, as before. Gentle AI 4.0.0 `install --agent pi` runs no `engram setup` for Pi, and `pi-engram init` only edits settings, so neither opens a store. Setup reclassifies after publishing the core, and the decision only selects the Engram environment its children and the launched Pi receive. On a new machine the wizard publishes the runtime without Engram, so the first `gentle-shell setup` publishes the core and the following launch is the fresh one that records provenance. Setup and every launch pin `npm:gentle-engram@0.4.2` through Pi's own `install` command (configuration only) and verify the declaration plus `engram.startupPolicy: 1`; a failed check is reported and Shell still starts, protected by the guard. `setup --dry-run` installs nothing.
274
276
 
@@ -6,6 +6,12 @@ For development, use [ODD and feature recovery](readme-reference.md#organic-driv
6
6
 
7
7
  Source map: [shell extension](../extensions/gentle-shell.ts), [shell bar](../lib/shell-bar.ts), [changes model](../lib/shell-changes.ts), [changes view](../lib/shell-changes-view.ts), [usage model](../lib/shell-usage.ts), [usage view](../lib/shell-usage-view.ts), [agents extension](../extensions/gentle-agents.ts), and [agent runner](../lib/agents-runner.ts).
8
8
 
9
+ ## Updating Pi safely
10
+
11
+ Run `gentle-shell update` to update the Pi this launcher runs. Shell verifies npm/pnpm global listing JSON and canonical package roots before installing exactly one supplying package. This supports pnpm v11 linked stores without relying on `PNPM_HOME` containment. When the launcher runs its adjacent Pi peer, the supplier is the listed `gentle-pi` package itself: Shell is reinstalled at its current version so the package manager re-resolves the peer, because updating a separate global Pi never changes the peer a Shell install runs. A Pi found on `PATH` or through `GENTLE_SHELL_PI` is updated through its own listed global package; a package-manager shim is followed to the single `cli.js` it names. Pi and Shell are never combined into a comma install.
12
+
13
+ Unknown or ambiguous ownership, npm source/link packages, and pnpm groups shared with other top-level packages refuse without installation changes. Separate groups in one aggregated listing are allowed. After the install, Shell reads the supplied Pi version back from a fresh listing and reports whether it changed. pnpm installs only releases older than its `minimumReleaseAge` setting (pnpm 11 defaults to 4320 minutes), so a very recent Pi release is picked up once it is old enough; Shell reports that rule instead of overriding it. Package managers run from the system temporary directory so a project's `packageManager` pin never changes which pnpm answers. Use the supplying package manager for source checkouts or unsupported layouts. `gentle-shell update --extensions-only` remains an extension-only Pi command; prefix-bundled Pi still updates with `gentle-shell upgrade`. See [managing packages](readme-reference.md#managing-packages) for the ownership contract.
14
+
9
15
  ## v2.6.0 workspace updates
10
16
 
11
17
  The [v2.6.0 release](https://github.com/Gentleman-Programming/gentle-pi/releases/tag/v2.6.0) makes the workspace state more durable and inspectable:
@@ -70,14 +76,15 @@ Changes shows the **current Git state** — working tree against HEAD, untracked
70
76
 
71
77
  ### What appears in Changes
72
78
 
73
- - One group per root: the session cwd's repository, worktrees registered with `session_worktree_register` or launched for subagents, and independent repositories of authorized foreign subagents. Captured write/edit evidence only tells Changes which roots exist; Git decides which files are listed.
79
+ - One group per root: the session cwd's repository, worktrees registered with `session_worktree_register` or launched for subagents, and independent repositories of authorized foreign subagents. Captured write/edit evidence only tells Changes which roots exist; Git supplies the file inventory, with the dependency-backup exception below.
74
80
  - Committed work disappears because Git no longer reports it; a removed worktree disappears because its directory is gone; a root whose Git fails or times out is omitted while the others stay.
75
81
  - Each group header shows that root's branch, `no commits yet` for an unborn branch, or `detached` for a real detached HEAD.
76
- - Edits from your editor, shell commands, or other tools appear too: Changes reflects Git, not attribution. Ignore noise the same way you would for `git status`, with `.gitignore`.
82
+ - Edits from your editor, shell commands, or other tools appear too: Changes reflects Git, not attribution.
83
+ - Untracked files inside `node_modules.backup-*` directories are automatically excluded before counting lines, at any depth; no `.gitignore` setup is needed. The suffix must be nonempty. Tracked files in those directories and ordinary new source files remain visible. Other unwanted artifacts can still be excluded with `.gitignore`.
77
84
 
78
85
  ### Bounds and session lifetime
79
86
 
80
- Changes lists every changed file without a count limit. One selected diff is bounded at 1 MiB or 20,000 lines; beyond that the panel shows "Diff too large — press o to open in editor."
87
+ Changes lists every changed file outside the untracked dependency-backup exclusion without a count limit. One selected diff is bounded at 1 MiB or 20,000 lines; beyond that the panel shows "Diff too large — press o to open in editor."
81
88
 
82
89
  Captured evidence (`gentle-pi.session-change/v1`) is still stored for session statistics and root discovery, capped at 256 operations and 4 MiB per session, without affecting the Changes list. Reload and resume read the same state from Git; new sessions and forks do not inherit another session's worktrees.
83
90
 
@@ -112,11 +119,11 @@ The panel rows a provider reports its windows with:
112
119
  glm5.3-flash ▰▰▱▱▱▱▱▱▱▱▱▱▱▱▱▱ 11% · resets in 12d 17h
113
120
  ```
114
121
 
115
- - For Codex, usage comes from the same account usage endpoint the Codex CLI reads, using the OAuth token pi already holds. It is fetched at session start, at most every 5 minutes after a turn, and on `r` in the panel. Rate-limit headers on SSE responses are picked up too. Codex windows require a finite positive duration reported by the provider; missing or invalid durations are ignored, never shown as `0m` or replaced with an assumed `5h` quota. A real `0%` remains visible when its window is valid, and a response with no valid header windows leaves the previous snapshot untouched. The same refresh covers every provider the session targets: the active model's own provider plus every provider the active profile's subagent routing names — a repository pin decides which profile that is, falling back to the global active profile when no pin applies. Each provider keeps its own 5-minute window, its own stale-source guard, and its own last good snapshot. Providers refresh concurrently, each inside its own bounded window (default 10s, `GENTLE_PI_SHELL_USAGE_TIMEOUT_MS`): the abort signal reaches the underlying fetch — composed with the caller's own signal when it carries one, never replacing it — a provider that outlives its window wears the generic failure note, and whatever it answers afterwards is discarded — a late answer never replaces what the timeout settled, exactly like the stale-source guard above.
116
- - A routing entry names its provider with a qualified ref (`provider/model`); a bare model id is resolved through the model registry only when exactly one provider carries that id, and is left untargeted rather than guessed when none or several do. The targeted scope is resolved when a refresh runs — at session start, on each turn's throttled refresh, on `r` or reopening the panel, and when a usage source registers — so a profile switch is picked up by the next refresh rather than live per render.
122
+ - For Codex, usage comes from the same account usage endpoint the Codex CLI reads, using the OAuth token pi already holds. It is fetched at session start, at most every 5 minutes after a turn, and on `r` in the panel. Rate-limit headers on SSE responses are picked up too. Codex windows require a finite positive duration reported by the provider; missing or invalid durations are ignored, never shown as `0m` or replaced with an assumed `5h` quota. A real `0%` remains visible when its window is valid, and a response with no valid header windows leaves the previous snapshot untouched. The same refresh covers every provider the session targets: the active model's own provider plus every provider the session profile's subagent routing names — the same profile subagent launches use: the profile selected in this session, otherwise the profile the session froze at startup from the local pin, the repository declaration, or the global active profile (see [Session profile frozen at startup](readme-reference.md#session-profile-frozen-at-startup)). Each provider keeps its own 5-minute window, its own stale-source guard, and its own last good snapshot. Providers refresh concurrently, each inside its own bounded window (default 10s, `GENTLE_PI_SHELL_USAGE_TIMEOUT_MS`): the abort signal reaches the underlying fetch — composed with the caller's own signal when it carries one, never replacing it — a provider that outlives its window wears the generic failure note, and whatever it answers afterwards is discarded — a late answer never replaces what the timeout settled, exactly like the stale-source guard above.
123
+ - A routing entry names its provider with a qualified ref (`provider/model`); a bare model id is resolved through the model registry only when exactly one provider carries that id, and is left untargeted rather than guessed when none or several do. The targeted scope is resolved when a refresh runs — at session start, on each turn's throttled refresh, on `r` or reopening the panel, and when a usage source registers — so a profile selected in the session (or a default change in `follow` mode) is picked up by the next refresh rather than live per render.
117
124
  - For Claude Pro/Max, usage arrives in the rate-limit headers of every response, so the 5h and weekly windows appear after the first turn.
118
125
  - For NaN Cloud, usage comes from the quota endpoint the official dashboard reads, with the same API key pi already holds. Each metered model reports one allowance for the billing period, and that window carries no label: the model id names it in the bar and the reset text says what it is in the panel. A model that also reports a rolling window shows that one labeled next to it (`4h`), which today's payload does not send; percentages are tokens used over the allowance, exactly as the dashboard draws them, and the allowance is the full-period cap (`fullCap`) whenever the model reports a positive one, because `cap` alone is the prorated allowance of the period in progress. It is fetched under the same 5-minute rule as Codex, counted per provider so a switch fetches the provider it switched to, refuses redirects so the bearer cannot be replayed to another origin, and keeps no cached copy. The endpoint sits outside NaN's published OpenAPI, so the parser reads it defensively: a model that reports no allowance is skipped, as the dashboard skips it, while a metered model whose usage cannot be read fails the whole read, so a partial payload never replaces a complete snapshot with a cheaper-looking one. A session that already has a snapshot keeps the last valid one through a malformed payload or a failed fetch, and the pending note appears only while there is nothing to draw.
119
- - Extensions can register a usage source for their own provider: gentle-shell has no built-in knowledge of it, but treats it exactly like Codex or NaN once registered. Emit `gentle-pi:usage-source/v1` on `pi.events` with `{ schema: "gentle-pi.usage-source/v1", provider, pendingNote?, fetch(apiKey, fetchFn, now) }`, where `fetch` resolves a `ProviderUsage` the same shape the built-in providers produce, or `undefined` when there is nothing to show yet. A malformed payload, a `fetch` that isn't a function, a provider id outside the safe id pattern, or a `fetch` call that throws or rejects is ignored rather than crashing the shell. The `fetchFn` a source receives is bounded the same way: once the provider's window expires it aborts, and a later resolution is discarded. Re-registering the same provider replaces its source, so emitting again at every `session_start` is safe and keeps load order irrelevant. Once registered, the provider shows `pendingNote` (or the same "no usage yet · r to fetch" default the built-ins use) until its first fetch, and a registration that arrives after the session already started, for any targeted provider (the session's own or a subagent route of the active profile), triggers one immediate refresh of that provider instead of waiting for the next turn or the 5-minute window. Example, using a neutral provider id:
126
+ - Extensions can register a usage source for their own provider: gentle-shell has no built-in knowledge of it, but treats it exactly like Codex or NaN once registered. Emit `gentle-pi:usage-source/v1` on `pi.events` with `{ schema: "gentle-pi.usage-source/v1", provider, pendingNote?, fetch(apiKey, fetchFn, now) }`, where `fetch` resolves a `ProviderUsage` the same shape the built-in providers produce, or `undefined` when there is nothing to show yet. A malformed payload, a `fetch` that isn't a function, a provider id outside the safe id pattern, or a `fetch` call that throws or rejects is ignored rather than crashing the shell. The `fetchFn` a source receives is bounded the same way: once the provider's window expires it aborts, and a later resolution is discarded. Re-registering the same provider replaces its source, so emitting again at every `session_start` is safe and keeps load order irrelevant. Once registered, the provider shows `pendingNote` (or the same "no usage yet · r to fetch" default the built-ins use) until its first fetch, and a registration that arrives after the session already started, for any targeted provider (the session's own or a subagent route of the session profile), triggers one immediate refresh of that provider instead of waiting for the next turn or the 5-minute window. Example, using a neutral provider id:
120
127
 
121
128
  ```ts
122
129
  pi.events.emit("gentle-pi:usage-source/v1", {
@@ -204,7 +211,7 @@ Gentle Shell ships its own interactive tools instead of depending on third-party
204
211
 
205
212
  ### Gentle Agents
206
213
 
207
- The current package requires Pi 0.99.1 or newer and Node >=22.19.0. Development tests resolve Pi through the open `>=1.0.0` development range. The private Vim editor adapter admits only the audited Pi `0.99.1`, `0.99.2`, `1.0.0`, and `1.1.0` releases; any other release keeps ordinary prompt editing until its editor is audited. Use the latest Pi release; gentle-pi does not update your installed Pi automatically. Children, including any `GENTLE_PI_AGENTS_PI` override, must emit `agent_settled`: `agent_end` records a run's output but is not completion because retries or queued continuations may follow.
214
+ The current package requires Pi 1.2.0 or newer and Node >=22.19.0. Development tests resolve Pi through the open `>=1.0.0` development range. The private Vim editor adapter admits only the audited Pi `0.99.1`, `0.99.2`, `1.0.0`, `1.1.0`, and `1.2.0` releases; any other release keeps ordinary prompt editing until its editor is audited. Use the latest Pi release; gentle-pi does not update your installed Pi automatically. Children, including any `GENTLE_PI_AGENTS_PI` override, must emit `agent_settled`: `agent_end` records a run's output but is not completion because retries or queued continuations may follow.
208
215
 
209
216
  The `subagent_*` tools and the agents card replace the third-party subagents package (remove `npm:pi-subagents-j0k3r` from your pi packages; while it is still installed the tools stay unregistered and a warning says so at startup). Agent definitions and settings are the ones you already have: markdown agents in `~/.pi/agent/agents/`, `~/.pi/agent/subagents/`, `<cwd>/.pi/agents/`, `<cwd>/.pi/subagents/` (project beats global, `subagents/` beats `agents/`), and `subagents.json` at the global and project level (`default_model`, `default_effort`, `default_mode`, `model_profiles`, `stall_timeout_ms`, `tool_stall_timeout_ms`, `max_concurrency`, `history_max_tasks`).
210
217
 
@@ -284,8 +291,8 @@ The `todo` tool and its card replace the third-party todo extension (remove `npm
284
291
  The runtime helps keep the list current beyond a static tool description:
285
292
 
286
293
  - `write` replaces the whole list in one call, so the model rewrites the plan instead of patching it; `add`, `update`, `clear`, and `list` remain for single moves.
287
- - Each agent run's system prompt carries the open tasks and the rules: in_progress before starting, done right after finishing, update before ending the run.
288
- - A list that goes two agent runs untouched while pending or in-progress tasks remain turns amber with `stale · N turns`, and the prompt reports that age.
294
+ - Each agent run's system prompt carries the same byte-stable rules block (in_progress before starting, done right after finishing, update before ending the run), so the cached prompt prefix never changes because of the list. While tasks stay open, the current list reaches the model as a hidden, request-local message at the end of each request; it is never persisted and never enters the system prompt, so status, title, note and ordering changes cost only the tail of the request.
295
+ - A list that goes two agent runs untouched while pending or in-progress tasks remain turns amber with `stale · N turns` on the card; that age is shown to the human only.
289
296
  - Within a long run, after four tool-use model turns without a successful todo write, the next model request receives one hidden, request-local reminder to review the plan. A successful `write`, `add`, `update`, or `clear` resets and rearms it; `list` and rejected writes do not. The turn containing the write does not count toward the threshold. Empty, finished and blocked-only plans receive no reminder. Reminder tracking restarts when the session loads; it does not change the existing card freshness, persist a message, start an extra model turn, change task statuses, or connect to subagents.
290
297
 
291
298
  Tasks are `pending` (`○`), `in_progress` (`◐`), `blocked` (`⊘`), `done` (`✓`), or `dropped` (`✕`):
@@ -295,7 +302,7 @@ Tasks are `pending` (`○`), `in_progress` (`◐`), `blocked` (`⊘`), `done` (`
295
302
 
296
303
  A finished list stays on screen for the turn it finished in and clears at the next. `ctrl+shift+t` collapses the card to one task line, truncating long titles and notes with `…` (`GENTLE_PI_TODO_KEY` rebinds it, `off` disables it); `GENTLE_PI_TODO=0` disables the tool and the card.
297
304
 
298
- Expanded cards reserve at most one third of the terminal height (up to 16 rows, with a minimum for card chrome), both in the sidebar and above the editor on narrow/mobile terminals. Scroll the full task text with the wheel in fullscreen mode or `ctrl+shift+up` / `ctrl+shift+down` in either mode. The collapse header stays visible; the row-range hint shows when more content is available. Task data and the agent's plan remain complete.
305
+ Expanded cards reserve at most one third of the terminal height (up to 16 rows, with a minimum for card chrome), both in the sidebar and above the editor on narrow/mobile terminals. Scroll the full task text with the wheel in fullscreen mode or `alt+shift+up` / `alt+shift+down` in either mode (Pi keeps `ctrl+shift+up` / `ctrl+shift+down` for its own prompt navigation). The collapse header stays visible; the row-range hint shows when more content is available. Task data and the agent's plan remain complete.
299
306
 
300
307
  ### Gentle Stats
301
308
 
@@ -58,7 +58,7 @@ it lets the window close.
58
58
 
59
59
  Current requirements come from `package.json` and
60
60
  [`gentle-ai-installer.mjs`](../scripts/gentle-ai-installer.mjs): Node ≥22.19.0,
61
- Pi ≥0.99.1, pnpm acquisition 11.1.1, Gentle Shell package 4.0.0 and package-local
61
+ Pi ≥1.2.0, pnpm acquisition 11.1.1, Gentle Shell package 4.0.0 and package-local
62
62
  Gentle AI 4.1.1. Windows native source builds require Go ≥1.25.10 when the
63
63
  required usable native binary is missing. A reusable binary does not require Go.
64
64
  When a build needs Go and the user's Go is missing, older or unchecked, the installer uses
@@ -489,9 +489,9 @@ Mutating and verification steps, returning `failed` with `failedStep` and the
489
489
  [runtime persistence](#runtime-persistence) steps run next:
490
490
 
491
491
  1. `install-global`: exactly one
492
- `pnpm add -g @earendil-works/pi-coding-agent@1.0.0 gentle-pi@<package version> --allow-build=gentle-pi`.
492
+ `pnpm add -g @earendil-works/pi-coding-agent@1.2.0 gentle-pi@<package version> --allow-build=gentle-pi`.
493
493
  pnpm 11 installs each argument as its own global group, so gentle-pi gets
494
- its optional peer Pi beside it, possibly newer than 1.0.0
494
+ its optional peer Pi beside it, possibly newer than 1.2.0
495
495
  ([The Pi that Gentle Shell runs](#the-pi-that-gentle-shell-runs)). pnpm 11
496
496
  silently skips global postinstall scripts by default; the package-scoped
497
497
  approval runs only gentle-pi's postinstall, which provisions Gentle AI. No
@@ -503,8 +503,8 @@ Mutating and verification steps, returning `failed` with `failedStep` and the
503
503
  launcher runs from that real path, as Node resolves it (gentle-pi's own
504
504
  `node_modules` first, then beside gentle-pi), must be a
505
505
  `@earendil-works/pi-coding-agent` package at a stable version at least the
506
- minimum (0.99.1); a missing, unreadable, prerelease or older one fails. When
507
- it is not 1.0.0, the outcome reports it as `piVersion` and the wizard says
506
+ minimum (1.2.0); a missing, unreadable, prerelease or older one fails. When
507
+ it is not 1.2.0, the outcome reports it as `piVersion` and the wizard says
508
508
  which Pi Gentle Shell runs.
509
509
  3. `verify-shell-bin`: `$PNPM_HOME/bin/gentle-shell` (`gentle-shell.cmd` on
510
510
  Windows) exists.
@@ -563,7 +563,7 @@ installation instead of blocking as an existing stack.
563
563
  Detection is the setup probe, with the same rule the runner applies
564
564
  (`recoverableStackRoot`): the global listing names Pi and gentle-pi exactly
565
565
  once each, both in the single project that owns gentle-pi, Pi at exactly
566
- 1.0.0 and gentle-pi at exactly this package's version, gentle-pi's path
566
+ 1.2.0 and gentle-pi at exactly this package's version, gentle-pi's path
567
567
  resolves (realpath) inside PNPM_HOME, and the Pi beside gentle-pi is at least
568
568
  the minimum. Preflight also requires Pi, Shell and
569
569
  Gentle AI to be `reusable`. Anything else (another version, a second listing,
@@ -650,7 +650,7 @@ Pi but never reinstalls or downgrades it, and nothing changes before consent.
650
650
 
651
651
  | Found | Plan |
652
652
  | --- | --- |
653
- | A compatible Pi (pnpm-global, or any `pi` on PATH whose `pi --version` reports a stable version ≥ the minimum) and no Gentle Shell | Install only Gentle Shell: `pnpm add -g gentle-pi@<version> --allow-build=gentle-pi`, after checking that pnpm lists no gentle-pi (`check-existing-shell`). Pi is left as it is, also when it is newer than 1.0.0. |
653
+ | A compatible Pi (pnpm-global, or any `pi` on PATH whose `pi --version` reports a stable version ≥ the minimum) and no Gentle Shell | Install only Gentle Shell: `pnpm add -g gentle-pi@<version> --allow-build=gentle-pi`, after checking that pnpm lists no gentle-pi (`check-existing-shell`). Pi is left as it is, also when it is newer than 1.2.0. |
654
654
  | A Gentle Shell that pnpm or npm owns | `update-shell-release` when it is older, unusable or a main build; `update-shell-main` on the main channel (needs Go). Then `setup-shell`. A missing Pi is installed first (`install-pi`). |
655
655
  | A current Gentle Shell that npm owns, on release | Nothing to do. |
656
656
  | A Pi older than the minimum that pnpm or npm owns | `update-pi` to `PI_INSTALL_VERSION` with that package manager (`pnpm add -g @earendil-works/pi-coding-agent@<version>` or `npm install -g …`), before any Gentle Shell step: ahead of `install-shell` or `update-shell-*`, or alone when Gentle Shell is current. The plan names the found and target versions and the manager. |
@@ -677,7 +677,7 @@ the same code as `gentle-shell upgrade --channel <channel>`
677
677
  (`check-installed-shell`, `update-shell`), then `verify-updated-shell` requires
678
678
  the same owner and a stable version not older than before (release) or a
679
679
  `-main.<sha12>` version (main), and for pnpm a Pi beside the updated
680
- gentle-pi at least the minimum (its version is reported when it is not 1.0.0);
680
+ gentle-pi at least the minimum (its version is reported when it is not 1.2.0);
681
681
  release also re-runs `verify-gentle-ai`. An
682
682
  update never runs `pnpm setup`: the existing installation already has its PATH.
683
683
 
@@ -734,7 +734,7 @@ PNPM_HOME, `pnpm add -g @earendil-works/pi-coding-agent@1.0.0 gentle-pi@4.0.0`
734
734
  lists Pi 1.0.0 while the Pi beside gentle-pi is 1.1.0. So Gentle Shell may run a
735
735
  Pi newer than `PI_INSTALL_VERSION`, always at least the minimum: the runner
736
736
  verifies that Pi (`verify-global-list`, the setup recovery, a pnpm
737
- `verify-updated-shell`) and, when it is not 1.0.0, the outcome says which Pi
737
+ `verify-updated-shell`) and, when it is not `PI_INSTALL_VERSION` (1.2.0 now; the 1.0.0/1.1.0 figures above are that older verification), the outcome says which Pi
738
738
  Gentle Shell runs. `--config.auto-install-peers=false` would leave gentle-pi
739
739
  with no Pi beside it, so the launcher would run any `pi` on PATH, and
740
740
  `--config.overrides` is ignored for global installs.