amicus 4.6.0 → 4.6.2

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 (51) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/CHANGELOG.md +128 -0
  3. package/README.md +2 -2
  4. package/docs/ROADMAP.md +45 -8
  5. package/docs/configuration.md +13 -9
  6. package/docs/council.md +7 -2
  7. package/docs/publishing.md +1 -1
  8. package/docs/troubleshooting.md +49 -20
  9. package/docs/usage.md +21 -1
  10. package/electron/setup-ui-aliases.js +2 -2
  11. package/electron/workspace-ui/index.html +3 -0
  12. package/electron/workspace-ui/live-model.js +71 -0
  13. package/electron/workspace-ui/workspace-app.js +2 -2
  14. package/electron/workspace-ui/workspace-panels.js +9 -10
  15. package/electron/workspace-ui/workspace-seats.js +117 -0
  16. package/electron/workspace-ui/workspace-verbs.js +1 -0
  17. package/electron/workspace-ui/workspace.css +6 -0
  18. package/package.json +1 -1
  19. package/schemas/alias-audit.schema.json +6 -1
  20. package/schemas/council-run.schema.json +14 -0
  21. package/skills/second-opinion/MODEL-NOTES.md +182 -35
  22. package/src/cli-handlers-doctor.js +16 -4
  23. package/src/cli.js +4 -0
  24. package/src/council/run-chair.js +49 -3
  25. package/src/council/run-launch.js +4 -0
  26. package/src/council/run-retry-notes.js +74 -0
  27. package/src/council/run-retry.js +280 -0
  28. package/src/council/run-stages.js +41 -11
  29. package/src/council/verdict.js +8 -1
  30. package/src/headless.js +119 -9
  31. package/src/mcp-council-awareness.js +1 -0
  32. package/src/mcp-server.js +17 -2
  33. package/src/mcp-tools.js +5 -1
  34. package/src/opencode-client.js +21 -0
  35. package/src/sidecar/fanout-leg.js +2 -2
  36. package/src/sidecar/fanout.js +1 -1
  37. package/src/sidecar/models-probe.js +119 -0
  38. package/src/sidecar/models.js +81 -6
  39. package/src/utils/alias-audit.js +52 -1
  40. package/src/utils/base-url-classify.js +74 -0
  41. package/src/utils/council-presets.js +6 -2
  42. package/src/utils/curated-models.js +29 -10
  43. package/src/utils/degrade.js +1 -0
  44. package/src/utils/doctor-base-url-check.js +41 -0
  45. package/src/utils/model-fetcher.js +1 -0
  46. package/src/utils/model-tiers.js +28 -7
  47. package/src/utils/no-output-backstop.js +48 -0
  48. package/src/utils/remediation-hints.js +15 -11
  49. package/src/utils/result-schema.js +29 -2
  50. package/src/utils/update-notice.js +171 -0
  51. package/src/workspace/live-normalize.js +1 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "amicus",
3
- "version": "4.6.0",
3
+ "version": "4.6.2",
4
4
  "description": "Multi-model LLM Council + parallel AI window for Claude Code. Run structured council reviews across Gemini, GPT, DeepSeek and more — or fork a conversation to any model and fold the results back.",
5
5
  "author": {
6
6
  "name": "Christian Wagner"
package/CHANGELOG.md CHANGED
@@ -5,6 +5,134 @@ All notable changes to Amicus are documented here. Format follows
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [4.6.2] - 2026-08-05
9
+
10
+ ### Added
11
+
12
+ - **Headless legs now fail fast when a model produces nothing.** A leg that produces zero
13
+ output, reasoning, or tool calls for `AMICUS_NO_OUTPUT_BACKSTOP_MS` (120 s default,
14
+ env-tunable) is failed with `NO_OUTPUT_BACKSTOP: …` instead of burning the full `--timeout`
15
+ to learn nothing — the "accepted but not serving" class. Disarms permanently on the first
16
+ sign of activity, so slow cold-prefill local models are unaffected; `0` (or negative)
17
+ disables it.
18
+ - **`doctor` gains a new `anthropic-base-url` row.** Prints the exact `ANTHROPIC_BASE_URL` the
19
+ process sees and how it will be treated — the host-form value can live only in a parent
20
+ process's environment, so the seen value is the only diagnostic there is.
21
+ - **Host-form `ANTHROPIC_BASE_URL` is now carried into the engine as `<value>/v1` by default**
22
+ (a provider-config override — zero env vars written; announced once per process).
23
+ Host-form is the Anthropic-SDK convention (the SDK appends `/v1` itself), but OpenCode's
24
+ provider layer treats the value as the full prefix, so unnormalized host-form previously
25
+ 404'd every direct-Anthropic leg. `AMICUS_BASE_URL_NORMALIZE=0` disables normalization
26
+ entirely.
27
+ - **`models --check` and the `doctor` aliases row now flag stored-alias drift**: a stored alias
28
+ that's still catalog-listed but no longer matches any route its family currently resolves to
29
+ (the v4.6.1 `gemini` release-gate class), with the exact `setup --add-alias` refresh command.
30
+ - **`models --check` gains an opt-in `--live` probe.** `--check --live` sends one real, tiny
31
+ request to every *stored* alias (curated defaults are out of scope) on a single quiet fan-out
32
+ wave and reports `SERVED` / `SILENT` (`accepted-but-silent`) / `ERROR` per alias — the
33
+ on-demand version of the check that would have caught the v4.6.1 `gemini` incident (a
34
+ catalog-listed model no longer actually served). Spends real money: one tiny leg per stored
35
+ alias. Non-served outcomes fold into the existing exit code; a stored-alias count above the
36
+ fan-out leg cap (`AMICUS_FANOUT_MAX_LEGS`) fails fast before anything is probed. When the
37
+ probe can't run (catalog unavailable, or `--refresh` also passed), Amicus announces the skip
38
+ instead of silently dropping the flag.
39
+ - **The Council Workspace seats panel now shows seats a run announced dead.** The **Seats**
40
+ table appends a row for every seat with zero usable legs — derived from `run.json`'s
41
+ `degrades[]` (dead-leg/dead-wave records) and the critic's `verdict.seatLoss` — with a
42
+ blind-maskable model name (a dead seat has no anonymity label, so blind mode renders
43
+ `(masked)` rather than leak the raw id), `did not review — retried once` (the degrade
44
+ recorded a `retryWaveId`) or plain `did not review`, no cost cell, and muted seat-dead
45
+ styling. Dead rows appear live, mid-poll: as soon as the run checkpoints the loss — always
46
+ post-retry, so a row never lands before the seat's one shot at recovery is spent — it paints
47
+ and stays through every tick after (immediately for a dead-wave seat; at that stage's
48
+ boundary for a dead-leg seat, since the panel keeps suppressing the row while the seat's own
49
+ errored-leg entry is still listed live); the run's terminal refresh then additionally unions
50
+ the critic's `seatLoss` on top, for any loss the live payload alone didn't carry.
51
+ - **The chair fallback walk now records every attempt on `run.json`.** Each leg it tries — `ch1`
52
+ (the requested chair), `ch2` (a same-chair retry), `ch3` (the ledger-promoted fallback) —
53
+ appends an entry to an additive `chairAttempts[]` array (`{waveId, model, outcome, reason}`,
54
+ `outcome ∈ completed|error|timeout|no-output`), checkpointed after every attempt so a mid-walk
55
+ kill never loses what already ran. When no chair leg completes at all, the `chair-failed`
56
+ degrade's "What was lost" `why` now names each attempt's cause instead of one flat sentence —
57
+ `ch1 <model>: <reason> · ch2 <model>: <reason> · ...` — while a chair that ran but produced no
58
+ parseable VERDICT line keeps its original flat why (the walk didn't fail). The `ch4`
59
+ VERDICT-line repair re-prompt is deliberately not counted as an attempt — its chair leg already
60
+ completed; only the verdict line gets re-prompted.
61
+
62
+ ### Fixed
63
+
64
+ - **The `gpt` quick-pick family stopped resolving once OpenAI split 5.6 into
65
+ tiers.** `idPattern` only matched bare numeric ids (`gpt-5.5`), so it missed
66
+ 5.6 entirely — `gpt-5.6-sol` (premium), `gpt-5.6-terra` (mid), `gpt-5.6-luna`
67
+ (economy), and their `-pro` siblings all fell outside it, leaving the family
68
+ pinned to the older `gpt-5.5` and the owner's stored `gpt` alias reported as
69
+ DRIFTED. Per owner ruling, `gpt` now tracks the TERRA (mid) tier: the
70
+ pattern additionally matches `-terra` while still excluding `-terra-pro`,
71
+ `-sol*`, `-luna*`, and the unrelated `-codex` family; the pinned fallback
72
+ moves to `openrouter/openai/gpt-5.6-terra`. Bare numeric ids stay matched as
73
+ a within-family fallback.
74
+ - **The curated `opus` pin fell behind the live catalog.** Both routes move from Claude
75
+ Opus 4.8 to Claude Opus 5: `openrouter/anthropic/claude-opus-5` plus the authored
76
+ direct `anthropic/claude-opus-5` (verified against the live catalog and Anthropic docs
77
+ 2026-08-04 — authored, never derived; anthropic stays in `DIVERGENT_VENDORS`). Same
78
+ live price, so the `frontier` bench's pricing evidence is unchanged. The offline
79
+ `ANTHROPIC_MODELS` floor gains a matching `anthropic/claude-opus-5` row so keyless and
80
+ offline installs never report the shipped default stale. Opus 5's two gateway forms
81
+ coincide (no dotted version segment), so the dot-vs-dash regression guards now ride
82
+ `haiku`, the surviving divergent-form alias.
83
+
84
+ ### Removed
85
+
86
+ - **The `devstral` alias** (owner ruling 2026-08-04). OpenRouter delisted the entire
87
+ devstral family — zero matches across the live catalog, any vendor — and the alias had
88
+ no other route. No served model is a devstral successor, so the alias was dropped
89
+ rather than retargeted ("no pinned guess is better than a wrong one"); `mistral`
90
+ remains the vendor's alias, and a stored `devstral` alias in user config is flagged
91
+ with replacement suggestions by `models --check` / `doctor`. This was the pin that made
92
+ `models --check` exit 1 — the v4.6.2 release-gate risk.
93
+
94
+ ## [4.6.1] - 2026-08-03
95
+
96
+ ### Added
97
+
98
+ - **The MCP channel finally hears about new versions** (spec 2026-08-03). The MCP server now
99
+ runs the update check at startup and appends one flavor-aware notice block to the first
100
+ successful tool result of each server process (once per session, latched); `amicus_guide`
101
+ carries an always-on update line, and one `[amicus] update available` line lands in the
102
+ client's MCP log on stderr. The instruction is chosen config-first (`npx -y amicus@latest`
103
+ registrations are told a restart suffices; cached/pinned npx copies get the re-point-or-
104
+ clear-cache hint in the unverified voice; global installs get `npm install -g amicus`, from
105
+ where #33's stale-version warning takes over). Words only — no auto-update over MCP, no
106
+ periodic re-check; `NO_UPDATE_NOTIFIER=1` still disables the check entirely.
107
+ - **A lost Stage-1 seat gets one more chance (SL-2).** A council sub-wave that dies before
108
+ its legs exist, or a leg that ends with no usable output, is relaunched exactly once —
109
+ serially, after the surviving launches settle. Recovery announces in the one voice
110
+ (`Recovered: seat X reviewed on retry — …`, a `stage1-retry` heal on
111
+ `run.json`/`verdict.json` `degrades[]`) and the run stays exit 0; a seat still dead after
112
+ its retry degrades exactly as before, with both attempts named in the why. Unconditional;
113
+ gated on the run's `--max-cost` position (an over-budget run skips the retry and records
114
+ the loss byte-identically to v4.6.0). Retry legs and their spend-ledger rows carry
115
+ `retryOfWaveId`. A healed critic counts as seated in `verdict.seatLoss`.
116
+
117
+ ### Changed
118
+
119
+ - **The shipped second-opinion MODEL-NOTES seed was corrected and enriched** (owner-ruled
120
+ fold-back, PR #93): the haiku "hard-404" warning re-caused to the `ANTHROPIC_BASE_URL` `/v1`
121
+ convention split, GLM's stale reliability caution withdrawn on the v4.4.1 fence-extractor
122
+ replay evidence, pre-degrade-era claims re-grounded in the current announce/retry contract,
123
+ and three model sections plus the "peer consensus ≠ evidence on published numbers" rule added
124
+ from the field.
125
+
126
+ ### Removed
127
+
128
+ - **The unused `rebuildElectron` remediation hint** (owner ruling 2026-08-03, closing Plan 3's
129
+ queued hint-voice question). It had no live call site — `doctor --fix`, the in-place Electron
130
+ self-heal, is its stated convergence target — and its prose asserted unverified causes ("after
131
+ an ABI mismatch or partial unpack"). Absence-pinned in `tests/remediation-hints.test.js`; a
132
+ reintroduction must adopt the unverified-cause voice. The same ruling keeps
133
+ `sweepSessionIndexTmp`'s confident voice: its cause is definitional (an atomic-write tmp orphan
134
+ has no other producer), not a guess.
135
+
8
136
  ## [4.6.0] - 2026-08-02
9
137
 
10
138
  ### Added (v4.6 milestone — the degrade announcement invariant, plans 1-4)
package/README.md CHANGED
@@ -441,7 +441,7 @@ $ amicus status demo123 --json
441
441
  "taskId": "demo123",
442
442
  "status": "complete",
443
443
  "elapsed": "5m 0s",
444
- "version": "4.6.0",
444
+ "version": "4.6.2",
445
445
  "model": "google/gemini-2.5-flash",
446
446
  "phase": "terminal"
447
447
  }
@@ -552,7 +552,7 @@ Run `amicus doctor` first — it checks keys, catalog, OpenCode binary, Electron
552
552
  | Install fails partway, or `amicus doctor` reports the OpenCode binary "not found" | A **transient** error during the OpenCode engine's own postinstall (a spawn `ENOENT`, or an antivirus file-lock while it lays down its 11 per-platform binaries) can roll back the whole atomic install — retrying usually succeeds | Just re-run `npm install -g amicus`. If it still fails, clear the cache first: `npm cache clean --force && npm install -g amicus`. |
553
553
  | `401` / auth error | No usable key for the model's vendor — bare `provider/model` ids fall back to `OPENROUTER_API_KEY` automatically, so this means neither the direct key nor an OpenRouter key is configured (or `--gateway direct`/`openrouter` forced a gateway whose key is missing) | Run `amicus setup`, or `amicus key <provider> <key>` to add the missing key; see [Routing](#routing). |
554
554
  | `402` / "Payment Required" on first council review / `start` / `fanout` call | Your OpenRouter key is real but has no credit. Key save (`amicus key openrouter <key>` or the setup wizard's key step) only checks that the key **authenticates** — it doesn't check balance, so a zero-credit key saves cleanly and only fails later, on the first real model call. (The `amicus council` subcommand itself is deterministic math and never calls a model.) | Add credit at [openrouter.ai/credits](https://openrouter.ai/credits), **or** switch to a zero-cost council: `amicus setup` → option 2 (Free OpenRouter council) builds one from live `:free`-suffixed models and saves it as `councils.free` — then run `amicus fanout --council free …`. See [Free council (zero-cost)](#free-council-zero-cost) above. |
555
- | Every direct `anthropic/…` model (`haiku`, `sonnet`, `opus`, `claude`) errors `Not Found` in ~2 s at zero tokens, but the same model works via `openrouter/anthropic/…` | An inherited `ANTHROPIC_BASE_URL` missing its `/v1` path segment. The engine appends only `/messages`, so requests hit `https://api.anthropic.com/messages` HTTP 404 with an empty body the bare status text. A shell spawned by Claude Code sets the `/v1`-less form for you. The model id, alias, and key are all fine. | `export ANTHROPIC_BASE_URL=https://api.anthropic.com/v1`, or unset it entirely, or pass `--gateway openrouter`. In a council a dead seat **degrades the run instead of failing it** — smoke-test each seat with one throwaway `amicus start` before paying for a council. See [docs/troubleshooting.md](./docs/troubleshooting.md#every-direct-anthropic-model-fails-with-not-found). |
555
+ | Every direct `anthropic/…` model (`haiku`, `sonnet`, `opus`, `claude`) errors `Not Found` in ~2 s at zero tokens, but the same model works via `openrouter/anthropic/…` | An inherited host-form `ANTHROPIC_BASE_URL` (missing `/v1`) the Anthropic-SDK convention, not OpenCode's; a shell spawned by Claude Code sets it for you. **Since v4.6.2 amicus normalizes this to `<value>/v1` automatically** (a provider-config override, announced once on stderr) the model id, alias, and key were never the problem, and now neither is the URL, unless `AMICUS_BASE_URL_NORMALIZE=0` is set or the path is nonstandard. | Should now self-heal confirm with `amicus doctor`'s `anthropic-base-url` row (shows the value seen + its treatment). Still failing: unset `AMICUS_BASE_URL_NORMALIZE` if you set it, or `export ANTHROPIC_BASE_URL=https://api.anthropic.com/v1` / unset the var entirely / pass `--gateway openrouter`. In a council a dead seat **degrades the run instead of failing it** — smoke-test each seat with one throwaway `amicus start` before paying for a council. See [docs/troubleshooting.md](./docs/troubleshooting.md#every-direct-anthropic-model-fails-with-not-found). |
556
556
  | `Model 'X' is unverified against the direct catalog; attempting anyway` for a model that plainly exists | Not a claim the model is wrong — amicus **couldn't check**. That vendor's direct catalog fetch failed (usually a stale or truncated stored key), leaving its namespace empty, and an empty namespace never blocks a launch. The engine may still run the model from its own credential store, so a working model warns forever. | `amicus models --refresh` and watch for a provider that stays empty; re-save the good key with `amicus key <provider> <apikey>`. See [docs/troubleshooting.md](./docs/troubleshooting.md#model-x-is-unverified-against-the-direct-catalog-attempting-anyway). |
557
557
  | Session not found | No session matches the given ID | Run `amicus list`, or omit `--session-id` to use the most recent. |
558
558
  | No conversation history found | Project-path encoding | Check `~/.claude/projects/`; `/` and `_` in the project path are encoded as `-` in the directory name. |
package/docs/ROADMAP.md CHANGED
@@ -7,12 +7,14 @@ major jump, gated on funding/cofounder. The observability arc is split so the **
7
7
  first (v4.3)** and the **Electron "Council Workspace" (v4.4)** rides on top of it. `--dry-run` cost
8
8
  preview dropped to the backlog.
9
9
 
10
- Amicus is at **v4.5.0** (tagged 2026-07-28). Each 4.x rev below leads with the benefit, not the
10
+ Amicus is at **v4.6.1** (tagged 2026-08-03). Each 4.x rev below leads with the benefit, not the
11
11
  plumbing.
12
12
 
13
- **Status:** v4.0 through **v4.5.0** have **shipped** — everything down to the v4.6 heading is a
14
- record of what landed, not a plan. **v4.6 (composition + tagging + GUI ergonomics) is the next
15
- rev.** v5.0 remains forward-looking.
13
+ **Status:** v4.0 through **v4.6.1** 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.
16
18
 
17
19
  > 📁 **Reading this from an npm install?** Some references below point at working documents that
18
20
  > live in the git repository and are deliberately **not** in the published package — anything under
@@ -95,7 +97,7 @@ stops hiding. Design: `docs/superpowers/specs/2026-07-27-v4.5-save-and-share-des
95
97
  client best able to show it. *(S–M; the pieces exist — see the design notes below.)*
96
98
  - **Council policy packs + full run-profiles** (bench + lenses + options + briefing template, invoke by name) — B7/F5 *(M)*
97
99
  - **Briefing templates + library** (F9) *(S–M)* — the foundation packs reference; the `{{input}}`
98
- chaining variable and the `critique`/`refine` built-ins arrive with v4.6
100
+ chaining variable and the `critique`/`refine` built-ins arrive with the composition rev (now v4.7)
99
101
  - **Ride-along fixes** — FR-1 (a failed council seat can render perpetually live), the FR-2 ruling,
100
102
  RN-1/RN-5/RN-11 Workspace renderer fixes, TST-3 real-CDP abort pass *(S each; dispositions for
101
103
  all 17 open items are tabled in the design doc's §8)*
@@ -103,7 +105,42 @@ stops hiding. Design: `docs/superpowers/specs/2026-07-27-v4.5-save-and-share-des
103
105
  > Why here: save/share velocity multipliers that only pay off once councils are a command (v4.0)
104
106
  > and observable (v4.3/v4.4); auto-open makes the v4.4 surface discoverable on its best client.
105
107
 
106
- ## v4.6 — "Compose your councils" *(specced after v4.5 shipsanti-rot rule)*
108
+ ## v4.6 — "A loss announces itself" *(the degrade announcement invariant)*✅ SHIPPED v4.6.0, 2026-08-02
109
+ **Benefit:** a council run can no longer degrade quietly — every loss states what was lost, why,
110
+ and what it does to the run, in one voice, on every surface (stderr, `run.json`, `verdict.json`,
111
+ the report, `doctor`). The north star made mechanical: a correct-but-silent degrade fails the bar
112
+ as hard as a crash.
113
+ - **The ten-channel degrade announcement contract** — every loss routes through one sink
114
+ (`src/council/run-degrade.js`, the only code allowed to flip `degraded.value`, enforced by a
115
+ source-scan invariant test) and lands with mandatory what/why/effect on every surface, including
116
+ the report's new **"What was lost"** section — #85 *(L)*
117
+ - **`verdict.seatLoss` derived from the degrade records** (#84 — a dead critic *leg* finally flips
118
+ `criticSeated`; the v4.5.2 seatLoss suites passed byte-unedited) + **Stage-2 judge legs get
119
+ `runStats` cost rows** (#83 — per-leg attribution for ~38% of a run's spend that had none) *(M)*
120
+ - **`doctor` speaks the vocabulary** — `doctor --json` gains additive `degrades[]`, `--fix` prints
121
+ `Recovered:` lines in the one voice, and the engine hints state causes as **unverified** instead
122
+ of asserting an antivirus guess *(M)*
123
+ - **Workspace discoverability from the CLI** — `watch` usage names `--ui` (#80), a CLI council run
124
+ with Electron present prints how to open the live Workspace (#81), `watch --ui` against an
125
+ `--out-dir` run names its cause (#82), and the Stage-5 verdict rebuild preserves
126
+ `seatLoss`/`degrades[]` (#87) *(S each)*
127
+ - **Deliberate behavior changes** — dropped preset members and shared-server acquisition failures
128
+ now exit degraded (2) on every transport; judge rows raise reported cost totals vs v4.5.x
129
+ (`runStats` consumers keying by model must exclude `role: 'judge'`).
130
+ - **Docs** — the full record is `CHANGELOG.md` §4.6.0; spec
131
+ `docs/superpowers/specs/2026-08-01-degrade-announcement-invariant-design.md`; plans 1–4 under
132
+ `docs/superpowers/plans/` (`2026-08-0*-v4.6-degrade-invariant-plan-*.md`).
133
+ > Why it jumped the queue (2026-08-01): the v4.5.x field reports showed the engine was not losing
134
+ > legs (11 four-seat council runs on v4.5.4, 10 clean) — but when a seat *was* lost, nothing told
135
+ > the user which one. That silent-degrade class was ruled a north-star violation and took the rev
136
+ > number; the composition scope below moved to v4.7.
137
+
138
+ **v4.6.1 (shipped 2026-08-03):** the follow-on point release — **SL-2** ("a lost seat gets one
139
+ more chance": the once-only Stage-1 retry with `Recovered:` heals, ruled heal-first off SL-3),
140
+ the **MCP update notice**, the `rebuildElectron` hint deletion, and the **fold-back-corrected
141
+ MODEL-NOTES seed** (PR #93). Ninth consecutive first-attempt publish.
142
+
143
+ ## v4.7 — "Compose your councils" *(renumbered from v4.6, 2026-08-02 — spec + fresh plan at kickoff per the anti-rot rule)*
107
144
  **Benefit:** councils chain — generate → critique → refine with no manual copy-paste — and history
108
145
  becomes navigable.
109
146
  - **Composable/chained waves** (`--input-from <id>` / `--prompt-file -` pipe + per-source digests) —
@@ -115,8 +152,8 @@ becomes navigable.
115
152
  remainder of TST-7
116
153
  - **README + docs update** *(S)*
117
154
  > The 2026-07-19 combined spec (`2026-07-19-v4.5-policy-packs-composition-design.md`) holds the
118
- > approved chaining/tagging/F10 design detail and is the primary input to the v4.6 brainstorm; it
119
- > is NOT executed as-written — v4.6 gets its own spec + fresh plan once v4.5 ships.
155
+ > approved chaining/tagging/F10 design detail and is the primary input to the v4.7 brainstorm; it
156
+ > is NOT executed as-written — v4.7 gets its own spec + fresh plan at kickoff.
120
157
 
121
158
  ### Deferred out of v4.4.1 into v4.5 (2026-07-27)
122
159
 
@@ -36,14 +36,16 @@ Per vendor, the bare form's direct key is: `google/...` → `GOOGLE_GENERATIVE_A
36
36
  `DEEPSEEK_API_KEY`.
37
37
 
38
38
  **Inherited provider base URLs.** Amicus does not define or read `*_BASE_URL` variables for the
39
- hosted vendors above, but it does pass the whole environment through to the OpenCode engine, which
40
- hands them to the underlying provider SDK. `ANTHROPIC_BASE_URL` is the one that bites: the SDK
41
- appends only `/messages` to it, so it must include the `/v1` path segment. A value of
42
- `https://api.anthropic.com` (**no** `/v1`) makes every direct `anthropic/…` model fail with a bare
43
- `Not Found` at zero tokens, while the same models still work through OpenRouter. Some hosts set
44
- this for you a shell spawned by Claude Code inherits the `/v1`-less form. Either export
45
- `https://api.anthropic.com/v1` or unset the variable. See
46
- [troubleshooting § Every Direct Anthropic Model Fails with `"Not Found"`](./troubleshooting.md#every-direct-anthropic-model-fails-with-not-found).
39
+ hosted vendors above, and it still passes the whole environment through to the OpenCode engine,
40
+ which hands them to the underlying provider SDK. `ANTHROPIC_BASE_URL` gets one extra treatment:
41
+ the SDK appends only `/messages` to it, so it must include the `/v1` path segment, and some hosts
42
+ set the `/v1`-less host-form for you a shell spawned by Claude Code inherits it. **Since v4.6.2,
43
+ amicus detects that host-form value and carries a normalized `<value>/v1` into the engine as a
44
+ provider-config override by default** (no env var is rewritten) see `AMICUS_BASE_URL_NORMALIZE`
45
+ above to disable it, and `amicus doctor`'s `anthropic-base-url` row to see how your current value
46
+ is being treated. Any other path (including an already-correct `/v1`) passes through unchanged.
47
+ See [troubleshooting § Every Direct Anthropic Model Fails with `"Not Found"`](./troubleshooting.md#every-direct-anthropic-model-fails-with-not-found)
48
+ for the pre-normalization failure mode and the manual fix if you've disabled the knob.
47
49
 
48
50
  ---
49
51
 
@@ -88,6 +90,7 @@ through OpenRouter as before.
88
90
  | `AMICUS_FANOUT_MAX_LEGS` | Cap the number of concurrent legs in a single fanout wave. Protects against accidental runaway costs when `--models` is a long list. Non-positive or non-integer values fall back to the default. | `10` |
89
91
  | `AMICUS_MCP_CLIENT` | Force the MCP server's `--client` value (`code-local`, `code-web`, or `cowork`) instead of auto-detecting it from the caller's MCP `initialize` handshake (`clientInfo.name`). Invalid values are ignored (with a warning) and detection proceeds normally. Note: `code-web` requires an explicit `--session-dir` and is not usable for MCP-spawned sessions. | auto-detected |
90
92
  | `AMICUS_MAX_SESSIONS` | Maximum number of concurrent sessions the shared OpenCode server (`src/utils/shared-server.js`) will track before rejecting new ones. Renamed from `SIDECAR_MAX_SESSIONS` in v2.0.0. | `20` |
93
+ | `AMICUS_BASE_URL_NORMALIZE` | Set `0` to stop amicus from carrying a host-form `ANTHROPIC_BASE_URL` into the engine as `<value>/v1`. Host-form is the Anthropic-SDK convention (the SDK appends `/v1`); OpenCode treats the value as a full prefix, so unnormalized host-form 404s every direct-Anthropic leg. | `1` |
91
94
 
92
95
  ---
93
96
 
@@ -95,7 +98,7 @@ through OpenRouter as before.
95
98
 
96
99
  These variables control the polling loop that drives headless sessions. The defaults are conservative and work for almost all workloads. You only need them if you are running against unusually slow or fast model endpoints, or if you are building tooling on top of Amicus and need tighter completion detection.
97
100
 
98
- **Which of these accept `0`, and which ignore it.** The four `SETTLE` knobs in this table (`AMICUS_USAGE_SETTLE_POLLS`, `AMICUS_USAGE_SETTLE_INTERVAL_MS`, `AMICUS_USAGE_SETTLE_CALL_TIMEOUT_MS`, `AMICUS_TOOL_SETTLE_GRACE_MS`) read through `envNumber()` (`src/utils/env-num.js`), which honours an explicit, finite `0` — for those, `0` is a documented escape hatch and each row below says what it switches off. **Every other variable in this table reads through `Number(env) || default`, so `0` is falsy and silently falls back to the default** — there is no way to set them to zero, and that is deliberate: a `0` poll interval would busy-loop and a `0` stall threshold would kill every leg on its first poll. In both families a blank, missing or non-finite value falls back to the default.
101
+ **Which of these accept `0`, and which ignore it.** Five knobs in this table — the four `SETTLE` knobs (`AMICUS_USAGE_SETTLE_POLLS`, `AMICUS_USAGE_SETTLE_INTERVAL_MS`, `AMICUS_USAGE_SETTLE_CALL_TIMEOUT_MS`, `AMICUS_TOOL_SETTLE_GRACE_MS`) plus `AMICUS_NO_OUTPUT_BACKSTOP_MS` — read through `envNumber()` (`src/utils/env-num.js`), which honours an explicit, finite `0` — for those, `0` is a documented escape hatch and each row below says what it switches off (`AMICUS_NO_OUTPUT_BACKSTOP_MS` treats any negative value the same as `0`). **Every other variable in this table reads through `Number(env) || default`, so `0` is falsy and silently falls back to the default** — there is no way to set them to zero, and that is deliberate: a `0` poll interval would busy-loop and a `0` stall threshold would kill every leg on its first poll. In both families a blank, missing or non-finite value falls back to the default.
99
102
 
100
103
  | Variable | Purpose | Default |
101
104
  |----------|---------|---------|
@@ -105,6 +108,7 @@ These variables control the polling loop that drives headless sessions. The defa
105
108
  | `AMICUS_STABLE_IDLE_POLLS` | Number of consecutive idle polls required when no explicit completion signal is received (approximately 60 s at the 2 s default). This is the fallback heuristic for models or SDK versions that don't emit a clean completion event. | `30` |
106
109
  | `AMICUS_MAX_CONSECUTIVE_POLL_FAILURES` | Consecutive poll failures before the headless runner bails. At the 2 s interval this is approximately 30 s. Prevents a dead server from burning the full session timeout on futile polls. | `15` |
107
110
  | `AMICUS_TOOL_CALL_STALL_MS` | How long a tool call may sit pending with **no** result and no output growth before the leg is failed with `Tool call stalled: <tool>` and its OpenCode session aborted. This is the wedge guard: it targets a leg producing nothing at all, and it is skipped while a tool-settle deferral is active (`AMICUS_TOOL_SETTLE_GRACE_MS` owns that decision instead, and ends in a completion rather than a failure). **`0` is ignored** — it falls back to the default rather than disabling the guard, because a `0` threshold would kill every leg on its first poll. There is no way to switch this off; raise it if you legitimately run very long single tool calls. | `180000` |
111
+ | `AMICUS_NO_OUTPUT_BACKSTOP_MS` | Fail a headless leg fast when the model has produced no output, reasoning, or tool calls for this long — the "accepted but not serving" class. Disarms permanently on the first sign of activity, so slow cold-prefill local models are unaffected. **Set `0` (or negative) to disable the backstop entirely** — silent legs then run to the ordinary timeout. | `120000` |
108
112
  | `AMICUS_USAGE_SETTLE_POLLS` | How many extra `getMessages` reads run **after** a leg has already finished, to catch provider usage/cost that lands milliseconds after the completion signal (measured: real paid legs losing their cost by 29 ms and 155 ms). The loop breaks early as soon as every assistant message carries usage, so the common case is one extra read. **Set to `0` to disable the reconciliation entirely** — legs then report whatever usage was present at completion, which can be `$0` on a leg that really did cost money. | `3` |
109
113
  | `AMICUS_USAGE_SETTLE_INTERVAL_MS` | Delay between those settle reads. **`0` is honoured and means no delay** — the reads run back to back. It does **not** disable the reconciliation (that is `AMICUS_USAGE_SETTLE_POLLS=0`); it only removes the gap between attempts. | `400` |
110
114
  | `AMICUS_USAGE_SETTLE_CALL_TIMEOUT_MS` | Per-call deadline for a settle read and for the child-session (subagent) spend walk. Deliberately much tighter than `AMICUS_POLL_CALL_TIMEOUT_MS`: the leg is already finished, so a hung read must not add 30 s × 3 to a run's wall time. The effective value is the **smaller** of this and `AMICUS_POLL_CALL_TIMEOUT_MS`, so raising it above that has no effect. **`0` is honoured and means no timer is armed at all** — a hung settle read or subtree walk would then wait indefinitely. | `5000` |
package/docs/council.md CHANGED
@@ -176,7 +176,9 @@ Key semantics:
176
176
  same per-member reason) as a preview, before you spend anything.
177
177
  - Chair failure recovery: one retry of the same chair → promote the highest peers-only
178
178
  street-cred model (from `amicus council stats`) that is not a bench seat → give up and write
179
- the verdict with `overallVerdict: null`.
179
+ the verdict with `overallVerdict: null`. Each attempt in that walk is additionally recorded on
180
+ `run.json` as `chairAttempts[]` (`{waveId, model, outcome, reason}`), checkpointed after every
181
+ attempt so a mid-walk kill preserves what already happened.
180
182
  - SIGINT/SIGTERM abort the active wave/solo, finalize `run.json` as `aborted`, exit 130/143.
181
183
  `amicus abort <councilRunId>` (and the MCP tools via the sessions-dir pointer file
182
184
  `council-<runId>.json`) work on council runs; `status`/`list` resolve them the same way.
@@ -329,7 +331,10 @@ launch:
329
331
  the Stage-2 contract carries no structured reason field, so rationale lives in prose; on a
330
332
  `--debate` run, a re-voted cell instead opens `revote-<model>.md` and also surfaces the
331
333
  structured `reason` `debate.json` records for that re-vote), chair verdict + street-cred +
332
- Stage-4 decisions, and a cost-by-seat table with a `--max-cost` ceiling gauge.
334
+ Stage-4 decisions, and a cost-by-seat table with a `--max-cost` ceiling gauge. The Seats table
335
+ also lists any seat the run announced dead as a muted, no-cost row, live — as soon as the run
336
+ checkpoints the loss, no terminal wait required (a seat whose errored legs still occupy the
337
+ active stage's roster paints once that stage completes).
333
338
 
334
339
  **Historical runs** render entirely from the run directory — open any old `council-<runId>` at any
335
340
  time; nothing here requires the run to still be live.
@@ -48,7 +48,7 @@ Provenance is implied under trusted publishing.
48
48
 
49
49
  Run top-to-bottom before `npm version`:
50
50
 
51
- 1. **MODEL-NOTES fold-back:** diff the machine-local ledger (`~/.claude/skills/second-opinion/MODEL-NOTES.md`) against the shipped seed (`skills/second-opinion/MODEL-NOTES.md`); port durable, machine-independent lessons into the shipped file (merge/prune, keep it tight — no run-ledger numbers, those live in `amicus council stats`).
51
+ 1. **MODEL-NOTES fold-back:** diff the machine-local ledger (`~/.claude/skills/second-opinion/MODEL-NOTES.md`) against the shipped seed (`skills/second-opinion/MODEL-NOTES.md`); cherry-pick generalizable lessons from the local ledger into the shipped seed — **per-section judgment, both directions, never a bulk copy** (the divergence is two-directional: adopt seed-side corrections back into the local ledger too). Merge/prune, keep it tight — no run-ledger numbers, those live in `amicus council stats`. Standing practice per the owner's ruling, 2026-08-03: shipped = curated seed, local = lab notebook.
52
52
  2. `npm test` green; `npm run lint` clean.
53
53
  3. **Integration tier, both rails:**
54
54
  - `npm run test:integration` (keyless) — must be **0 failures**. This is the same thing the `integration` job in `ci.yml` runs on every push, so it should already be green.
@@ -5,7 +5,7 @@
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, 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 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
9
 
10
10
  ---
11
11
 
@@ -56,33 +56,50 @@ one.** The council degrades around the missing model — the chair silently fall
56
56
  collapses from 3 seats to 2, and every finding comes back `confidence: "thin"` because it only ever
57
57
  had one peer corroborator. Nothing in `verdict.json` records that the roster changed.
58
58
 
59
- **Cause:** an `ANTHROPIC_BASE_URL` environment variable that is missing the `/v1` path segment.
60
- The OpenCode engine passes it straight through to `@ai-sdk/anthropic` as the SDK `baseURL`, and the
61
- SDK appends only `/messages` to it. With `ANTHROPIC_BASE_URL=https://api.anthropic.com` the engine
62
- therefore posts to `https://api.anthropic.com/messages` instead of
59
+ **Cause:** an `ANTHROPIC_BASE_URL` environment variable in **host-form** missing the `/v1` path
60
+ segment. That's the Anthropic-SDK convention (the SDK appends `/v1` itself), not OpenCode's its
61
+ provider layer treats the value as the already-complete prefix and appends only `/messages`, so
62
+ `https://api.anthropic.com` (no `/v1`) posts to `https://api.anthropic.com/messages` instead of
63
63
  `https://api.anthropic.com/v1/messages`. That URL returns HTTP **404 with an empty body**, so the AI
64
- SDK has no error payload to report and surfaces the bare HTTP status text — `Not Found`. The model
65
- id, the alias, and the API key are all fine; only the URL is wrong.
66
-
67
- Some hosts set this variable for you. Notably, running `amicus` from a shell spawned by Claude Code
68
- inherits `ANTHROPIC_BASE_URL=https://api.anthropic.com` (no `/v1`) from the host process, so amicus
69
- can fail this way on a machine where nothing in the amicus config is wrong.
70
-
71
- **Confirm it in one command** (no key needed for the first line):
64
+ SDK has no error payload to report and surfaces the bare HTTP status text — `Not Found`. Some hosts
65
+ set this variable for you: notably, a shell spawned by Claude Code inherits
66
+ `ANTHROPIC_BASE_URL=https://api.anthropic.com` (no `/v1`) from the host process. The model id, the
67
+ alias, and the API key are never the problem; only the URL is wrong.
68
+
69
+ **Since v4.6.2, this self-heals by default.** Amicus classifies `ANTHROPIC_BASE_URL` on every
70
+ engine start; when it's host-form, it carries a normalized `<value>/v1` into the engine as a
71
+ provider-config override (no env var is rewritten) and prints one
72
+ `Notice: ANTHROPIC_BASE_URL is host-form (…); passing …/v1 to the engine …` line to stderr, once per
73
+ process. `amicus doctor` also gained an `anthropic-base-url` row that always prints the exact value
74
+ it sees and how it's being treated. So on a current install, this failure should be rare — if
75
+ you're seeing it anyway, it's one of:
76
+ - **`AMICUS_BASE_URL_NORMALIZE=0` is set**, the deliberate escape hatch — it disables the fix
77
+ entirely. Easy to hit by accident if you set it while chasing something unrelated.
78
+ - **`ANTHROPIC_BASE_URL` carries a nonstandard path** — anything other than blank/`/` (host-form)
79
+ or an already-correct `/v1` — which amicus passes through untouched rather than guessing (an
80
+ exotic proxy serving `/messages` at a custom root stays possible).
81
+ - You're running a **pre-v4.6.2** amicus, where none of the above exists yet.
82
+
83
+ **Confirm it in one command** (no key needed for the first three lines):
72
84
 
73
85
  ```bash
74
- echo "$ANTHROPIC_BASE_URL" # if this prints a URL with no /v1 suffix, that's the cause
75
- amicus start --model openrouter/anthropic/claude-haiku-4.5 --prompt hi --no-ui # works
76
- amicus start --model haiku --prompt hi --no-ui # "Not Found"
86
+ amicus doctor # anthropic-base-url row: the value seen + how it's treated
87
+ echo "$ANTHROPIC_BASE_URL" # host-form (no /v1 suffix) is the underlying condition
88
+ echo "$AMICUS_BASE_URL_NORMALIZE" # "0" here is what disables the automatic fix
89
+ amicus start --model haiku --prompt hi --no-ui # should complete; stderr shows the Notice line
90
+ # the first time normalization actually fires
77
91
  ```
78
92
 
79
93
  **Fix** — pick one:
80
- - Add the missing segment: `export ANTHROPIC_BASE_URL=https://api.anthropic.com/v1`
81
- - Or unset it entirely and let the SDK use its own default:
82
- `unset ANTHROPIC_BASE_URL` (PowerShell: `Remove-Item Env:\ANTHROPIC_BASE_URL`)
94
+ - Unset `AMICUS_BASE_URL_NORMALIZE` (or set it to anything other than `0`) to restore the default
95
+ self-heal.
96
+ - Still failing with normalization on? Add the missing segment yourself:
97
+ `export ANTHROPIC_BASE_URL=https://api.anthropic.com/v1` (PowerShell:
98
+ `$env:ANTHROPIC_BASE_URL = 'https://api.anthropic.com/v1'`), or unset the variable entirely:
99
+ `unset ANTHROPIC_BASE_URL` (PowerShell: `Remove-Item Env:\ANTHROPIC_BASE_URL`).
83
100
  - Or route Anthropic models through OpenRouter for the run: `--gateway openrouter`.
84
101
 
85
- Either of the first two makes `amicus start --model haiku …` complete normally. **Before spending
102
+ Any of these gets `amicus start --model haiku …` completing normally again. **Before spending
86
103
  money on a council, run one throwaway `amicus start` against each configured seat** — a
87
104
  `Not Found` there costs nothing, whereas discovering it mid-council costs a degraded verdict.
88
105
 
@@ -188,6 +205,18 @@ For full headless configuration, see [docs/configuration.md](./configuration.md)
188
205
 
189
206
  ---
190
207
 
208
+ ## Headless Leg Fails with `NO_OUTPUT_BACKSTOP`
209
+
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`.
211
+
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.
213
+
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.
215
+
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).
217
+
218
+ ---
219
+
191
220
  ## Multiple Active Sessions / Wrong Session Picked Up
192
221
 
193
222
  **Symptom:** Amicus resumes or reads from the wrong session.
package/docs/usage.md CHANGED
@@ -376,10 +376,29 @@ amicus models # List the catalog
376
376
  amicus models --search gemini # Filter by substring over id and name
377
377
  amicus models --refresh # Force-refresh from provider APIs
378
378
  amicus models --check # Audit your aliases against the catalog
379
+ amicus models --check --live # + probe every stored alias with a real leg (spends)
379
380
  ```
380
381
 
381
382
  `amicus models --check` exits with the **number of stale aliases** (capped at 100) and prints same-vendor replacement suggestions for each, so it drops cleanly into CI.
382
383
 
384
+ **Drifted aliases.** `--check` (and the `doctor` aliases row) also flags **`DRIFTED:`** stored aliases — a stored alias whose target is still catalog-listed but no longer matches any route its family currently resolves to (the v4.6.1 `gemini` release-gate class, where `doctor` stayed green while the model behind it had moved on). Each drift line prints the exact `amicus setup --add-alias <alias>=<current>` refresh command. Drift is informational only — unlike stale aliases, it never changes the exit code.
385
+
386
+ **Live probe (`--check --live`).** Presence in the catalog is not proof of service — a stored alias can point at a model id the catalog still lists but the provider has quietly stopped serving (the v4.6.1 `gemini` incident). `--check` alone can't see that; `--live` can, by actually asking. Scope is **stored aliases only** (`amicus setup --add-alias`) — curated defaults follow the catalog by construction and have no "was it actually served" question for a live probe to answer. **This spends real money — one tiny leg per stored alias** — every probed alias gets one ordinary engine leg on a single quiet fan-out wave, with a real session dir and a real spend-ledger row, exactly as if you'd run it yourself.
387
+
388
+ Each stored alias resolves to one of three outcomes:
389
+
390
+ | Outcome | Example line | Meaning |
391
+ |---------|--------------|---------|
392
+ | `SERVED` | `SERVED: gemini -> openrouter/google/gemini-3.6-flash ($0.0004)` | The model answered; cost shown in parens. |
393
+ | `SILENT` (`accepted-but-silent`) | `SILENT: probetest -> anthropic/claude-opus-4-8 — NO_OUTPUT_BACKSTOP: … (accepted but not serving)` | The endpoint accepted the request but produced nothing for the probe's 30 s backstop window (shorter than the ordinary 120 s default, and not tunable) — the exact "listed but not actually serving" failure this check exists to catch. |
394
+ | `ERROR` | `ERROR: gpt -> openai/gpt-5.6-terra — 402 Payment Required` | Routing, auth, or provider failure; the raw error is printed. |
395
+
396
+ **Exit code.** The probe's non-served count folds into the same exit code as the static audit — `max(existing exit, min(nonServedCount, 100))` — so a single `SILENT` or `ERROR` fails the check even when every alias is otherwise catalog-fresh. No stored aliases prints `Live probe: no stored aliases to probe` and never affects the exit code. `--json` adds `probe` (the per-alias array) and `probeCount` (its length) to the `alias-audit` document — both additive, `[]`/`0` when `--live` wasn't passed.
397
+
398
+ **Cap.** The probe is one fan-out leg per stored alias, so it's bound by the same fan-out leg cap as everything else — 10 by default, raise it with `AMICUS_FANOUT_MAX_LEGS`. More stored aliases than the cap fails fast with a one-line error and probes nothing, so a doomed wave never spends a token.
399
+
400
+ **When it doesn't run.** `--live` requires `--check` (a bare `--live` errors immediately). If the catalog itself is unavailable, or `--refresh` is also on the command line (which returns before `--check` ever runs), the probe is skipped — Amicus says so instead of silently dropping the flag: `--live skipped: <reason> — nothing was probed`. The `--json` signal differs by path: for catalog-unavailable, the `alias-audit` document carries an additive `probeSkipped` field (a reason slug, e.g. `"catalog-unavailable"`; `null` once the probe actually ran or wasn't requested); for the `--refresh` case, `--json`'s stdout document is a `model-catalog` doc instead, which never carries `probeSkipped` — the announcement goes to stderr there so stdout stays valid JSON.
401
+
383
402
  **Validation on launch.** `start` and `fanout` validate the model against the catalog before launching. For an explicit `--model` on `continue`/`resume` this is **blocking** (a typo'd model fails fast with suggestions); for a model *inherited* from a prior session it's **advisory**. Skip it any time with `--no-validate-model`, or fix the catalog with `amicus models --refresh`.
384
403
 
385
404
  **Aliases are a curated seed, not a fixed list.** `amicus setup` seeds a curated set of short aliases (e.g. `gemini`, `gpt`, `opus`, `deepseek`), and you add or override them with `amicus setup --add-alias name=provider/model`. To see exactly what resolves on *your* machine, run `amicus models` — that is the source of truth.
@@ -443,7 +462,7 @@ $ amicus status demo123 --json
443
462
  "taskId": "demo123",
444
463
  "status": "complete",
445
464
  "elapsed": "5m 0s",
446
- "version": "4.6.0",
465
+ "version": "4.6.2",
447
466
  "model": "google/gemini-2.5-flash",
448
467
  "phase": "terminal"
449
468
  }
@@ -501,6 +520,7 @@ Runs every check below, in order, and prints a ✓/⚠/✗ line for each plus a
501
520
  | `default-model` | Your default model alias resolves | error |
502
521
  | `catalog` | Model-catalog cache present and within the 24h TTL | warn |
503
522
  | `aliases` | Your configured aliases still resolve against the catalog | warn |
523
+ | `anthropic-base-url` | `ANTHROPIC_BASE_URL` isn't host-form (host-form 404s every direct-Anthropic leg unless normalized) | warn |
504
524
  | `opencode-bin` | The OpenCode engine binary is on `PATH` | error |
505
525
  | `engine-mcp` | The engine copy `npx -y amicus@latest mcp` would actually launch (catches a broken npx-cache copy a healthy local install would hide) | warn (error only if there's exactly one npx-cache copy and it's broken) |
506
526
  | `electron` | Electron (the interactive GUI) is installed | warn — headless still works |
@@ -5,14 +5,14 @@
5
5
  * delete, and add functionality for the setup wizard Step 3.
6
6
  */
7
7
 
8
- /** Grouping metadata for the 20 default aliases */
8
+ /** Grouping metadata for the 19 default aliases */
9
9
  const ALIAS_GROUPS = [
10
10
  { name: 'Gemini', keys: ['gemini', 'gemini-pro'] },
11
11
  { name: 'GPT', keys: ['gpt', 'gpt-pro', 'codex'] },
12
12
  { name: 'Claude', keys: ['claude', 'sonnet', 'opus', 'haiku', 'fable'] },
13
13
  { name: 'DeepSeek', keys: ['deepseek'] },
14
14
  { name: 'Qwen', keys: ['qwen', 'qwen-coder', 'qwen-flash'] },
15
- { name: 'Mistral', keys: ['mistral', 'devstral'] },
15
+ { name: 'Mistral', keys: ['mistral'] },
16
16
  { name: 'Other', keys: ['glm', 'minimax', 'grok', 'kimi', 'seed'] },
17
17
  ];
18
18
 
@@ -102,6 +102,9 @@
102
102
  <script src="./live-model.js"></script>
103
103
  <script src="./workspace-render.js"></script>
104
104
  <script src="./workspace-matrix.js"></script>
105
+ <!-- ⚠️ D8 extraction (Task 1, v4.6.2 PR4): workspace-seats.js must load BEFORE
106
+ workspace-panels.js, whose renderSeatsPanel delegates to it. -->
107
+ <script src="./workspace-seats.js"></script>
105
108
  <!-- ⚠️ DE-ROT (F05): panels + verbs are the Task 13 split of workspace-app.js.
106
109
  Load order matters: workspace-app.js boots last and owns `state`. -->
107
110
  <script src="./workspace-panels.js"></script>
@@ -104,8 +104,79 @@
104
104
  });
105
105
  }
106
106
 
107
+ /**
108
+ * D6 (v4.6.2 PR4 Task 2, "dead-seat rows"): announced-dead seats, unioned
109
+ * from the run's own `degrades[]` (dead-leg/dead-wave channels — the live
110
+ * announcement; shapes verified against src/council/run-retry-notes.js and
111
+ * the skipped-path notes at src/council/run-stages.js:155-174) and the
112
+ * verdict's derived `seatLoss` (verdict.js summarizeSeatLoss/deriveSeatLoss
113
+ * — the critic-loss backstop, kept for verdicts written before `degrades[]`
114
+ * existed, v4.5.2 precedent). De-duped by seat model: `degrades` is scanned
115
+ * FIRST, so a real record's retry marker always beats the backstop's
116
+ * no-data guess for the same seat.
117
+ *
118
+ * `retried` reads `data.retryWaveId` / `data.firstFailure`: every
119
+ * still-dead-after-retry note (run-retry-notes.js's four builders) carries
120
+ * `retryWaveId`; the two run-stages.js skipped-path notes (fired when the
121
+ * once-only retry pass never even attempted the seat — an unmappable lens
122
+ * loss, an out-of-range index, or a zero-model unit) carry neither, so they
123
+ * correctly fall back to the plain phrasing.
124
+ *
125
+ * D6 filter (zero usable legs ONLY, "no ghost when a retry succeeded"): a
126
+ * candidate already present in `liveSeats` (it has a cost row — SL-2 healed
127
+ * it) is dropped. This is the one thing standing between a recovered seat
128
+ * and a duplicate/ghost row — same failure family as the F37 debate-role
129
+ * collision and the RN-11 keyed-row lessons just above (seatsFromRunStats,
130
+ * seatCells): an identity that is not carefully matched silently
131
+ * duplicates or overwrites instead of failing loud.
132
+ *
133
+ * @param {Array<object>} degrades run.json's `degrades[]` (may be absent)
134
+ * @param {?object} seatLoss verdict.json's `seatLoss` (may be absent)
135
+ * @param {Array<{model: string}>} liveSeats seatsFromRunStats(...)'s output
136
+ * (or any seat list keyed the same way — the live seat map)
137
+ * @returns {Array<{model: string, statusText: string}>}
138
+ */
139
+ function deadSeats(degrades, seatLoss, liveSeats) {
140
+ var seen = {};
141
+ var order = [];
142
+ function add(model, retried) {
143
+ if (!model || seen[model]) { return; }
144
+ seen[model] = true;
145
+ order.push({
146
+ model: model,
147
+ statusText: retried ? 'did not review — retried once' : 'did not review',
148
+ });
149
+ }
150
+ (degrades || []).forEach(function (d) {
151
+ if (!d || d.kind !== 'degrade') { return; }
152
+ if (d.channel !== 'dead-leg' && d.channel !== 'dead-wave') { return; }
153
+ var data = d.data || {};
154
+ var retried = !!(data.retryWaveId || data.firstFailure);
155
+ if (d.channel === 'dead-leg') {
156
+ add(data.seat, retried);
157
+ } else {
158
+ (data.models || []).forEach(function (m) { add(m, retried); });
159
+ }
160
+ });
161
+ if (seatLoss && seatLoss.criticRequested && !seatLoss.criticSeated) {
162
+ add(seatLoss.criticRequested, false);
163
+ }
164
+ var live = {};
165
+ // ⚠️ Fable review (PR4b fix wave): same F34/F36 alias-selection seatCells already uses
166
+ // (`seat.modelInput || seat.model`, above) — a LIVE payload seat's `model` is the RESOLVED
167
+ // executable id, not the alias a degrade record names; `modelInput` carries the alias.
168
+ // Keying this map on `s.model` alone meant a dead-leg seat whose errored roster row was
169
+ // still in the active stage's `liveSeats` never matched its alias-keyed degrade candidate,
170
+ // so D6 failed to suppress it — both rows rendered until the stage boundary dropped the
171
+ // errored row. Terminal-path cost rows (seatsFromRunStats) carry no `modelInput` at all and
172
+ // are already alias-only, so `|| s.model` leaves that path unchanged.
173
+ (liveSeats || []).forEach(function (s) { live[s.modelInput || s.model] = true; });
174
+ return order.filter(function (s) { return !live[s.model]; });
175
+ }
176
+
107
177
  // ⚠️ DE-ROT (F41): STAGE_LABELS is exported so applyLive() can label post-open stages.
108
178
  var api = { pollDelay: pollDelay, seatCells: seatCells, seatsFromRunStats: seatsFromRunStats,
179
+ deadSeats: deadSeats,
109
180
  defaultBlind: defaultBlind, dash: dash, TERMINAL_STATUSES: TERMINAL_STATUSES, STAGE_LABELS: STAGE_LABELS };
110
181
  if (typeof module !== 'undefined' && module.exports) { module.exports = api; }
111
182
  if (typeof window !== 'undefined') { window.AmicusLive = api; }