pi-goal-list-loop-audit 0.34.138 → 0.35.4

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 (43) hide show
  1. package/README.md +119 -76
  2. package/docs/DESIGN.md +103 -16
  3. package/docs/RELEASING.md +7 -4
  4. package/extensions/drafter-model.ts +97 -0
  5. package/extensions/faulty-objective-recovery.ts +4 -0
  6. package/extensions/goal-commands.ts +79 -23
  7. package/extensions/goal-continuation.ts +117 -25
  8. package/extensions/goal-heartbeat.ts +73 -11
  9. package/extensions/goal-loop-auditor-process.ts +235 -20
  10. package/extensions/goal-loop-auditor.ts +1 -1
  11. package/extensions/goal-loop-backoff.ts +9 -69
  12. package/extensions/goal-loop-core.ts +266 -88
  13. package/extensions/goal-loop-display.ts +143 -86
  14. package/extensions/goal-loop-forever.ts +37 -4
  15. package/extensions/goal-loop-subagents.ts +37 -17
  16. package/extensions/goal-loop.ts +106 -15
  17. package/extensions/goal-recovery.ts +354 -154
  18. package/extensions/goal-settings.ts +181 -53
  19. package/extensions/goal-state.ts +1 -1
  20. package/extensions/loops/goal-activation.ts +146 -84
  21. package/extensions/loops/goal-auditor-hooks.ts +160 -113
  22. package/extensions/loops/goal-list-queue.ts +236 -14
  23. package/extensions/loops/goal-orchestrator.ts +53 -48
  24. package/extensions/loops/goal-runtime-globals.ts +8 -4
  25. package/extensions/loops/goal-session.ts +167 -38
  26. package/extensions/loops/goal-settings-ui.ts +251 -63
  27. package/extensions/loops/goal-tools.ts +106 -48
  28. package/extensions/loops/goal-ui.ts +13 -18
  29. package/extensions/loops/goal.ts +3 -5
  30. package/extensions/main-model-recovery.ts +74 -77
  31. package/extensions/model-picker.ts +48 -3
  32. package/extensions/model-selector.ts +13 -1
  33. package/extensions/multi-model-picker.ts +224 -24
  34. package/extensions/quota-retry.ts +113 -94
  35. package/extensions/reviewer.ts +0 -5
  36. package/extensions/settings-menu.ts +203 -135
  37. package/package.json +8 -7
  38. package/prompts/goal-loop-continuation.md +15 -9
  39. package/prompts/goal-loop-draft.md +10 -0
  40. package/prompts/goal-loop-forever-draft.md +8 -0
  41. package/schemas/goal.schema.json +64 -9
  42. package/scripts/goal-auditor-worker.mjs +66 -6
  43. package/scripts/smoke.sh +115 -11
package/README.md CHANGED
@@ -10,7 +10,7 @@ This is a detached process, not a nested session in the main pi process. `comple
10
10
 
11
11
  On Windows, npm installs the `pi.cmd` shim rather than a directly executable `pi` binary. The auditor launches it through an explicitly quoted `cmd.exe` boundary; POSIX keeps direct shell-less execution. Protocol snapshots also tolerate transient Windows file-locks without deleting the last valid snapshot first.
12
12
 
13
- **Current package version:** `v0.34.138` — use `/glla version` to see the installed version and the command for comparing it with the registry latest. This checkout may contain unreleased changes; the npm registry is authoritative for published versions.
13
+ **Current package version:** `v0.35.3` — use `/glla version` to see the installed version and the command for comparing it with the registry latest. This checkout may contain unreleased changes; the npm registry is authoritative for published versions.
14
14
 
15
15
  ## Why this exists
16
16
 
@@ -122,6 +122,26 @@ ONE confirmed batch, not 50 dialogs.
122
122
  Note: every list item is audited individually, so at hundreds of items the
123
123
  audit cost per item is the thing to think about.
124
124
 
125
+ Long-running judgment is explicit: the drafter preserves the objective and
126
+ verification contract, prefers a durable root-cause fix, allows a safe,
127
+ reversible workaround when it is genuinely useful, and asks only at an actual
128
+ scope/permission/irreversible-action decision boundary. If you want a design
129
+ checkpoint before implementation, mark a goal, list item, or task-plan entry
130
+ with `Agent: Designer` (also accepted: `Role: designer` or `Designer: yes`).
131
+ The managed Designer is read-only; if it is unavailable, the main agent keeps
132
+ the same checkpoint inline.
133
+
134
+ Drafting can use a separate temporary agent configured in `/glla` under the
135
+ **Drafter** tab (`Drafter agent`, `Drafter thinking`, and `Drafter fallback
136
+ agents`).
137
+ The thinking choice is derived from the selected model, applies only while the
138
+ drafting agent is active, and restores the original session thinking level
139
+ after confirmation or interruption. A drafting failure retries the existing
140
+ interview through that agent's fallback chain. Main-agent and auditor-agent
141
+ recovery chains remain separate. Provider recovery is intentionally blind:
142
+ there is no live quota checking or reset inference; the generic retry envelope
143
+ and the optional hourly `:00:30` probe remain the policy.
144
+
125
145
  **Drafting is the default for long-running things.** `/goal` and
126
146
  `/loop` with no arguments — and any vague `/list` dump — all start a
127
147
  grilling turn that ends in a Confirm dialog. For `/loop` specifically, the orchestrator **test-runs the proposed
@@ -231,13 +251,17 @@ A persistent `glla:` status segment + an above-editor widget show the current
231
251
  goal/list item/loop at all times: objective, durable state, elapsed time,
232
252
  tokens, next task or loop metric, pause reason, and live auditor progress
233
253
  during audits. If something is running, you can see it — no command needed.
254
+ Goal cards label their total wall-clock age explicitly; it includes time parked
255
+ for recovery or waiting on a verdict, so it should not be read as active model
256
+ compute. Recovery cards call the timestamp `last host activity` for the same
257
+ reason: a retry/error event is liveness evidence, not proof of useful work.
234
258
 
235
259
  The status bar is the single activity HUD. It uses compact state capsules plus
236
260
  an animated pulse waveform so live work is obvious at a glance without turning
237
261
  the line into a progress meter. Fresh stream age is the proof of live work:
238
262
 
239
263
  ```text
240
- glla: [▁▂▄▆█▆ LIVE · WORKING] 1m 09s · last stream 11s ago · 3 queued
264
+ glla: [▁▂▄▆█▆ LIVE · WORKING] total 1m 09s · last stream 11s ago · 3 queued
241
265
  glla: [QUEUED] 44s · 18 queued
242
266
  ```
243
267
 
@@ -252,63 +276,62 @@ Activity is otherwise intentionally honest:
252
276
  | `QUEUED` | A continuation is waiting to start; no work is fabricated. |
253
277
  | `IDLE` | The durable item remains active, but no recent work is observed. |
254
278
  | `auditor …` | A detached, extension-less verifier is queued, running, quiet, or waiting for its verdict. |
255
- | `QUOTA WALL` | The provider rejected the request for a quota/plan window (by its own wording); saved work is waiting for a durable probe. |
256
-
257
- ## Provider failures: one retry envelope, bounded (v0.34.51)
258
-
259
- Error text is **not trusted** to pick a retry policy: we only know that an
260
- error came, and provider messages vary. Every main-model failure quota,
261
- rate limit, billing/credits, auth, transient, or unclassifiable — rides the
262
- same durable recovery envelope `15m 30m → 1h → 2h → 4h → 5h` (probe cap
263
- 5h, automatic window 24h, then an explicit `/goal resume`/`/list resume`/
264
- `/loop resume` starts a fresh window). The only failures that do not auto-retry
265
- are the ones identified by *positive evidence* as futile: context/output-token
266
- limits and user aborts (`non-recoverable`), plus auditor watchdog timeouts
267
- (a hanging verification command will hang again the stored claim waits for
268
- an explicit resume).
269
- A provider hint (`retry_after`/`reset_at`) is honored when it fits the
270
- five-hour probe budget; an over-budget hint (e.g. a week-long reset) never
271
- parks the goal the bounded cadence owns the wait, and only the 24h horizon
272
- ends automatic probes (a `/goal resume`/`/list resume`/`/loop resume` then
273
- starts a fresh window). With global `autoResume=on`, pending
274
- probes survive a session reload. For continuous work, configure ordered
275
- **Main model backups** in `/glla` using a model from a different provider or
276
- billing/quota pool another model on the same exhausted plan is not a real
277
- fallback.
278
-
279
- **Quota walls engage fast** (v0.34.57): a surfaced long-lived failure
280
- (quota / billing / auth) records a 30-minute knowledge window; a send-rearm
281
- storm inside that window escalates into the recovery envelope after **3
282
- minutes** of failed sends instead of the generic 15 a wedge right after a
283
- quota wall is almost always the same wall. Transient (5xx/stream/network)
284
- failures never record the signal and keep the fast error ladder. The
285
- envelope is armed by configuration: an empty `mainModelFallbacks` list means
286
- "park and probe the same model" rather than switching pools the
287
- never-switch posture is a first-class policy, not an accident.
288
-
289
- Classification still exists, but it only *labels*: the card and badge show
290
- what the provider said (quota wall, billing, rate limit) so the reason is
291
- diagnosable, and `QUOTA WALL` is only shown when the provider's own words say
292
- quota ambiguous prose is never relabeled. The raw provider message stays in
293
- the ledger/durable state; the card shows the classified reason and recovery
294
- action. Detached-auditor failures get the same treatment: any infrastructure
295
- error on a stored completion claim pauses the goal with a durable bounded
296
- one-shot retry (`auditor retry: …`), and only the plan's 24h horizon stops
297
- automatic probes.
298
-
299
- The quota-specific card hides raw provider JSON while preserving it in durable
300
- state and the ledger:
301
-
302
- ```text
303
- glla: ⟦⏳ QUOTA WALL · next probe in 10m 48s⟧ · 1 queued
304
- ├─ QUOTA WALL · Token Plan usage limit · 1 waiting in list
305
- ├─ waiting nothing for you to do · next probe in 10m 48s
306
- ```
307
-
308
- Increasing pi's per-request retry count is usually the wrong fix for a
309
- multi-hour plan cap: it prolongs the 429 noise and delays the durable pause;
310
- it does not make the provider reset sooner. A provider that says "reset in a
311
- week" therefore does not cause a week of unattended probes.
279
+ | `RECOVERING` | A bounded automatic retry is pending; the status does not guess why the provider failed. |
280
+
281
+ Detached-auditor elapsed time keeps advancing between worker events. A long
282
+ tool call or hidden reasoning interval therefore remains visibly timed; after
283
+ three minutes without worker activity the UI changes to `auditor quiet` and
284
+ names the cancellation/recovery path instead of presenting a frozen live card.
285
+
286
+ ## Provider failures: aggressive retry envelope, bounded (v0.35.0)
287
+
288
+ Error text is **not trusted** to pick a retry policy. The runtime does not
289
+ query or infer provider quota state, and it does not use status codes, billing
290
+ words, rate-limit words, or `Retry-After` hints to choose a branch. Those
291
+ values are retained only as bounded diagnostics. Every recoverable main-model
292
+ failure uses the same durable envelope: an eager 5-second retry, then
293
+ `base 2×base 4×base 8×base 16×base → 5h`, where `base` is the
294
+ `mainModelRetryMinutes` setting (15 minutes by default). `hourlyRetryProbe=on`
295
+ adds a blind `:00:30` retry after each hour starts, so work can be picked up
296
+ quickly after a possible provider-side change. The automatic window is 24h;
297
+ explicit `/goal resume`, `/list resume`, or `/loop resume` starts a fresh
298
+ window. With global `autoResume=on`, pending retries survive a session reload.
299
+
300
+ Only failures identified by positive evidence as futile avoid automatic retry:
301
+ context/output-token limits and user aborts (`non-recoverable`). Auditor
302
+ watchdog timeouts are also kept separate because rerunning a hanging local
303
+ verification command immediately would repeat the same local failure; the
304
+ stored claim remains available for explicit resume.
305
+
306
+ For continuous work, configure up to **10 ordered Main-agent fallback models**
307
+ in `/glla` using independent model references when possible. The editor is in
308
+ the **Main agent** settings tab (one role per tab, with the current agent,
309
+ thinking level, and fallback chain together). It shows the actual try order as
310
+ `current fallback 1 fallback 2 …`, shows each configured fallback's rank,
311
+ lets **Space** add/remove a fallback, and **Tab** enters order mode where
312
+ **↑/↓** moves the highlighted fallback (brackets `[` `]` also reorder without
313
+ leaving the list). Every recoverable provider failure uses the same ordered
314
+ chain: glla calls `setModel` for the first eligible fallback, the next
315
+ supervised turn tests it, and later failures advance left-to-right. Forbidden,
316
+ unavailable, and unauthenticated references are skipped; a successful
317
+ supervised turn clears the episode. The chain is global, durable, and its
318
+ attempted cursor survives reload. After the chain is exhausted, bounded
319
+ retries continue on the active model rather than silently abandoning work.
320
+ The Main agent tab shows the `N/10` count and numbered chain. The Drafter and
321
+ Auditor tabs likewise show each selected model together with its requested
322
+ thinking level; fallback rows show the effective/requested thinking level when
323
+ the model registry exposes the capability map. Press `d` in the settings table
324
+ to toggle the long descriptions and give the model/value column the available
325
+ width.
326
+
327
+ Provider payloads are never copied into chat cards or notifications. A bounded
328
+ diagnostic may remain in the ledger and durable state for forensics, while
329
+ user-facing surfaces use the same generic provider-error label for every
330
+ failure family. The detached auditor follows the same rule: every retriable
331
+ infrastructure failure gets one eager 5-second retry, then stored-claim
332
+ retries at `:00:30` after each hour starts. Its existing 5-attempt/24-hour
333
+ safety envelope prevents an unbounded worker storm; explicit resume starts a
334
+ fresh window.
312
335
 
313
336
  For long-running `/list` work, the card adds a compact queue trail with the
314
337
  immediate next item and its truthful wait age while `/list` remains the
@@ -410,31 +433,48 @@ confirmation is intentionally not bypassed.
410
433
  Open `/glla` to edit these settings in the table (the rows show effective values and provenance):
411
434
 
412
435
  - Auditor model and thinking level
413
- - Auditor fallback model
436
+ - Auditor fallback agent
414
437
  - Notify command, token limit, and wedge-alert minutes
415
438
  - Auto-resume, auto-accept drafts, decision popup, and carryover policy
416
- - Ordered main-session backups and recovery cadence
417
- - Audit cap/report size, aggressive mode, quota retry, and stall brakes
439
+ - Main-agent current model/thinking, fallback models, and recovery cadence in the Main agent tab
440
+ - Drafter agent/thinking/fallback agents in the Drafter tab
441
+ - Auditor agent/thinking/fallback agent in the Auditor tab
442
+ - Forbidden model patterns and switch policy
443
+ - Audit cap/report size, aggressive mode (ON by default), retry cadence, and
444
+ stall brakes
418
445
 
419
446
  The argument namespace is reserved for actions such as `/glla status`, `/glla
420
447
  resume`, `/glla cancel`, `/glla stats`, `/glla audits`, `/glla tooloverride`,
421
- and `/glla wipe`. Cancel stops the active objective; wipe clears all live state
422
- while preserving history.
448
+ `/glla fallbacks clear`, and `/glla wipe`. `fallbacks clear` atomically removes
449
+ the global main-agent fallback chain and cancels any pending fallback switch. Cancel stops the
450
+ active objective; wipe clears all live state while preserving history.
423
451
  There is no top-level `/glla key=value` setting syntax.
424
452
 
425
- Resolution per key: **project > global > defaults** — EXCEPT `autoResume`,
426
- which is **global-only** (v0.29.5): per-project opt-ins from old versions
453
+ Resolution per key: **project > global > defaults** — EXCEPT `autoResume` and
454
+ agent recovery settings (`mainModelFallbacks`, `mainModelRetryMinutes`,
455
+ `drafterModel`, `drafterThinkingLevel`, `drafterModelFallbacks`,
456
+ `hourlyRetryProbe`),
457
+ which are **global-only**: per-project opt-ins from old versions
427
458
  silently overrode the global hold at launch (the junk-runner incident), so
428
459
  the launch-restore gate and the reviewer-enqueue gate read only the global
429
- file now. Main-session backups are global and ordered: a quota/provider error
430
- switches to the next authenticated candidate before another supervised turn;
431
- when every candidate is down, glla cancels the provider-held retry and uses a
432
- bounded `15m 30m 1h 2h 4h 5h` probe ladder. Automatic recovery stops
433
- at 24h (or earlier when the provider supplies a reset beyond the five-hour
434
- budget), preserves the saved work, and requires an explicit `/goal resume`,
435
- `/list resume`, or `/loop resume` to start a fresh window. A quota window
436
- returning within that horizon therefore resumes saved work without manual
437
- intervention; no blind 50ms resend loop is introduced. The detached auditor uses an explicit cascade: primary
460
+ file now. Main-session recovery policy is likewise one global chain/cadence
461
+ for the active session. Main-agent fallback models are global and ordered (up to 10): a provider
462
+ failure selects fallback 1, then fallback 2, and so on, one supervised turn at a
463
+ time. The Main agent tab leads with the ordered-chain editor a multi-select
464
+ picker where Space toggles membership, Tab enters order mode (↑/↓ moves a
465
+ chain row), and clearing the selection removes the global key. Forbidden,
466
+ unavailable, and unauthenticated refs are skipped. When every candidate is
467
+ down, glla stops the current send attempt and uses the configured
468
+ `base 2×base 4×base 8×base 16×base 5h` ladder (`base` defaults to
469
+ 15m). `hourlyRetryProbe=on` adds a blind :00:30 retry after each hour starts.
470
+ No provider availability or quota check is made before any retry; all
471
+ recoverable failures walk the ordered fallbacks and then continue on the active
472
+ model through the bounded retry policy. Automatic recovery stops at 24h,
473
+ preserves the saved work, and requires an explicit
474
+ `/goal resume`, `/list resume`, or `/loop resume` to start a fresh window. A
475
+ provider becoming available within that horizon therefore resumes saved work
476
+ without manual intervention; no blind 50ms resend loop is introduced. The
477
+ detached auditor uses an explicit cascade: primary
438
478
  `auditorModel` → optional fallback pin → the pi session model. If a selected
439
479
  model fails after launch, the worker retries it once and then advances through
440
480
  that same cascade; every candidate is still audited in a detached,
@@ -501,7 +541,10 @@ in-session warning + your configured notify push, once per interval while
501
541
  it persists. Tune Wedge alert minutes in the `/glla` settings table (0 = off).
502
542
 
503
543
  Every other wait is bounded too: continuation retries are milliseconds,
504
- stuck backoff caps at 5 minutes then pauses, measure commands get a 10m
544
+ nudge accounting counts consecutive unproductive turns (substantive text
545
+ or tool calls reset the counter) and pauses the goal / stops the loop after
546
+ 3 — provider-error and user-abort turns are exempt (v0.27.3+). Measure
547
+ commands get a 10m
505
548
  hard timeout, and the detached auditor aborts after 10m with no activity while no
506
549
  an auditor tool is running. A long-running verification tool is allowed to
507
550
  finish, but each tool has an independent five-minute ceiling and the worker
package/docs/DESIGN.md CHANGED
@@ -189,21 +189,40 @@ architectural decisions that changed the SHAPE of the system:
189
189
 
190
190
  ## Addendum v0.34.31 (main-session model recovery)
191
191
 
192
- - **Ordered global backups**: `mainModelFallbacks` is an explicit ordered list
193
- of `provider/model` references. A provider/quota error can rotate the MAIN
194
- session through authenticated candidates; the detached auditor's model
195
- cascade remains a separate subsystem.
192
+ > Historical rollout note: this section records the older reason-aware
193
+ > recovery implementation. The active policy is the v0.34.142 addendum below;
194
+ > quota/rate-limit wording no longer selects fallback or retry timing.
195
+
196
+ - **Global recovery policy**: `mainModelFallbacks`,
197
+ `mainModelFallbackOnRateLimit`, `mainModelRetryMinutes`, and
198
+ `hourlyQuotaProbe` are global-only, so the runtime and settings provenance
199
+ cannot disagree about which recovery policy is active.
200
+ - **Ordered global fallback agents**: `mainModelFallbacks` is an explicit ordered list
201
+ of up to 10 `provider/model` references. A provider/quota error selects the
202
+ first eligible authenticated candidate for account/plan/billing/auth failures,
203
+ calls `setModel`, and lets the next supervised turn test it; later failures
204
+ walk the list left-to-right. Explicit HTTP 429/request-rate failures also
205
+ walk the list when global `mainModelFallbackOnRateLimit` is on (default); off
206
+ keeps them on the current model with bounded retry + hourly probe cadence.
207
+ The detached auditor's model cascade remains a separate subsystem. The
208
+ ordered-chain editor lives in the **Main agent** settings tab: Space toggles
209
+ membership, Tab enters an explicit order mode where ↑/↓ moves a chain row,
210
+ and clearing the list removes the global key. The attempted cursor is durable, so a reload cannot
211
+ restart at an already-failed rung.
196
212
  - **No accepted-send inference**: model rotation occurs only after a provider
197
213
  failure is observed (or after a 15-minute, five-minute-silent provider-held
198
214
  retry storm). A successful `sendMessage()` return is never treated as a
199
215
  started turn.
200
216
  - **Durable recovery instead of abandonment**: when all candidates fail,
201
217
  `.pi-glla/active.jsonl` stores the primary, active candidate, attempted set,
202
- retry time, and supervisor kind. Recovery probes back off 15m → 30m →
203
- hourly forever (configurable base), while a paused goal/held loop remains
204
- resumable. A fresh startup obeys the existing `autoResume` consent gate.
218
+ retry time, and supervisor kind. Unhinted recovery waits use the configured
219
+ `mainModelRetryMinutes` base and double per attempt up to 5h; factual
220
+ provider hints win when in budget, while `hourlyQuotaProbe` is a separate
221
+ optional :00:30 ticker. A paused goal/held loop remains resumable. A fresh
222
+ startup obeys the existing `autoResume` consent gate.
205
223
  - **Successful-turn reset**: a real non-error agent end clears the recovery
206
- cycle. Manual model selection cancels it; goal/list/loop cancellation clears
224
+ cycle. Manual model selection cancels it; host restore selections do not, and
225
+ user aborts do not masquerade as success. Goal/list/loop cancellation clears
207
226
  its timer and durable state.
208
227
 
209
228
  ## Addendum v0.34.48–v0.34.56 (lifecycle/recovery hardening — the stale-handle era)
@@ -240,16 +259,23 @@ replacement without delivering a successor `session_start`:
240
259
  counterpart are represented as explicitly unmatched facts, never falsely paired —
241
260
  the report surface stays truthful (the AuditProgress/AuditorProgress dual-interface
242
261
  rule: display evidence-gates on `unmatchedStarts + unmatchedEnds > 0`).
243
- - **Uniform retry envelope, no text-trust** (v0.34.51): error text is not trusted to
244
- pick a retry policy. Quota, billing, auth, transient, and unknown failures all ride
245
- ONE bounded durable envelope `15m 30m 1h → 2h → 4h → 5h` (cap 5h, automatic
246
- window 24h); classification only labels the display. The billing-hold special case
247
- is removed (`main_model_billing_hold` is legacy). Positive-evidence futile classes
248
- (context/output-token limits, user aborts) plus auditor watchdog timeouts never
249
- auto-retry; provider hints are honored only within the 5h probe budget.
262
+ - **Uniform retry envelope, no text-trust** (v0.34.51 historical baseline): error
263
+ text was not trusted to pick a retry policy. The current ordered-fallback policy
264
+ supersedes that baseline: account/plan/billing/auth failures may walk the configured
265
+ chain, while explicit HTTP 429/request-rate failures walk it when
266
+ `mainModelFallbackOnRateLimit` is on (default), or remain on the current model
267
+ when it is off. Both paths use the configured bounded ladder plus optional
268
+ hourly ticker. Positive-
269
+ evidence futile classes (context/output-token limits, user aborts) plus auditor
270
+ watchdog timeouts never auto-retry; provider hints are honored only within the
271
+ 5h probe budget.
250
272
 
251
273
  ## Addendum v0.34.57 (quota walls engage recovery fast)
252
274
 
275
+ > Historical rollout note: the knowledge-window and quota-specific branches
276
+ > described here are retained for design history only and are not active in
277
+ > v0.34.142.
278
+
253
279
  - **Knowledge-window escalation**: a surfaced long-lived failure (quota /
254
280
  billing / auth) records a 30-minute knowledge window. A send-rearm storm
255
281
  inside that window escalates into the recovery envelope after 3 minutes of
@@ -261,7 +287,25 @@ replacement without delivering a successor `session_start`:
261
287
  the 5s→3m error ladder and the pi-core retry budget.
262
288
  - **Armed by configuration**: the envelope is inert without
263
289
  `mainModelFallbacks` (rotation) — an empty list means "park and probe the
264
- same model" instead of switching pools.
290
+ same model" instead of switching pools. When the list is non-empty, the
291
+ runtime tries one eligible fallback at a time in persisted order, skips
292
+ forbidden/unavailable refs, and parks only after the ordered chain is
293
+ exhausted. Explicit HTTP 429/rate-limit errors remain request-rate signals,
294
+ not token-limit labels; the global `mainModelFallbackOnRateLimit` switch
295
+ controls whether they walk that chain or stay current, and the optional
296
+ :00:30 hourly ticker can retry them.
297
+
298
+ ## Addendum Unreleased (process ownership and bounded fallback hardening)
299
+
300
+ - **Worker ownership is durable**: detached auditor locks are rewritten with
301
+ the worker PID, allowing a replacement host to reap stale workers for the
302
+ same completion claim. Parent cancellation waits for exit and escalates to
303
+ the detached POSIX process group or Windows `taskkill /t` tree, so nested
304
+ shells/tests cannot outlive their worker.
305
+ - **Test/smoke teardown is explicit**: direct worker fixtures and the tmux smoke
306
+ harness clean their owned process trees and temporary directories on normal,
307
+ failed, or interrupted exits. Browser processes are not launched by this
308
+ package; external Chrome automation remains outside this lifecycle boundary.
265
309
 
266
310
  ## Addendum v0.35.x (one-shot parked completion-audit recovery)
267
311
 
@@ -281,6 +325,49 @@ replacement without delivering a successor `session_start`:
281
325
  remains held unless the existing Auto-resume policy or a validated lifecycle
282
326
  handoff supplies consent. `/goal resume` still starts a direct fresh audit.
283
327
 
328
+ ## Addendum v0.34.142 (generic provider recovery)
329
+
330
+ - **No quota availability check exists in the live policy**: provider text,
331
+ status codes, billing/rate-limit words, and upstream retry hints do not
332
+ select a recovery branch, fallback gate, or delay. They may remain as
333
+ bounded diagnostics for forensics and redaction only.
334
+ - **One retry envelope covers every recoverable provider failure**: the first
335
+ retry is eager at 5 seconds, later retries use the configured bounded
336
+ ladder, and the optional `hourlyRetryProbe` adds a blind `:00:30` retry
337
+ after every hour starts. Main-model recovery and detached-auditor recovery
338
+ use this same reason-agnostic rule, with existing context/user-abort and
339
+ safety-horizon exceptions.
340
+ - **Legacy state is inert**: old `quota-waiting` phases, quota-named retry
341
+ counters, and provider-hint fields are accepted only long enough to load
342
+ and normalize old files. Canonical persisted state uses `retry-waiting`,
343
+ `retryAttempts`, `retryFirstAt`, and `retryUntil`.
344
+
345
+ ## Addendum v0.35.0 (long-running judgment, Designer, and drafting chain)
346
+
347
+ - **Judgment is a prompt-level contract**: drafting and continuation preserve
348
+ the objective and verification contract, prefer durable root-cause fixes,
349
+ allow only safe/reversible/testable in-scope workarounds, and ask one focused
350
+ question only at a genuine scope, priority, permission, or irreversible-action
351
+ boundary. The unattended fallback is explicit and never infers provider or
352
+ quota state.
353
+ - **Designer routing is explicit, persisted, and read-only**: `Agent: Designer`,
354
+ `Role: designer`, and `Designer: yes` are declarations rather than natural
355
+ language classification. The role is carried by goals, queue items, and task
356
+ plans; continuation injects the hand-off; a managed `Designer.md` uses only
357
+ read/search tools; unavailable role/provider falls back to an inline design
358
+ checkpoint.
359
+ - **Drafting owns a temporary agent lease**: the `/goal`, `/list`, and `/loop`
360
+ interviews resolve a separate primary/fallback chain, select a model-specific
361
+ thinking level, retry the existing interview after generic provider errors,
362
+ and restore the original model and thinking level after confirmation or
363
+ interruption. A current-session last resort is bounded and does not enter
364
+ main-goal recovery. A generation fence and serialized restore prevent
365
+ stale-session or overlapping-draft agent changes.
366
+ - **Host replacement remains host-owned**: Pi event contexts do not expose the
367
+ command-only `newSession`/`fork`/`switchSession` operations. glla therefore
368
+ persists the work and truthfully asks for `/new` when no replacement boundary
369
+ arrives; the proposed event-safe host API is recorded in the audit docs.
370
+
284
371
  ## Addendum v0.4.0 (completion)
285
372
 
286
373
  - **Auditor compaction enabled** (flaw #3 — the last open one). Safety:
package/docs/RELEASING.md CHANGED
@@ -18,10 +18,13 @@ the repository.
18
18
  ## Release checklist
19
19
 
20
20
  Accumulated changes since the last release live under an `## Unreleased`
21
- section at the top of `CHANGELOG.md` (with the in-repo milestone labels such
22
- as `### 0.34.51`); the release commit renames that section to the released
23
- version. Do not invent version headers for work that was never tagged —
24
- untagged work stays under `Unreleased` until the release commit.
21
+ section at the top of `CHANGELOG.md`; each milestone gets its own
22
+ `## <version> <one-line title> (<date>)` header plus `###` topic
23
+ subsections (e.g. `## 0.35.3 live auditor clock and clearer recovery
24
+ timing (2026-08-15)`). The release commit renames the `Unreleased` section
25
+ to the released version. Do not invent version headers for work that was
26
+ never tagged — untagged work stays under `Unreleased` until the release
27
+ commit.
25
28
 
26
29
  ```bash
27
30
  npm version <major.minor.patch> --no-git-tag-version
@@ -0,0 +1,97 @@
1
+ // Drafting-only model resolution and fallback selection.
2
+ //
3
+ // Drafting runs in the main pi session, so the selected model is a temporary
4
+ // lease: the caller must restore the original session model when drafting
5
+ // ends. This module only resolves an ordered candidate list; lifecycle and
6
+ // retries remain in goal-list-queue.ts.
7
+
8
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
9
+
10
+ import { isForbiddenModel } from "./goal-loop-core.js";
11
+ import { MAX_MAIN_MODEL_FALLBACKS, modelRef, normalizeMainModelFallbackRefs } from "./main-model-recovery.js";
12
+ import { ModelSelector } from "./model-selector.js";
13
+ import type { Settings } from "./goal-settings.js";
14
+
15
+ export const MAX_DRAFTER_FALLBACKS = MAX_MAIN_MODEL_FALLBACKS;
16
+
17
+ export interface DrafterModelCandidate {
18
+ ref: string;
19
+ model: any;
20
+ via: "configured" | "session-last-resort";
21
+ }
22
+
23
+ export interface DrafterModelResolution {
24
+ configuredRefs: string[];
25
+ candidates: DrafterModelCandidate[];
26
+ selected?: DrafterModelCandidate;
27
+ }
28
+
29
+ /** Resolve a provider/model ref without making a provider request. */
30
+ export function resolveDrafterModelRef(ctx: Pick<ExtensionContext, "modelRegistry">, ref: string): any | undefined {
31
+ const trimmed = ref.trim();
32
+ if (!trimmed) return undefined;
33
+ const slash = trimmed.indexOf("/");
34
+ try {
35
+ if (slash > 0) {
36
+ const model = ctx.modelRegistry.find(trimmed.slice(0, slash), trimmed.slice(slash + 1));
37
+ if (!model || !ctx.modelRegistry.hasConfiguredAuth(model)) return undefined;
38
+ return model;
39
+ }
40
+ return ctx.modelRegistry
41
+ .getAvailable()
42
+ .filter((candidate: any) => candidate.id === trimmed || candidate.name === trimmed)
43
+ .find((candidate: any) => ctx.modelRegistry.hasConfiguredAuth(candidate));
44
+ } catch {
45
+ return undefined;
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Resolve the dedicated drafter chain. The current session model is always a
51
+ * final in-process fallback, even when every configured candidate is missing
52
+ * auth or forbidden. No error text, quota state, or retry hint is inspected.
53
+ */
54
+ export function resolveDrafterModel(ctx: ExtensionContext, settings: Pick<Settings, "drafterModel" | "drafterModelFallbacks" | "forbiddenModels">): DrafterModelResolution {
55
+ const primary = typeof settings.drafterModel === "string" ? settings.drafterModel.trim() : "";
56
+ const configuredRefs = normalizeMainModelFallbackRefs([
57
+ ...(primary ? [primary] : []),
58
+ ...normalizeMainModelFallbackRefs(settings.drafterModelFallbacks),
59
+ ]).slice(0, MAX_DRAFTER_FALLBACKS);
60
+ const currentRef = modelRef(ctx.model);
61
+ const forbidden = (ref: string) => isForbiddenModel(ref, settings.forbiddenModels);
62
+ const selector = new ModelSelector({
63
+ getChain: () => configuredRefs,
64
+ resolve: (ref) => resolveDrafterModelRef(ctx, ref),
65
+ isForbidden: forbidden,
66
+ });
67
+ const attempted: string[] = [];
68
+ const candidates: DrafterModelCandidate[] = [];
69
+
70
+ // ModelSelector deliberately skips the current model so the main recovery
71
+ // walker never selects the model that just failed. Drafting is different:
72
+ // its configured primary may intentionally be the current session model,
73
+ // and that primary still needs a lease so its later fallbacks remain
74
+ // available if the first drafting turn fails.
75
+ const configuredPrimary = configuredRefs[0];
76
+ if (
77
+ configuredPrimary &&
78
+ currentRef &&
79
+ configuredPrimary.toLowerCase() === currentRef.toLowerCase() &&
80
+ !forbidden(configuredPrimary) &&
81
+ ctx.model
82
+ ) {
83
+ candidates.push({ ref: configuredPrimary, model: ctx.model, via: "configured" });
84
+ attempted.push(configuredPrimary);
85
+ }
86
+
87
+ for (;;) {
88
+ const selected = selector.selectNextValid({ kind: "drafter" }, currentRef, attempted);
89
+ if (!("model" in selected) || typeof selected.ref !== "string") break;
90
+ attempted.push(selected.ref);
91
+ candidates.push({ ref: selected.ref, model: selected.model, via: "configured" });
92
+ }
93
+ if (ctx.model && currentRef && !candidates.some((candidate) => candidate.ref.toLowerCase() === currentRef.toLowerCase())) {
94
+ candidates.push({ ref: currentRef, model: ctx.model, via: "session-last-resort" });
95
+ }
96
+ return { configuredRefs, candidates, selected: candidates[0] };
97
+ }
@@ -185,6 +185,10 @@ function proposalFrom(
185
185
  function approvedCompletionContext(goal: Goal): string | null {
186
186
  const latest = goal.auditHistory?.at(-1);
187
187
  if (!latest?.approved || latest.disapproved || latest.regressionShieldPassed === false) return null;
188
+ // A completion summary approved for an older contract is not saved intent
189
+ // for the current objective. Legacy verdicts without a revision retain the
190
+ // historical compatibility policy; revisioned verdicts must match exactly.
191
+ if (latest.revision !== undefined && goal.revision !== undefined && latest.revision !== goal.revision) return null;
188
192
  return usableCandidate(goal.completionSummary);
189
193
  }
190
194