pi-goal-list-loop-audit 0.34.139 → 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 (39) hide show
  1. package/README.md +105 -93
  2. package/docs/DESIGN.md +55 -4
  3. package/docs/RELEASING.md +7 -4
  4. package/extensions/drafter-model.ts +97 -0
  5. package/extensions/goal-commands.ts +31 -20
  6. package/extensions/goal-continuation.ts +61 -11
  7. package/extensions/goal-heartbeat.ts +40 -8
  8. package/extensions/goal-loop-auditor-process.ts +5 -6
  9. package/extensions/goal-loop-auditor.ts +1 -1
  10. package/extensions/goal-loop-backoff.ts +9 -69
  11. package/extensions/goal-loop-core.ts +142 -98
  12. package/extensions/goal-loop-display.ts +76 -78
  13. package/extensions/goal-loop-forever.ts +23 -4
  14. package/extensions/goal-loop-subagents.ts +37 -17
  15. package/extensions/goal-loop.ts +77 -13
  16. package/extensions/goal-recovery.ts +41 -105
  17. package/extensions/goal-settings.ts +73 -49
  18. package/extensions/goal-state.ts +1 -1
  19. package/extensions/loops/goal-activation.ts +57 -75
  20. package/extensions/loops/goal-auditor-hooks.ts +159 -112
  21. package/extensions/loops/goal-list-queue.ts +206 -11
  22. package/extensions/loops/goal-orchestrator.ts +15 -27
  23. package/extensions/loops/goal-runtime-globals.ts +5 -4
  24. package/extensions/loops/goal-session.ts +10 -27
  25. package/extensions/loops/goal-settings-ui.ts +166 -57
  26. package/extensions/loops/goal-tools.ts +50 -41
  27. package/extensions/loops/goal-ui.ts +9 -17
  28. package/extensions/loops/goal.ts +2 -5
  29. package/extensions/main-model-recovery.ts +27 -94
  30. package/extensions/model-selector.ts +2 -1
  31. package/extensions/quota-retry.ts +99 -81
  32. package/extensions/reviewer.ts +0 -5
  33. package/extensions/settings-menu.ts +195 -144
  34. package/package.json +8 -7
  35. package/prompts/goal-loop-continuation.md +15 -9
  36. package/prompts/goal-loop-draft.md +10 -0
  37. package/prompts/goal-loop-forever-draft.md +8 -0
  38. package/schemas/goal.schema.json +51 -8
  39. package/scripts/smoke.sh +20 -7
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.139` — 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,81 +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)
279
+ | `RECOVERING` | A bounded automatic retry is pending; the status does not guess why the provider failed. |
258
280
 
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. The normal unhinted wait is
263
- `base → 2×base → 4×base → 8×base → 16×base → 5h`, where `base` is the
264
- `mainModelRetryMinutes` setting (15 minutes by default). A factual provider
265
- `Retry-After` hint wins when it fits the five-hour probe budget; an over-budget
266
- hint falls back to the configured ladder. `hourlyQuotaProbe=on` adds a separate
267
- `:00:30` probe while recovery is parked. The automatic window is 24h, then an
268
- explicit `/goal resume`/`/list resume`/`/loop resume` starts a fresh window.
269
- The only failures that do not auto-retry are the ones identified by *positive
270
- evidence* as futile: context/output-token limits and user aborts
271
- (`non-recoverable`), plus auditor watchdog timeouts (a hanging verification
272
- command will hang again — the stored claim waits for an explicit resume).
273
- With global `autoResume=on`, pending probes survive a session reload.
274
-
275
- For continuous work, configure up to **10 ordered Main model backups** in
276
- `/glla` using models from different providers or billing/quota pools when
277
- possible — another model on the same exhausted plan is not a real fallback.
278
- The editor is the top row of the **Backups** settings tab (one place for
279
- main-session backup policy). It shows the actual try order as
280
- `current → backup 1 → backup 2 …`, shows each configured backup's rank, lets
281
- **Space** add/remove a backup, and **Tab** enters order mode where **↑/↓**
282
- moves the highlighted backup (brackets `[` `]` also reorder without leaving
283
- the list). On an account/plan/billing/auth provider failure, glla calls `setModel` for the
284
- first eligible backup only; the next supervised turn tests it. If that
285
- candidate fails, the next backup is tried in order. With the global
286
- `mainModelFallbackOnRateLimit=on` default, an explicit HTTP 429/request-rate
287
- failure also walks the ordered chain; set it off to keep 429s on the current
288
- model with the bounded cadence plus extra post-hour probe. A 429 remains a
289
- request-rate classification, not a token-limit failure. Forbidden,
290
- unavailable, and unauthenticated refs are skipped; a successful supervised
291
- turn clears the episode. The chain
292
- is global, durable, and its attempted cursor survives reload; after the chain
293
- is exhausted, bounded recovery probes continue rather than silently abandoning
294
- work. The Backups tab shows the `N/10` count and the numbered chain.
295
-
296
- **Quota walls engage fast** (v0.34.57): a surfaced long-lived failure
297
- (quota / billing / auth) records a 30-minute knowledge window; a send-rearm
298
- storm inside that window escalates into the recovery envelope after **3
299
- minutes** of failed sends instead of the generic 15 — a wedge right after a
300
- quota wall is almost always the same wall. Transient (5xx/stream/network)
301
- failures never record the signal and keep the fast error ladder. The
302
- envelope is armed by configuration: an empty `mainModelFallbacks` list means
303
- "park and probe the same model" rather than switching pools. The
304
- `mainModelFallbackOnRateLimit=off` setting is the explicit never-switch
305
- posture for non-empty chains.
306
-
307
- Classification still exists, but it only *labels*: the card and badge show
308
- what the provider said (quota wall, billing, rate limit) so the reason is
309
- diagnosable, and `QUOTA WALL` is only shown when the provider's own words say
310
- quota — ambiguous prose is never relabeled. The raw provider message stays in
311
- the ledger/durable state; the card shows the classified reason and recovery
312
- action. Detached-auditor failures get the same treatment: any infrastructure
313
- error on a stored completion claim pauses the goal with a durable bounded
314
- one-shot retry (`auditor retry: …`), and only the plan's 24h horizon stops
315
- automatic probes.
316
-
317
- The quota-specific card hides raw provider JSON while preserving it in durable
318
- state and the ledger:
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.
319
285
 
320
- ```text
321
- glla: ⟦⏳ QUOTA WALL · next probe in 10m 48s⟧ · 1 queued
322
- ├─ QUOTA WALL · Token Plan usage limit · 1 waiting in list
323
- ├─ waiting — nothing for you to do · next probe in 10m 48s
324
- ```
286
+ ## Provider failures: aggressive retry envelope, bounded (v0.35.0)
325
287
 
326
- Increasing pi's per-request retry count is usually the wrong fix for a
327
- multi-hour plan cap: it prolongs the 429 noise and delays the durable pause;
328
- it does not make the provider reset sooner. A provider that says "reset in a
329
- week" therefore does not cause a week of unattended probes.
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.
330
335
 
331
336
  For long-running `/list` work, the card adds a compact queue trail with the
332
337
  immediate next item and its truthful wait age while `/list` remains the
@@ -428,44 +433,48 @@ confirmation is intentionally not bypassed.
428
433
  Open `/glla` to edit these settings in the table (the rows show effective values and provenance):
429
434
 
430
435
  - Auditor model and thinking level
431
- - Auditor fallback model
436
+ - Auditor fallback agent
432
437
  - Notify command, token limit, and wedge-alert minutes
433
438
  - Auto-resume, auto-accept drafts, decision popup, and carryover policy
434
- - Ordered main-session backups at the top of the Backups tab, plus recovery cadence (including the optional hourly probe)
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
435
442
  - Forbidden model patterns and switch policy
436
- - Audit cap/report size, aggressive mode, quota retry, and stall brakes
443
+ - Audit cap/report size, aggressive mode (ON by default), retry cadence, and
444
+ stall brakes
437
445
 
438
446
  The argument namespace is reserved for actions such as `/glla status`, `/glla
439
447
  resume`, `/glla cancel`, `/glla stats`, `/glla audits`, `/glla tooloverride`,
440
448
  `/glla fallbacks clear`, and `/glla wipe`. `fallbacks clear` atomically removes
441
- the global backup chain and cancels any pending backup switch. Cancel stops the
449
+ the global main-agent fallback chain and cancels any pending fallback switch. Cancel stops the
442
450
  active objective; wipe clears all live state while preserving history.
443
451
  There is no top-level `/glla key=value` setting syntax.
444
452
 
445
453
  Resolution per key: **project > global > defaults** — EXCEPT `autoResume` and
446
- main-session recovery settings (`mainModelFallbacks`,
447
- `mainModelFallbackOnRateLimit`, `mainModelRetryMinutes`, `hourlyQuotaProbe`),
454
+ agent recovery settings (`mainModelFallbacks`, `mainModelRetryMinutes`,
455
+ `drafterModel`, `drafterThinkingLevel`, `drafterModelFallbacks`,
456
+ `hourlyRetryProbe`),
448
457
  which are **global-only**: per-project opt-ins from old versions
449
458
  silently overrode the global hold at launch (the junk-runner incident), so
450
459
  the launch-restore gate and the reviewer-enqueue gate read only the global
451
460
  file now. Main-session recovery policy is likewise one global chain/cadence
452
- for the active session. Main-session backups are global and ordered (up to 10): a provider
453
- failure selects backup 1, then backup 2, and so on, one supervised turn at a
454
- time. The Backups tab leads with the ordered-chain editor — a multi-select
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
455
464
  picker where Space toggles membership, Tab enters order mode (↑/↓ moves a
456
465
  chain row), and clearing the selection removes the global key. Forbidden,
457
466
  unavailable, and unauthenticated refs are skipped. When every candidate is
458
- down, glla cancels the provider-held retry and uses the configured
467
+ down, glla stops the current send attempt and uses the configured
459
468
  `base → 2×base → 4×base → 8×base → 16×base → 5h` ladder (`base` defaults to
460
- 15m). `hourlyQuotaProbe=on` adds a separate :00:30 probe after each hour
461
- starts. Explicit HTTP 429/rate-limit errors remain request-rate failures, not
462
- token-limit labels. With `mainModelFallbackOnRateLimit=on` (default) they
463
- walk the ordered backups; with it off they stay on the current model through
464
- the bounded retry policy. Automatic recovery stops at 24h, preserves the saved
465
- work, and requires an explicit
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
466
474
  `/goal resume`, `/list resume`, or `/loop resume` to start a fresh window. A
467
- quota window returning within that horizon therefore resumes saved work without
468
- manual intervention; no blind 50ms resend loop is introduced. The detached auditor uses an explicit cascade: primary
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
469
478
  `auditorModel` → optional fallback pin → the pi session model. If a selected
470
479
  model fails after launch, the worker retries it once and then advances through
471
480
  that same cascade; every candidate is still audited in a detached,
@@ -532,7 +541,10 @@ in-session warning + your configured notify push, once per interval while
532
541
  it persists. Tune Wedge alert minutes in the `/glla` settings table (0 = off).
533
542
 
534
543
  Every other wait is bounded too: continuation retries are milliseconds,
535
- 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
536
548
  hard timeout, and the detached auditor aborts after 10m with no activity while no
537
549
  an auditor tool is running. A long-running verification tool is allowed to
538
550
  finish, but each tool has an independent five-minute ceiling and the worker
package/docs/DESIGN.md CHANGED
@@ -189,11 +189,15 @@ architectural decisions that changed the SHAPE of the system:
189
189
 
190
190
  ## Addendum v0.34.31 (main-session model recovery)
191
191
 
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
+
192
196
  - **Global recovery policy**: `mainModelFallbacks`,
193
197
  `mainModelFallbackOnRateLimit`, `mainModelRetryMinutes`, and
194
198
  `hourlyQuotaProbe` are global-only, so the runtime and settings provenance
195
199
  cannot disagree about which recovery policy is active.
196
- - **Ordered global backups**: `mainModelFallbacks` is an explicit ordered list
200
+ - **Ordered global fallback agents**: `mainModelFallbacks` is an explicit ordered list
197
201
  of up to 10 `provider/model` references. A provider/quota error selects the
198
202
  first eligible authenticated candidate for account/plan/billing/auth failures,
199
203
  calls `setModel`, and lets the next supervised turn test it; later failures
@@ -201,7 +205,7 @@ architectural decisions that changed the SHAPE of the system:
201
205
  walk the list when global `mainModelFallbackOnRateLimit` is on (default); off
202
206
  keeps them on the current model with bounded retry + hourly probe cadence.
203
207
  The detached auditor's model cascade remains a separate subsystem. The
204
- ordered-chain editor leads the **Backups** settings tab: Space toggles
208
+ ordered-chain editor lives in the **Main agent** settings tab: Space toggles
205
209
  membership, Tab enters an explicit order mode where ↑/↓ moves a chain row,
206
210
  and clearing the list removes the global key. The attempted cursor is durable, so a reload cannot
207
211
  restart at an already-failed rung.
@@ -256,7 +260,7 @@ replacement without delivering a successor `session_start`:
256
260
  the report surface stays truthful (the AuditProgress/AuditorProgress dual-interface
257
261
  rule: display evidence-gates on `unmatchedStarts + unmatchedEnds > 0`).
258
262
  - **Uniform retry envelope, no text-trust** (v0.34.51 historical baseline): error
259
- text was not trusted to pick a retry policy. The current ordered-backup policy
263
+ text was not trusted to pick a retry policy. The current ordered-fallback policy
260
264
  supersedes that baseline: account/plan/billing/auth failures may walk the configured
261
265
  chain, while explicit HTTP 429/request-rate failures walk it when
262
266
  `mainModelFallbackOnRateLimit` is on (default), or remain on the current model
@@ -268,6 +272,10 @@ replacement without delivering a successor `session_start`:
268
272
 
269
273
  ## Addendum v0.34.57 (quota walls engage recovery fast)
270
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
+
271
279
  - **Knowledge-window escalation**: a surfaced long-lived failure (quota /
272
280
  billing / auth) records a 30-minute knowledge window. A send-rearm storm
273
281
  inside that window escalates into the recovery envelope after 3 minutes of
@@ -280,7 +288,7 @@ replacement without delivering a successor `session_start`:
280
288
  - **Armed by configuration**: the envelope is inert without
281
289
  `mainModelFallbacks` (rotation) — an empty list means "park and probe the
282
290
  same model" instead of switching pools. When the list is non-empty, the
283
- runtime tries one eligible backup at a time in persisted order, skips
291
+ runtime tries one eligible fallback at a time in persisted order, skips
284
292
  forbidden/unavailable refs, and parks only after the ordered chain is
285
293
  exhausted. Explicit HTTP 429/rate-limit errors remain request-rate signals,
286
294
  not token-limit labels; the global `mainModelFallbackOnRateLimit` switch
@@ -317,6 +325,49 @@ replacement without delivering a successor `session_start`:
317
325
  remains held unless the existing Auto-resume policy or a validated lifecycle
318
326
  handoff supplies consent. `/goal resume` still starts a direct fresh audit.
319
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
+
320
371
  ## Addendum v0.4.0 (completion)
321
372
 
322
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
+ }