amicus 4.6.3 → 4.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/CHANGELOG.md +349 -0
  3. package/README.md +19 -7
  4. package/bin/amicus.js +31 -0
  5. package/docs/ROADMAP.md +143 -36
  6. package/docs/configuration.md +52 -4
  7. package/docs/council.md +65 -12
  8. package/docs/doc-system.md +8 -7
  9. package/docs/schemas.md +10 -1
  10. package/docs/testing.md +1 -1
  11. package/docs/troubleshooting.md +33 -5
  12. package/docs/usage.md +64 -14
  13. package/electron/workspace-ui/index.html +3 -0
  14. package/electron/workspace-ui/live-model.js +52 -14
  15. package/electron/workspace-ui/workspace-app.js +14 -3
  16. package/electron/workspace-ui/workspace-lazy.js +233 -0
  17. package/electron/workspace-ui/workspace-matrix.js +12 -1
  18. package/electron/workspace-ui/workspace-panels.js +24 -171
  19. package/electron/workspace-ui/workspace-render.js +6 -2
  20. package/electron/workspace-ui/workspace-seats.js +68 -0
  21. package/electron/workspace-ui/workspace.css +6 -0
  22. package/package.json +8 -4
  23. package/schemas/council-run.schema.json +1 -0
  24. package/schemas/council-stats.schema.json +9 -1
  25. package/schemas/run.schema.json +2 -1
  26. package/schemas/spend.schema.json +1 -1
  27. package/schemas/wave.schema.json +2 -1
  28. package/scripts/postinstall.js +6 -3
  29. package/scripts/setup-hooks.js +49 -3
  30. package/skills/second-opinion/MANUAL-ORCHESTRATION.md +12 -0
  31. package/skills/second-opinion/MODEL-NOTES.md +5 -4
  32. package/skills/sidecar/SKILL.md +9 -2
  33. package/src/cli-council-run-bench.js +86 -0
  34. package/src/cli-handlers-council-run.js +65 -81
  35. package/src/cli-handlers-council.js +17 -5
  36. package/src/cli-handlers-fanout.js +179 -0
  37. package/src/cli-handlers-pack.js +24 -10
  38. package/src/cli-handlers-resume-continue.js +20 -0
  39. package/src/cli-handlers-run.js +19 -161
  40. package/src/cli-template-args.js +48 -0
  41. package/src/cli.js +39 -46
  42. package/src/council/debate.js +89 -10
  43. package/src/council/ledger.js +72 -11
  44. package/src/council/report.js +17 -6
  45. package/src/council/run-assemble.js +15 -3
  46. package/src/council/run-budget.js +2 -2
  47. package/src/council/run-chair.js +61 -5
  48. package/src/council/run-debate.js +51 -67
  49. package/src/council/run-launch.js +20 -2
  50. package/src/council/run-retry.js +17 -2
  51. package/src/council/run-stage1-launch.js +94 -0
  52. package/src/council/run-stage2.js +25 -4
  53. package/src/council/run-stages.js +79 -86
  54. package/src/council/run-state.js +10 -2
  55. package/src/council/run.js +26 -2
  56. package/src/council/tally.js +6 -2
  57. package/src/headless.js +69 -6
  58. package/src/mcp-council-awareness.js +1 -0
  59. package/src/mcp-council-bench.js +4 -0
  60. package/src/mcp-council-run.js +10 -0
  61. package/src/mcp-server.js +114 -54
  62. package/src/mcp-tools.js +12 -5
  63. package/src/pack/pack-cli.js +1 -1
  64. package/src/pack/pack-forward.js +12 -4
  65. package/src/pack/pack-resolve.js +3 -0
  66. package/src/pack/pack-store.js +20 -3
  67. package/src/pack/pack-validate.js +5 -1
  68. package/src/sidecar/budget.js +38 -4
  69. package/src/sidecar/continue.js +8 -23
  70. package/src/sidecar/fanout-budget.js +1 -2
  71. package/src/sidecar/fanout-leg-fallback.js +7 -3
  72. package/src/sidecar/fanout-retry.js +15 -3
  73. package/src/sidecar/fanout-wave-io.js +13 -1
  74. package/src/sidecar/fanout.js +11 -9
  75. package/src/sidecar/list-limit.js +50 -0
  76. package/src/sidecar/list-search.js +69 -0
  77. package/src/sidecar/read.js +90 -5
  78. package/src/sidecar/reopen-spend.js +32 -0
  79. package/src/sidecar/resume.js +1 -1
  80. package/src/sidecar/start-metadata.js +58 -0
  81. package/src/sidecar/start.js +8 -43
  82. package/src/sidecar/workspace-auto-open.js +2 -2
  83. package/src/spend-query.js +2 -1
  84. package/src/template/apply.js +7 -4
  85. package/src/template/render.js +6 -2
  86. package/src/template/store.js +1 -1
  87. package/src/utils/cli-preflight.js +27 -1
  88. package/src/utils/config.js +15 -0
  89. package/src/utils/doctor-engine-check.js +32 -0
  90. package/src/utils/engine-install-scan.js +98 -15
  91. package/src/utils/engine-repair.js +96 -2
  92. package/src/utils/remediation-hints.js +29 -0
  93. package/src/utils/result-schema-rebuild.js +1 -0
  94. package/src/utils/result-schema.js +6 -1
  95. package/src/utils/session-index-tmp-sweep.js +18 -3
  96. package/src/utils/session-index.js +1 -0
  97. package/src/utils/session-metadata-tmp-sweep.js +24 -4
  98. package/src/utils/spend-ledger.js +11 -4
  99. package/src/utils/validators.js +16 -0
package/docs/ROADMAP.md CHANGED
@@ -2,19 +2,26 @@
2
2
 
3
3
  **Reprioritization guidance (Christian, 2026-07-18):** engine-first is locked; the near-term work
4
4
  ships as an incremental **4.x point-release line**, each rev delivering a **behavioral / feature
5
- benefit users feel**; **enterprise-readiness is a venture unto itself** the deliberate **5.0**
6
- major jump, gated on funding/cofounder. The observability arc is split so the **data layer ships
7
- first (v4.3)** and the **Electron "Council Workspace" (v4.4)** rides on top of it. `--dry-run` cost
8
- preview dropped to the backlog.
9
-
10
- Amicus is at **v4.6.2** (tagged 2026-08-05). Each 4.x rev below leads with the benefit, not the
5
+ benefit users feel**. The observability arc is split so the **data layer ships first (v4.3)** and
6
+ the **Electron "Council Workspace" (v4.4)** rides on top of it. `--dry-run` cost preview dropped to
7
+ the backlog.
8
+
9
+ **Amendment (Christian, 2026-08-05): enterprise-readiness leaves the rev pipeline.** It was carried
10
+ here as a numbered **v5.0** heading, which made it read as *scheduled work with a version reserved
11
+ for it* — a commitment the product cannot make while it is gated on funding and a cofounder. It now
12
+ lives under **Backlog (tracked, not scheduled)** with everything else that is real but unscheduled.
13
+ Nothing about the content changed and no judgment about its value is implied; only its status. When
14
+ an org buyer and the org to support them exist, it earns a number then.
15
+
16
+ Amicus is at **v4.7.0** (tagged 2026-08-08). Each 4.x rev below leads with the benefit, not the
11
17
  plumbing.
12
18
 
13
- **Status:** v4.0 through **v4.6.2** have **shipped** — everything down to the v4.7 heading is a
14
- record of what landed, not a plan. **v4.7 (composition + tagging + GUI ergonomics) is the next
15
- planned rev** — that scope carried the number v4.6 here until the degrade-announcement-invariant
16
- milestone took the v4.6.0 release (2026-08-02); renumbered, not descoped. v5.0 remains
17
- forward-looking.
19
+ **Status:** v4.0 through **v4.7.0** have **shipped** — everything on this page is a record of what
20
+ landed, not a plan. Composition the scope that
21
+ carried the number v4.6 here until the degrade-announcement-invariant milestone took the v4.6.0
22
+ release (2026-08-02) — is now an unscheduled candidate for the next rev, tabled in its own section
23
+ below (dropped from v4.7, 2026-08-05); its contents are decided at kickoff per the anti-rot rule,
24
+ not assumed in advance. There is **no numbered major** on this roadmap.
18
25
 
19
26
  > 📁 **Reading this from an npm install?** Some references below point at working documents that
20
27
  > live in the git repository and are deliberately **not** in the published package — anything under
@@ -169,20 +176,78 @@ fooled, and a handful of proven small defects land with tests.
169
176
  path; the README/install scripts/doctor all agree on the real Node ≥22.12 floor; eleven
170
177
  duplicated doctor test fixtures consolidate into one factory — #110
171
178
 
172
- ## v4.7 — "Compose your councils" *(renumbered from v4.6, 2026-08-02 — spec + fresh plan at kickoff per the anti-rot rule)*
173
- **Benefit:** councils chain generate critique refine with no manual copy-paste and history
174
- becomes navigable.
175
- - **Composable/chained waves** (`--input-from <id>` / `--prompt-file -` pipe + per-source digests) —
176
- F6 *(M)*brings the `{{input}}` template variable + the `critique`/`refine` built-ins
177
- - **Session/wave tagging + `--search` + grouped history** (F8) *(S–M)*
178
- - **GUI power ergonomics** (F10: focus-follows fold hotkey, distinguishable window titles, tiling
179
- presets) *(S each)*
180
- - Deferred-item candidates per the v4.5 design doc's §8: RN-2, TST-1/TST-2, REL-2, CA-4
181
- (repair-solos half the failed-chair half shipped v4.6.2), remainder of TST-7
182
- - **README + docs update** *(S)*
183
- > The 2026-07-19 combined spec (`2026-07-19-v4.5-policy-packs-composition-design.md`) holds the
184
- > approved chaining/tagging/F10 design detail and is the primary input to the v4.7 brainstorm; it
185
- > is NOT executed as-written v4.7 gets its own spec + fresh plan at kickoff.
179
+ ## v4.7 — "The count is the count" SHIPPED v4.7.0, 2026-08-08
180
+ **Benefit:** every number amicus shows you is the number what a council cost, which legs ran, and
181
+ which model earned the credit.
182
+
183
+ **Shipped across ten `v4.7-*` PRs** PR0 (extractions) through PR7, plus the PR3 riders and a
184
+ closing documentation pass: the `runStats` completeness half of CA-4, the GOA-7 ledger
185
+ prerequisite, F8 session/wave tagging with `--search`, and four correction sweeps (PR4–PR7).
186
+
187
+ **Why this scope, and why it replaced composition.** Rescoped after a roadmap review on 2026-08-05
188
+ (the number was carried here as v4.6 until the degrade-announcement-invariant milestone took the
189
+ v4.6.0 release) that started from *how the tool is actually used* rather than from the deferral
190
+ list. Two findings drove it:
191
+
192
+ 1. **The Workspace is an instrument panel, not a workspace** (owner, 2026-08-05): it is used for
193
+ **live status while a council runs** and for **quantitative stats** — *never* to read council
194
+ output, which is read in the terminal or through the orchestrating agent. That retires the F10
195
+ ergonomics line wholesale (all three items are reading/working affordances) and promotes anything
196
+ that makes the numbers right.
197
+ 2. **`runStats` is a cost source, not just a record.** Verified at `8d0584a`:
198
+ `cli-handlers-council.js:56` computes `amicus council stats` cost as
199
+ `sumWaveUsage(r.runStats).cost` with **no fallback**; `council/report.js:79` falls back to
200
+ `sumWaveUsage(runStats).cost` when wave usage is absent; `council/ledger.js:24` joins street-cred
201
+ off the same array. So CA-4's omissions are not a schema nicety — they under-report spend on the
202
+ surface the owner relies on, which collides with the cost-truth principle (*reported > estimated
203
+ > unknown; never fabricate $0*). An omitted leg is not "unknown" — it renders as money never
204
+ spent on legs that spent money.
205
+
206
+ > **Tense note.** The bullets below were written as pre-work problem statements and are kept for
207
+ > the record of *why* the rev was scoped this way. Each now leads with what shipped; the
208
+ > problem-statement text that follows it is history, not a live defect.
209
+
210
+ - **CA-4 (remaining half) — `runStats` completeness** *(M)*: **shipped.** Stage-2 judges and repair
211
+ solos *were* absent from `tally.json`'s `runStats` (observed: 5 rows for 11 real legs in
212
+ `wsgate04`); `runStats` now carries one row per paid launch — one `judge` row per judge
213
+ (`run-assemble.js:180-184`) and one `repair` row per `-q<N>` solo, failed ones included
214
+ (`run-stage2.js:122`).
215
+ ⚠️ **Scope correction:** the failed-chair third of the original CA-4 is **closed** — v4.6.2's
216
+ `chairAttempts[]` records every attempt on `run.json` (`run-chair.js:133` cites LC-5 by name), and
217
+ failed-chair cost already reaches `runStats` too: a failed ch1–ch3 attempt gets its own
218
+ `chair-attempt` row there carrying that leg's real `usage` (`run-chair.js:154-156`), so no third
219
+ row class was needed.
220
+ - **GOA-7 prerequisite — segment the ledger by RESOLVED model, not alias** *(S–M)*: **shipped.**
221
+ `ledger.js:124` now keys on `row.resolvedModel || row.model` and `LEDGER_SCHEMA_VERSION` is 2.
222
+ It *was* a live defect: ledger rows keyed by council alias and aliases silently retarget (`gpt-pro` →
223
+ `gpt-5.6-sol-pro`, the `opus` re-pin — both 2026-08-04), so `council stats` conflates distinct
224
+ models under one name. The ledger is append-only, so every run adds rows that will later have to
225
+ be distrusted, and both GOA-1 and GOA-2 plan to build on this data. Bump `LEDGER_SCHEMA_VERSION`;
226
+ old rows stay readable (absent id ⇒ legacy). Full write-up and schema discipline: `BACKLOG.md`
227
+ GOA-7. *(Recency decay — GOA-7's second half — is NOT in this rev.)*
228
+ - **Session/wave tagging + `--search` + grouped history** (F8) *(S–M)* — **shipped**
229
+ (`--tag`, `amicus list --search`, `--limit`, `spend --group-by tag`). The one element carried
230
+ over from the composition scope, and the one with a visible paper trail: this repo's own
231
+ `BACKLOG.md` hand-maintained an index of run identifiers (`wave 47278069`, `run dfb6a692`,
232
+ `runs 0084d48c + 2039b2d1`, `wsgate02`/`wsgate04`) **because there was no search**. It is also the
233
+ rev's only daily-felt user surface — three schema fixes alone are a thin story.
234
+ - **README + docs update** *(S)* — the last scope line; **closed by PR #132**, which corrected the
235
+ sentences v4.7 had made false rather than adding coverage the feature PRs had already shipped.
236
+
237
+ > **Why these belong in one rev.** CA-4 and GOA-7's prerequisite are the same defect class — the run
238
+ > record under-reporting what actually happened — and both are schema-shaped. Each was individually
239
+ > deferred with the same reason (*"M, a schema question, not a fix"*), which is exactly why neither
240
+ > has ever been done: too big for a patch, too small to carry a rev alone. One schema pass is
241
+ > materially cheaper than two.
242
+ >
243
+ > **Lineage.** v4.6 made a loss announce itself; v4.7 makes the accounting match reality. Same
244
+ > invariant family, applied to numbers instead of degradation.
245
+ >
246
+ > ⚠️ **Two hard gates apply before any task touches council internals** — see `BACKLOG.md`
247
+ > *Next-rev hard gates*: the tight-file extraction pass (`cli-handlers-council-run.js` is at
248
+ > **299/300 exactly**, `run-debate.js` at 299, two files **at 300**), and KNOWN_VARIABLES
249
+ > single-sourcing **only if** `{{input}}` is ever scoped — it is not in this rev, so that gate
250
+ > travels with composition rather than blocking here.
186
251
 
187
252
  ### Deferred out of v4.4.1 into v4.5 (2026-07-27)
188
253
 
@@ -202,7 +267,7 @@ a proposed disposition — v4.5 ride-along / v4.6 / backlog — tabled for rulin
202
267
  | **CA-4** | `tally.json`'s `runStats` omits Stage-2 judges, repair solos and failed chair attempts (5 rows for 11 real legs in `wsgate04`) | `M` — a schema question, not a fix |
203
268
  | **CA-5** | `isSubagentToolCall` is still a `name === 'task'` string proxy | `M`, and **reduced** by v4.4.0: it is now only the fallback when the real subtree walk finds nothing |
204
269
  | **LC-1** | B53's stall kill is skipped while a tool-settle deferral is active | `S–M` — shipped deliberately; the author wants a second opinion, which needs data from real runs |
205
- | **LC-5** | A chair fallback leaves no trace in `run.json` (`wsgate02`'s haiku failed twice; only `"chair":"minimax"` was recorded) | `M` a run-record schema addition |
270
+ | ~~**LC-5**~~ | ~~A chair fallback leaves no trace in `run.json`~~ — ✅ **CLOSED by v4.6.2**: `chairAttempts[]` records every attempt (`{waveId, model, outcome, reason}`), checkpointed after each; `run-chair.js:113` cites LC-5 by name. **Do not re-file.** | |
206
271
  | **RN-1** | `sanitizeName` collisions surface as a banner rather than a refusal | `S` + a product decision that was already argued once |
207
272
  | **RN-2** | `renderRunList` blind masking is best-effort — only the open run resolves labels | `M` |
208
273
  | **RN-5** | A blind-mode flip closes every open prose panel and repaints twice | `S–M` |
@@ -219,15 +284,6 @@ record ("eleven `Number(env) || default` sites"), not a task — a blanket migra
219
284
  six new defects to fix one, and `src/utils/env-num.js`'s docblock records which knobs deliberately
220
285
  keep the old form.
221
286
 
222
- ## v5.0 — Enterprise-readiness *(the deliberate major jump — a venture unto itself, gated on funding / cofounder)*
223
- **Benefit:** team/org deployment — but a distinct product + go-to-market motion (SOC2, SLAs, sales, support), not a feature drop. Parked as the 5.0 major per the chair's hard-question #5: a solo dev can't credibly ship or support this alone.
224
- - Secret-store backends + env-var-only mode (A3); org allowlists/blocklists, per-team cost ceilings, read-only enforcement (A6); RBAC
225
- - Audit & compliance: reproducibility manifests + replay (B11), seed/temp/version pinning (A7), spend export to SIEM/warehouse (A10), `/health` + metrics + structured logging (A8)
226
- - Team config `.amicusrc` (A9); spend **governance** (per-team caps/enforcement) — the governance half of A4
227
- - Learning loops that need scale anyway: reliability-aware seat selection (B4), calibration benchmarks (B5), decision-outcome feedback (B10), adaptive strategy planner (B8), evidence provenance (B6)
228
- - README + docs update: deployment/admin documentation for the above, in `README.md` and `docs/`
229
- > These cluster because they share one prerequisite you don't have yet: an org buyer + the org to support. Revisit as a funded track.
230
-
231
287
  ---
232
288
 
233
289
  ### Design notes — auto-open the Council Workspace
@@ -271,6 +327,57 @@ LC-10) that stretch the definition; a third that pops a GUI window would not be
271
327
  patch. Sits naturally beside v4.5's existing **GUI power ergonomics (F10)** line.
272
328
 
273
329
  ## Backlog (tracked, not scheduled)
330
+
331
+ ### Enterprise-readiness *(unscheduled — gated on funding / cofounder)*
332
+ *Moved here from a numbered `v5.0` heading, 2026-08-05. Content unchanged; only its status. It was
333
+ never a rev — it is a distinct product and go-to-market motion (SOC2, SLAs, sales, support), and per
334
+ the chair's hard-question #5 a solo dev can't credibly ship or support it alone. These items cluster
335
+ because they share one prerequisite that does not exist yet: **an org buyer, and the org to support
336
+ them.** Revisit as a funded track; it earns a version number when that track is real.*
337
+ - Secret-store backends + env-var-only mode (A3); org allowlists/blocklists, per-team cost ceilings, read-only enforcement (A6); RBAC
338
+ - Audit & compliance: reproducibility manifests + replay (B11), seed/temp/version pinning (A7), spend export to SIEM/warehouse (A10), `/health` + metrics + structured logging (A8)
339
+ - Team config `.amicusrc` (A9); spend **governance** (per-team caps/enforcement) — the governance half of A4
340
+ - Learning loops that need scale anyway: reliability-aware seat selection (B4), calibration benchmarks (B5), decision-outcome feedback (B10), adaptive strategy planner (B8), evidence provenance (B6)
341
+ - README + docs update: deployment/admin documentation for the above, in `README.md` and `docs/`
342
+ > ⚠️ **B4 (reliability-aware seat selection) now overlaps live backlog work.** `GOA-1` (auto-bench
343
+ > query-aware seat selection, filed 2026-08-05 in `BACKLOG.md`) blends the street-cred ledger into
344
+ > seat choice — that is B4's core idea arriving as a single-user feature rather than an enterprise
345
+ > learning loop. Reconcile before either is scoped; do not build both.
346
+
347
+ ### Composition / chained waves (F6) *(unscheduled — dropped from v4.7, 2026-08-05)*
348
+ `--input-from <id>` / `--prompt-file -` pipe + per-source digests *(M)*, bringing the `{{input}}`
349
+ template variable and the `critique`/`refine` built-ins.
350
+
351
+ *Not cancelled — waiting on a use case that asks for it.* The reasoning, recorded so it is not
352
+ re-argued from scratch: **the chaining already happens, performed by the orchestrating agent.** When
353
+ a council needs to critique a previous council's output, Claude reads run A's verdict and composes
354
+ run B's prompt — adapting the handoff, dropping what is irrelevant, reframing what matters. A fixed
355
+ `--input-from` digest is *less* flexible than that for interactive use. The feature's real
356
+ beneficiary is **headless/CI chaining, where no orchestrator is in the loop** — and the one headless
357
+ consumer today (the Council Review GitHub Action) runs a single review per PR, not a chain.
358
+
359
+ **Revisit when:** a headless or scheduled workflow genuinely needs to chain councils without an
360
+ agent driving it, or the `critique`/`refine` built-ins are wanted on their own — those are a much
361
+ smaller slice than the chaining machinery and could ship independently of F6.
362
+
363
+ ⚠️ **Carries its own hard gate:** KNOWN_VARIABLES single-sourcing (`src/template/render.js:45` keeps
364
+ two hand-maintained copies of the known-variable set) must land **before** `{{input}}` does. See
365
+ `BACKLOG.md` *Next-rev hard gates* — that gate travels with this item, not with v4.7.
366
+
367
+ ### GUI power ergonomics (F10) *(unscheduled — dropped from v4.7, 2026-08-05)*
368
+ Focus-follows-fold hotkey, distinguishable window titles, tiling presets *(S each)*.
369
+
370
+ Dropped on an owner usage finding, recorded here because it should inform every future GUI decision:
371
+ **the Council Workspace is used as an instrument panel — live status while a council runs, plus
372
+ quantitative stats — and never to read council output.** All three F10 items are reading/working
373
+ ergonomics for a surface that is not used that way; "distinguishable window titles" and "tiling
374
+ presets" both presuppose multi-window reading sessions that do not happen.
375
+
376
+ **Consequence beyond F10:** GUI work should be judged on *live-status fidelity* and *stat accuracy*
377
+ first. The prose-panel and blind-masking nits (RN-2, RN-5, the `T19-*`/`T20-*` family in
378
+ `BACKLOG.md`) sit on the unused half of the surface and should rank accordingly.
379
+
380
+ ### Other tracked items
274
381
  - **`--dry-run` / cost & route preview** across start/fanout/council — E2/C7/F4 *(M)* — "know the cost/route before you commit"; useful, not essential to the near-term line.
275
382
  - **F7** — Parallel council panels + super-chair (opt-in `--panels N` high-assurance; niche).
276
383
  - **E7** — Prompt dedup cache (exact-dup, opt-in, excludes council/fanout) — minor cost optimization.
@@ -285,6 +392,6 @@ patch. Sits naturally beside v4.5's existing **GUI power ergonomics (F10)** line
285
392
  - **Local providers stays near-term** (v4.2) — a broad cost/privacy benefit, not enterprise.
286
393
  - **Observability arc split:** v4.3 = the data layer + terminal surface + resilience + spend (ships first); **v4.4 = the Electron Council Workspace (B9)** as a GUI on that data.
287
394
  - **`--dry-run` cost preview → backlog** (was in the observability rev).
288
- - **Enterprise/governance/audit/compliance/learning-loops → v5.0**, reframed as the deliberate *major-version venture* gated on funding.
395
+ - **Enterprise/governance/audit/compliance/learning-loops → v5.0**, reframed as the deliberate *major-version venture* gated on funding. **Superseded 2026-08-05:** moved out of the rev pipeline entirely, into *Backlog (tracked, not scheduled)*. A reserved version number read as a commitment; it isn't one.
289
396
  - The **cheap trust fixes** (envelope, injection fencing, fold nonce) pulled into **v4.0** because the engine needs them to be trustworthy in automation.
290
397
  - **Docs are part of the rev (2026-07-20):** every rev from v4.1 onward closes with a **README + docs update** line item, so each release ships its own documentation rather than deferring it.
@@ -238,7 +238,7 @@ Everything lives under `~/.config/amicus/` (`getConfigDir()` in `src/utils/confi
238
238
  | `.env` | `amicus setup` / `amicus key` | API keys (`OPENROUTER_API_KEY`, `GOOGLE_GENERATIVE_AI_API_KEY`, `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `DEEPSEEK_API_KEY`). `0600` permissions. |
239
239
  | `model-catalog.json` | `refreshCatalog()` (`src/utils/model-catalog.js`) | The cached provider model list, schema-versioned, with a **24-hour TTL**. Also carries refresh-outcome fields — `lastRefreshAttempt` and `lastRefreshError` — stamped on a *failed* refresh without touching the last-good `models`/`fetchedAt` (a bad fetch never clobbers a good cache). Human-readable JSON; safe to delete, it rebuilds on next use. |
240
240
  | `sessions-index.json` | `session-index.js` (`recordSession`, written at session start) | A **global** map of `taskId → project path`, consulted only when a per-project session lookup misses (e.g. an MCP server whose cwd differs from where the session was created). Navigation aid only, never authoritative — a corrupt index degrades to "no entry," never a crash. |
241
- | `council-ledger.jsonl` | `src/council/ledger.js` (`appendRun`), on every `council tally` | One row per council model per run — findings raised, severity breakdown, street-cred, conformance. Read back by `amicus council stats`. |
241
+ | `council-ledger.jsonl` | `src/council/ledger.js` (`appendRun`), on every `council tally` | One row per council model per run — findings raised, severity breakdown, street-cred, conformance. At `LEDGER_SCHEMA_VERSION` **2** (v4.7 GOA-7), rows may also carry `resolvedModel` (the executable id that served); legacy-read, no migration — a row without one (all pre-v2 history, plus leg-less rows) aggregates under its alias, and a group is marked `legacy` only when every row in it lacks `resolvedModel`. Read back by `amicus council stats`. |
242
242
  | `spend-ledger.jsonl` | `src/utils/spend-ledger.js` (`appendSpend`), new in Phase 16 | One row per completed run/leg — tokens + resolved cost. Read back by `amicus spend` for the cross-run rollup. Append is best-effort and can never fail the run it's recording; safe to delete (starts fresh, loses history only). |
243
243
  | `packs/<name>.json` (v4.5) | `amicus pack save` (`src/pack/pack-store.js`) | One JSON file per saved policy pack — bench/model, chair/critic/lenses, options, and a briefing-template *reference*. Peer directory of `templates/` below. Safe to inspect, hand-edit, or delete individually; see [Policy packs](./usage.md#policy-packs). |
244
244
  | `templates/<name>.md` (v4.5) | You, by hand (your editor is the manager) | User-authored briefing templates; a file here shadows a built-in of the same name. Amicus itself never writes into this directory — there is no `template save`/`rm`. See [Briefing templates](./usage.md#briefing-templates). |
@@ -287,8 +287,20 @@ Per-session directory contents:
287
287
  **Fanout waves.** A wave (`amicus fanout`) gets its own session dir at `<waveId>` (same
288
288
  `amicus_sessions/` root); each leg is a full sibling session dir named `<waveId>-1` through
289
289
  `<waveId>-N` (`deriveLegIds()` in `src/sidecar/fanout.js`). The wave-heartbeat display reads each
290
- leg's `progress.json`/`conversation.jsonl` directly nothing wave-specific is stored beyond the
291
- per-leg session dirs themselves plus the wave's own `metadata.json` (type `wave`, `legs: [...]`).
290
+ leg's `progress.json`/`conversation.jsonl` directly. The wave dir itself holds:
291
+
292
+ ```
293
+ <waveId>/
294
+ metadata.json # type "wave", legs: [...], plus a 200-char rendered briefing excerpt
295
+ wave.json # written on completion
296
+ briefing.md # the RENDERED prompt — the corpus `amicus list --search` matches against.
297
+ # Written by mcp-server.js BEFORE the child spawns (so an aborted wave
298
+ # stays searchable), and again by fanout.js:145 once the child runs
299
+ ```
300
+
301
+ One more file appears only for an `amicus_fanout` wave whose prompt came from a **template**: a
302
+ sibling `briefing-input.md` holding the raw pre-render prompt handed to the spawned child, so the
303
+ child's own re-render stays byte-identical and `promptMeta.template` provenance survives.
292
304
 
293
305
  ### Log location + LOG_LEVEL
294
306
 
@@ -383,6 +395,42 @@ a hard-coded built-in (`DEFAULTS` in `src/cli.js`) — so for those the chain is
383
395
  pack > built-in** today. See [Policy packs](./usage.md#policy-packs) for the full per-kind field
384
396
  reference.
385
397
 
398
+ ### Cost gate
399
+
400
+ Two independent pre-flight guards run before a paid model call, both set via top-level
401
+ `config.json` keys that are **hand-edited only** — no wizard or CLI command writes them
402
+ (`src/sidecar/budget.js`):
403
+
404
+ - **`maxCostPerMtok`** — hard per-$/Mtok refusal threshold. Refuses any leg whose catalog
405
+ price-per-Mtok exceeds the cap. Defaults to **60**; a non-positive value falls back to that
406
+ default.
407
+ - **`maxCost`** — soft ceiling on the estimated total $ for the call. Absent, zero or negative all
408
+ mean no ceiling.
409
+
410
+ > ⚠️ **`0` means the opposite thing on each key.** `maxCostPerMtok: 0` falls back to the default 60,
411
+ > so that guard stays **on**; `maxCost: 0` disables the ceiling entirely. Neither key is turned off
412
+ > by setting it to zero in the way you might expect — use `--no-cost-gate` (CLI) or
413
+ > `noCostGate` (`amicus_council_run`) to actually disable them.
414
+
415
+ ```jsonc
416
+ {
417
+ "maxCostPerMtok": 60,
418
+ "maxCost": 5
419
+ }
420
+ ```
421
+
422
+ On the CLI, `--max-cost <$>` overrides `maxCost` for that call, and `--no-cost-gate` disables both
423
+ guards (e.g. for an intentional o3 run).
424
+
425
+ Over MCP the per-call override depends on the tool. `amicus_council_run` takes its own `maxCost`
426
+ and `noCostGate` params, which forward to the spawned child exactly as the CLI flags do.
427
+ **`amicus_start` takes neither.** On that path `maxCostPerMtok` is config-only and nothing can turn
428
+ the gate off at all; the soft ceiling is the **effective** `maxCost` — the pack's if the run used a
429
+ pack that set one, otherwise the config's (`mcp-server.js:454`). Only one of those two values is in
430
+ effect, so raising the other one changes nothing. See
431
+ [Troubleshooting: MCP run fails with "budget gate refused the
432
+ run"](./troubleshooting.md#mcp-run-fails-with-budget-gate-refused-the-run).
433
+
386
434
  ### Uninstall instructions
387
435
 
388
436
  `npm uninstall -g amicus` removes the package and its bin shims. It does **not** clean up everything
@@ -412,6 +460,6 @@ aliases, and council presets carry over untouched.
412
460
  | `eslint` ^8.0.0 | Code linting |
413
461
  | `lint-staged` ^16.3.2 | Run linters on staged files |
414
462
 
415
- `opencode-ai` (>=1.0.0) is the bundled LLM conversation engine it is installed automatically as a postinstall step and does not need a separate `npm install`.
463
+ The bundled **`opencode-ai`** engine (pinned to `1.18.15`, matched by `@opencode-ai/sdk`) installs automatically as a normal dependency you don't install it separately. Its own postinstall lays down the per-platform binaries.
416
464
 
417
465
  > **Legacy names.** Pre-rebrand `SIDECAR_*` environment variables were removed entirely in v2.0.0 — they are no longer read, with no warning. Rename to the `AMICUS_*` equivalents documented above. See [docs/SHIMS.md](./SHIMS.md) for the full removal record and rename table.
package/docs/council.md CHANGED
@@ -85,9 +85,15 @@ Three things to hold onto:
85
85
  changed for them.** The skill's opt-in elements (critic seat, expert lenses, debate mode, chair
86
86
  verdict scale — see [SKILL.md](../skills/second-opinion/SKILL.md) and
87
87
  [SEAT-BRIEFS.md](../skills/second-opinion/SEAT-BRIEFS.md)) ride on existing engine surfaces:
88
- seat roles travel as free-form `runStats[].role` labels (`"critic"`, `"lens:<slug>"`), debate
89
- mode runs the Stage-2 tally with `--no-ledger` (provisional) and re-tallies after the rebuttal
90
- round (that second, post-rebuttal tally is the ledger-recorded one), and lens runs always pass
88
+ seat roles travel as free-form `runStats[].role` labels in the tally/verdict artifact
89
+ (`"critic"`, `"lens:<slug>"`, or any other skill-authored label) **but since v4.7 the
90
+ council-ledger join reads only an allowlist** (`seat`, `critic`, `lens:*`, `chair`, `claude`,
91
+ `council`, `redteam` — see the `runStats[]` row inventory under
92
+ [`amicus council tally`](#amicus-council-tally)), so a custom/free-form label outside that set
93
+ still renders in the tally/report artifact but no longer contributes its
94
+ role/wasChair/conformance to `amicus council stats` reliability numbers. Debate mode runs the
95
+ Stage-2 tally with `--no-ledger` (provisional) and re-tallies after the rebuttal round (that
96
+ second, post-rebuttal tally is the ledger-recorded one), and lens runs always pass
91
97
  `--no-ledger` so non-comparable reviews never feed `stats`.
92
98
 
93
99
  ---
@@ -322,8 +328,10 @@ launch:
322
328
  discovered from the sessions-dir pointer files (`council-<runId>.json`); each row shows status,
323
329
  the chair's `overallVerdict` chip once one exists, and cost.
324
330
  - **Run detail** (`--ui <runId>`) — header + status/verdict chips, a stage rail, a live **Seats**
325
- table (model, role, status, stage, messages, tokens, cost, last activity, a stall flag), the
326
- Stage-1 reviews, the **verbatim** anonymized Stage-2 packet (`bundle-stage2.md`, shown as-is
331
+ table (model, role, status, stage, messages, tokens, cost, last activity, and a trailing flag
332
+ cell — `⏳ stalled` while live, or on a finished run `↻ retried once` marking a reviewing seat
333
+ whose once-only Stage-1 retry didn't save it), the Stage-1 reviews, the **verbatim** anonymized
334
+ Stage-2 packet (`bundle-stage2.md`, shown as-is —
327
335
  never re-rendered), judge prose, the **adjudication matrix** (finding × judge, tier-colored
328
336
  rows, `a/d/n` basis counts, a `thin` badge when `a+d<=1`, an override badge when a Stage-4
329
337
  decision changed a finding's tier, capped at 500 rows with a "showing N of M" note past that),
@@ -532,8 +540,8 @@ model to the reliability ledger. Thin CLI wrapper over `tally()` (`src/council/t
532
540
  ### Tally-input schema
533
541
 
534
542
  Claude assembles this file at the end of Stage 2 (the full assembly recipe is in
535
- [SKILL.md](../skills/second-opinion/SKILL.md#stage-2--cross-review), under "Stage-2 → tally
536
- assembly recipe"). It needs **all five top-level keys** — `tally()` throws
543
+ [SKILL.md](../skills/second-opinion/SKILL.md#the-engine-run--stages-13-plus-the-stage-5-artifacts),
544
+ under "Stage-2 → tally assembly recipe"). It needs **all five top-level keys** — `tally()` throws
537
545
  `Cannot read properties of undefined (reading 'map')` if `meta` or `findings` is missing:
538
546
 
539
547
  | Field | Type | Meaning |
@@ -546,7 +554,44 @@ assembly recipe"). It needs **all five top-level keys** — `tally()` throws
546
554
  | `findings[]` | array | One entry per finding across all reviews: `{id, raiser, severity}` (`claim` may ride along but isn't required by the tally engine). `id` is the run-global label (e.g. `A1`, `B2`) assigned during Stage-2 assembly, not the reviewer's local integer id. |
547
555
  | `adjudications[]` | array | One entry per (judge × finding): `{findingId, judge, verdict}`, `verdict ∈ {agree, dispute, neutral}`. Include every judge's verdict on every finding, **including the raiser's own adjudication of its own finding** — the engine excludes it automatically when scoring (don't pre-filter it). |
548
556
  | `rankings[]` | array | One entry per judge: `{judge, order}`. `order` is that judge's `FINAL RANKING:` block translated to model ids, e.g. `["gpt", "deepseek"]` (ties may use a nested array, e.g. `[["gpt","deepseek"], "mistral"]`). |
549
- | `runStats[]` | array | One entry per model call: `{model, role, wasChair, conformance, status, durationMs, usage}`. May be `[]`. Any leg with no run document gets `durationMs: null, usage: null` — never invent a value. |
557
+ | `runStats[]` | array | One row per paid launch (v4.7 spec §5 D1/D2 — no longer capped at one row per model; see the role roster below): `{model, role, wasChair, conformance, status, durationMs, usage, waveId?, resolvedModel?}`. May be `[]`. Any leg with no run document gets `durationMs: null, usage: null` — never invent a value. `waveId` is emit-only-when-set. `resolvedModel?` (v4.7) — the executable id that actually served the row's leg, emit-only-when-set; leg-less rows (the give-up chair row, dead seats with no leg, the claude row) never carry it. `model` stays the council alias. |
558
+
559
+ **`runStats[].role` roster (v4.7 row-per-launch).** Every leg the run budget counts gets exactly
560
+ one row, so a seat that needed a repair or lost a leg to a retry can now show up more than once.
561
+
562
+ *Primary rows* — exactly one per requested reviewing seat, unchanged in shape from pre-v4.7:
563
+ `seat`, `critic`, `lens:<slug>`, `judge`, `chair` (`wasChair: true`), synthetic `claude`, and the
564
+ legacy default `council` (pre-#83 rows, or hand-assembled tally input that never set a role). A
565
+ dead seat/critic/lens with no recovery, and a chair walk that gives up entirely, get an honest
566
+ primary **error** row too — the #83 judge treatment extended to every seat (`usage: null` on the
567
+ give-up chair; the dead leg's own usage on a dead seat/critic/lens).
568
+
569
+ *Non-primary rows* — `wasChair` always `false`: `chair-attempt` (a failed ch1–ch3 chair launch),
570
+ `repair` (a Stage-1 `-p`, Stage-2 `-q`, chair-ch4, or debate-born `-d<N>r`/`-rv-…r` solo — a
571
+ failed defense or re-vote repair), and `superseded` (a first leg a later attempt replaced — an
572
+ SL-2 retry or a debate repair) — all three new in v4.7's row-per-launch change — plus `rebuttal`
573
+ and `revote` (a `--debate` round's defense/re-vote legs; v4.1, pre-dating row-per-launch). All
574
+ five still cost money and land in `runStats`, so they raise the run's totals everywhere those are
575
+ summed. In `council report`'s cost table only `judge`/`chair-attempt`/`repair`/`superseded` get a
576
+ suffixed label (`rebuttal`/`revote` render unsuffixed); `council tally` has no per-row cost table
577
+ at all, only an aggregate. And only `chair-attempt`/`repair`/`superseded` are filtered out of the
578
+ Workspace seats panel — `rebuttal`/`revote` rows still render there.
579
+
580
+ `runStats[].waveId` names the exact wave/leg a row was built from, present **iff a real billed
581
+ leg backs the row** — e.g. the synthetic `claude` row, a give-up chair's error row, and a
582
+ leg-less dead-seat/critic/lens primary error row (the two SL-2 retry note-classes that never
583
+ produced a real leg for the seat at all) carry none. It's the join key the leg–row bijection
584
+ invariant suite (`tests/council/run-cost-bijection.test.js`) uses to prove every budget-counted
585
+ leg lands on exactly one row. `resolvedModel` follows the same emit-only-when-set discipline and
586
+ the same never-invent rule — it is never derived from the alias.
587
+
588
+ **Ledger-join consequence.** `council stats`'s reliability aggregation (`ledger.js`) only reads
589
+ rows whose role is in the allowlist above (`seat`, `critic`, `lens:*`, `chair`, `claude`,
590
+ `council`, `redteam`) — everything else, including all three new non-primary roles and any
591
+ custom/free-form label a skill or caller invents, is fail-closed excluded and never contributes
592
+ role/wasChair/conformance to reliability stats, even though it still renders in the tally/report
593
+ artifact. Since v4.7 the ledger row copies the joined row's `resolvedModel` and `council stats`
594
+ groups by `resolvedModel || model` — see the stats section below.
550
595
 
551
596
  ### Tally-record schema (what `tally()` returns / prints)
552
597
 
@@ -723,7 +768,10 @@ Since v4.0 (council schema v2), `--json` wraps the rows in the family envelope
723
768
  `{ "schemaVersion": 2, "type": "council-stats", "models": [ … ] }` — the per-model row
724
769
  shape below is unchanged. (Pre-4.0 emitted the bare array.)
725
770
 
726
- **Output**, one row per model that has ever appeared in `meta.models`:
771
+ **Output**: one row per RESOLVED model (v4.7 — rows that carry `resolvedModel` group by the
772
+ executable id that served; rows without one group by alias and are marked `legacy`). Each row
773
+ also lists `aliases[]` — every alias observed for the group, most recent first; the chair
774
+ fallback promotion launches `aliases[0]`.
727
775
 
728
776
  | Field | Meaning |
729
777
  |---|---|
@@ -733,6 +781,8 @@ shape below is unchanged. (Pre-4.0 emitted the bare array.)
733
781
  | `lifetimeConfirmRate` | Mean, across runs, of `(findings this model raised that landed Confirmed) / (findings this model raised)`. `null` when `judged` was false for every run or the model raised nothing. |
734
782
  | `lifetimeFactErrorRate` | Same shape, but for the `Disputed` tier — a proxy for how often the bench caught this model asserting something wrong. |
735
783
  | `conformance` | Tally of `{clean, repaired, unstructured}` counts — how often this model's Stage-1 findings JSON needed a repair re-prompt. |
784
+ | `aliases` | Every alias (row-level `model` value) observed for this group, most recently observed first (v4.7). `aliases[0]` is the launch-preferred name. |
785
+ | `legacy` | `true` when every row in the group lacks `resolvedModel` — alias-keyed history from before resolved-id segmentation, or leg-less rows whose resolution is unknowable (v4.7). Omitted (not `false`) when the group has any resolved rows. |
736
786
 
737
787
  This is the data source the `second-opinion` skill's Stage 0 model recommendations and the
738
788
  `MODEL-NOTES.md` quantitative table both read — **never hand-edit reliability numbers there**;
@@ -910,11 +960,14 @@ _Legend: ✓ agree · ✗ dispute · – neutral · `*` raiser's own vote_
910
960
  ```bash
911
961
  $ amicus council stats
912
962
  model runs avg-cred confirm fact-err notes
913
- deepseek 1 2.00 1.00 0.00 low-N
914
- gpt 1 1.00 0.00 0.00 low-N
963
+ deepseek 1 2.00 1.00 0.00 low-N legacy
964
+ gpt 1 1.00 0.00 0.00 low-N legacy
915
965
  ```
916
966
 
917
- (`low-N` because each model has only 1 recorded run — `runs < 3`.)
967
+ (`low-N` because each model has only 1 recorded run — `runs < 3`. `legacy` because this
968
+ hand-assembled `tally-input.json` never sets `runStats[].resolvedModel` — v4.7 groups by
969
+ alias and marks the group `legacy` whenever none of its rows carry a resolved id; see
970
+ [`amicus council stats`](#amicus-council-stats) above.)
918
971
 
919
972
  **7. Presets**, for reference (independent of the run above):
920
973
 
@@ -56,16 +56,18 @@ When running `--check` mode, the script validates every markdown link in CLAUDE.
56
56
 
57
57
  ## Plans Index
58
58
 
59
- `docs/plans/index.md` is auto-generated by the same script. It lists all `.md` files in:
60
- - `docs/plans/` (active plans)
61
- - `docs/archive/plans/` (archived plans)
62
-
63
- Each entry shows the filename, first heading, and date extracted from the filename.
59
+ No plans index is generated **today**. `buildPlansIndex()` in `scripts/generate-docs.js` scans
60
+ `docs/plans/` and `docs/archive/plans/`, but neither directory exists in this repo, and
61
+ `runWriteMode()` writes `docs/plans/index.md` only when `docs/plans/` already exists — so the write
62
+ never fires. Note that guard is a runtime `fs.existsSync` check, not a disabled feature: creating
63
+ `docs/plans/` would silently reactivate it, emitting a bare list of `- [name](path)` links without
64
+ the per-plan first heading or date the old text here promised. Plans actually live in
65
+ `docs/superpowers/plans/`, uncataloged.
64
66
 
65
67
  ## Commands
66
68
 
67
69
  ```bash
68
- node scripts/generate-docs.js # Regenerate all auto sections + plans index
70
+ node scripts/generate-docs.js # Regenerate all auto sections
69
71
  node scripts/generate-docs.js --check # Verify everything is current (CI mode)
70
72
  npm run generate-docs # Alias for write mode
71
73
  npm run generate-docs:check # Alias for check mode
@@ -89,4 +91,3 @@ Hook order:
89
91
  | "Marker not found" error | Ensure `<!-- AUTO:name -->` and `<!-- /AUTO:name -->` exist in CLAUDE.md |
90
92
  | Stale markers after code change | Run `node scripts/generate-docs.js` manually |
91
93
  | Cross-link validation failure | Fix the broken link in CLAUDE.md or create the missing file |
92
- | Plans index missing | Run the script; it creates `docs/plans/index.md` |
package/docs/schemas.md CHANGED
@@ -49,7 +49,16 @@ This is the one non-additive shape change in the envelope unification; migrate `
49
49
 
50
50
  ## Documented exclusions
51
51
 
52
- - **The two JSONL ledgers** (`council-ledger.jsonl`, `spend-ledger.jsonl`): internal append-only storage, each stays at its own v1 row format (`SPEND_LEDGER_SCHEMA_VERSION` is 1, unrelated to and unbumped by the result/council envelope versions above) — not an emitted/published doc. `spend.schema.json` (the `amicus spend --json` output) is the published, versioned doc built *from* `spend-ledger.jsonl` rows — the ledger row shape itself is not published.
52
+ - **The two JSONL ledgers** (`council-ledger.jsonl`, `spend-ledger.jsonl`): internal
53
+ append-only storage, not emitted/published docs. `spend-ledger.jsonl` stays at its own v1
54
+ row format (`SPEND_LEDGER_SCHEMA_VERSION` is 1, unrelated to the envelope versions above).
55
+ `council-ledger.jsonl` is at `LEDGER_SCHEMA_VERSION` **2** (v4.7 GOA-7): v2 rows may carry
56
+ `resolvedModel` — the executable id that actually served. **Legacy-read, no migration:**
57
+ readers never inspect a row's schemaVersion; a row without `resolvedModel` (all pre-v2
58
+ history, plus leg-less rows whose resolution is unknowable) simply aggregates under its
59
+ alias — a group is marked `legacy` in `council stats` output only when EVERY row in it
60
+ lacks `resolvedModel`. `spend.schema.json` (above) is still the published doc built from
61
+ `spend-ledger.jsonl` rows — neither ledger's row shape itself is published.
53
62
  - **`amicus_list`** (MCP): returns a bare JSON array of session rows; a wrap would be a second breaking change and is deliberately not taken.
54
63
  - **`setup` / `update` / `key`**: interactive-only commands with no `--json` mode.
55
64
  - **MCP acks and live-status snapshots** (start/resume/continue/abort acks, `amicus_status`/`amicus_wait` bodies): carry the envelope keys with subject-family types (`run`/`wave`/`abort`) but are point-in-time snapshots, not the durable result docs the published schemas describe.
package/docs/testing.md CHANGED
@@ -523,7 +523,7 @@ Complete mapping of test files to their targets and focus areas.
523
523
  | `evals/tests/result_writer.test.js` | Result output | Summary formatting, file writing |
524
524
  | `scripts/check-secrets.test.js` | Secret detection | Pattern matching, allowlist, multi-secret |
525
525
  | `scripts/check-file-sizes.test.js` | File size limits | Line counting, batch checking |
526
- | `scripts/validate-docs.test.js` | Doc drift detection | Section extraction, drift comparison, staged file check |
526
+ | `scripts/validate-docs.test.js` | Doc drift detection | Staged file check |
527
527
  | `helpers/cdp-client.test.js` | CDP helper | Mock HTTP+WebSocket CDP server, factory methods |
528
528
  | `electron-headless-mode.test.js` | Electron headless | Source-level verify `AMICUS_HEADLESS_TEST` guard |
529
529
  | `cli-headless-e2e.integration.test.js` | CLI E2E (real LLM) | `start --no-ui`, `list`, `read`, `read --metadata` |
@@ -5,7 +5,33 @@
5
5
 
6
6
  ## First: run `amicus doctor`
7
7
 
8
- Before working through any symptom below, run `amicus doctor` (plugin-only installs: `npx -y amicus@latest doctor`). It checks, in order: Node version, config directory, API keys, default model, catalog freshness, alias staleness and drift, the ANTHROPIC_BASE_URL form, the OpenCode binary, the OpenCode engine's MCP launch path, Electron, installed skills, MCP registration, the legacy sidecar MCP entry, session index tmp files, OpenRouter credit, local providers, and the project root — and prints a targeted fix hint for every failing check. `amicus doctor --fix` self-heals what it can (e.g. re-installs a broken Electron in place); `--json` gives machine-readable output.
8
+ Before working through any symptom below, run `amicus doctor` (plugin-only installs: `npx -y amicus@latest doctor`). It checks, in order: Node version, config directory, API keys, default model, catalog freshness, alias staleness and drift, the ANTHROPIC_BASE_URL form, the OpenCode binary, the OpenCode engine's MCP launch path, Electron, installed skills, MCP registration, the legacy sidecar MCP entry, session index and session metadata tmp files, OpenRouter credit, local providers, and the project root — and prints a targeted fix hint for every failing check. `amicus doctor --fix` self-heals what it can (e.g. re-installs a broken Electron in place); `--json` gives machine-readable output.
9
+
10
+ ---
11
+
12
+ ## MCP run fails with "budget gate refused the run"
13
+
14
+ **Symptom:** An `amicus_start` call over MCP returns an error with `message: "Error: budget gate
15
+ refused the run"` (`ERROR_CODES.BUDGET_EXCEEDED`).
16
+
17
+ **Cause:** v4.7 made the `amicus_start` shared-server budget gate unconditional
18
+ (`src/mcp-server.js:442-467`) — it used to run only when a pack forwarded `maxCost`, so a no-pack
19
+ MCP start that worked on 4.6.x can now refuse. The gate has two independent guards; when both fire,
20
+ raising only one will not clear the run.
21
+
22
+ **Fix:** the error text names which guard fired — raise that one, or choose a cheaper model. **If
23
+ both fired, you must raise both**; clearing one leaves the other refusing. See
24
+ [Cost gate](./configuration.md#cost-gate).
25
+
26
+ - **`maxCostPerMtok`** (the per-$/Mtok guard) lives only in `config.json`. No pack and no MCP param
27
+ can override it.
28
+ - **`maxCost`** (the total-$ ceiling) is whichever value is *in effect*: **the pack's if this run
29
+ used a pack that set one, otherwise the config's.** Editing the loser of that pair changes
30
+ nothing.
31
+
32
+ `amicus_start` has **no per-call override** — it takes neither a `maxCost` nor a `noCostGate` param,
33
+ and nothing can turn the gate off on that path. (`amicus_council_run` is different: it does take
34
+ both, and they forward to its child exactly like the CLI flags.)
9
35
 
10
36
  ---
11
37
 
@@ -207,13 +233,15 @@ For full headless configuration, see [docs/configuration.md](./configuration.md)
207
233
 
208
234
  ## Headless Leg Fails with `NO_OUTPUT_BACKSTOP`
209
235
 
210
- **Symptom:** A headless leg (`amicus start --no-ui`, or one leg of a `fanout`/council run) fails with an error starting `NO_OUTPUT_BACKSTOP: model produced no output, reasoning, or tool calls in Ns`.
236
+ **Symptom:** A headless leg (`amicus start --no-ui`, or one leg of a `fanout`/council run) fails with an error starting `NO_OUTPUT_BACKSTOP: no output, reasoning, or tool calls in Ns — the AMICUS_NO_OUTPUT_BACKSTOP_MS window (0 disables)`. You may instead see `...in Ns — a caller-set window overriding the AMICUS_NO_OUTPUT_BACKSTOP_MS default` — this covers two different cases with the same wording: a Stage-1 retry (its window is the resolved `AMICUS_NO_OUTPUT_BACKSTOP_MS` value **doubled**, so a 120 s default reads `240s` on the retry) and the `amicus models --check --live` probe (a fixed, non-tunable `30s` — see below). Both are "caller-set" in the sense that the specific window wasn't read live from the env var at that call, but only the retry case is actually governed by it.
237
+
238
+ **Cause:** The mechanism only knows that the deadline passed with no substantive activity — no output, reasoning, or tool calls — for the backstop window (120 s by default for an ordinary leg). It does **not** know *why*. Do not read this as "the endpoint is dead" or "the model isn't being served" — the message deliberately stops short of that claim, because at one of its two firing sites the backstop can win the race before the prompt send even resolves, so acceptance itself was never observed. Treat `NO_OUTPUT_BACKSTOP` as "silence past the deadline," not a diagnosis.
211
239
 
212
- **Cause:** The model endpoint accepted the request but produced nothing no output, reasoning, or tool calls for the entire backstop window (120 s by default). This is the "accepted but not serving" class: usually a dead or misconfigured endpoint, or a catalog-listed model that's no longer actually being served upstream.
240
+ **Lesson from #133:** an earlier version of this message *did* guess a cause ("likely a listed-but-not-serving model or a dead endpoint"), and that guess sent 30 minutes of debugging at model ids and API keys on a real incident. The actual cause was an OpenCode engine version skew between the npx-cached copy and the global install sitting the whole time in `~/.local/share/opencode/log/opencode.log`. **Before chasing model/endpoint theories, check that log for the session's real error.**
213
241
 
214
- **Confirm:** `amicus models --check` — audits your configured aliases against the live catalog and flags drift/staleness. A model that still resolves locally but has quietly been retired upstream is exactly this failure mode, but a static audit only compares against the catalog's *listing* — it can't tell you whether a still-listed model actually answers. For that, run `amicus models --check --live` on demand: it sends one tiny real request to every stored alias and reports `SERVED` / `SILENT` / `ERROR` per alias, where `SILENT` (`accepted-but-silent`) is this exact `NO_OUTPUT_BACKSTOP` failure, caught deliberately instead of by accident. It spends real money (one tiny leg per stored alias) — see [docs/usage.md § `amicus models`](./usage.md#amicus-models--the-model-catalog) for cost, scope, and exit-code details.
242
+ **Confirm:** `amicus models --check` — audits your configured aliases against the live catalog and flags drift/staleness. A model that still resolves locally but has quietly been retired upstream is one possible explanation, but a static audit only compares against the catalog's *listing* — it can't tell you whether a still-listed model actually answers. For that, run `amicus models --check --live` on demand: it sends one tiny real request to every stored alias and reports `SERVED` / `SILENT` / `ERROR` per alias, where `SILENT` (`accepted-but-silent`) is this exact `NO_OUTPUT_BACKSTOP` failure, caught deliberately instead of by accident. It spends real money (one tiny leg per stored alias) — see [docs/usage.md § `amicus models`](./usage.md#amicus-models--the-model-catalog) for cost, scope, and exit-code details.
215
243
 
216
- **Fix:** Check the alias's target (`amicus models --search <term>` to find the current id, then re-point the alias) — this is the common case. Raise `AMICUS_NO_OUTPUT_BACKSTOP_MS` only if a model legitimately needs more than 120 s to produce its first token; see [docs/configuration.md § Headless Poller Tuning](./configuration.md#headless-poller-tuning).
244
+ **Fix:** Check the `~/.local/share/opencode/log/opencode.log` for the session's real error first (see #133 above). Then check the alias's target (`amicus models --search <term>` to find the current id, then re-point the alias) — a stale alias is a common case, but not the only one. If the failing leg was a Stage-1 retry (the window read `240s`, or generally double your configured/default value), `AMICUS_NO_OUTPUT_BACKSTOP_MS` **is** the lever — raising it raises both the first attempt's window and the retry's doubled one, unless your `--timeout` is low enough that the doubled window is clamped to the leg timeout. On an ordinary (non-retry) leg, raise it only if a model legitimately needs more than 120 s to produce its first token. The live probe's fixed 30 s window is the one case the env var genuinely cannot touch. See [docs/configuration.md § Headless Poller Tuning](./configuration.md#headless-poller-tuning).
217
245
 
218
246
  ---
219
247