the-high-council 0.7.8 → 0.7.9

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.
package/README.md CHANGED
@@ -7,7 +7,8 @@ You choose the number of debate rounds, the seats and labs (any lab except xAI),
7
7
  limit for each model's replies.
8
8
 
9
9
  We recommend our own chains: `cheap-7-v2`, `plan-premium-7` and `plan-highest-7` (the top
10
- models, GPT-6 Astra, Claude Fable 5.1, DeepSeek V4 Pro and GLM-5.3, write the alternative
10
+ models, GPT-6 Astra, Claude Opus 5.5, DeepSeek V4 Pro
11
+ and GLM-5.3, write the alternative
11
12
  architectures and are the only seats that vote; cheaper seats write the proposals and the debate). You can also edit a chain to suit yourself, or run every seat on
12
13
  your own machine with `local-ollama`.
13
14
 
@@ -40,8 +41,8 @@ Three commands from nothing to a priced run of your own idea (no key needed unti
40
41
  | Chain | Who does what | Needs | Worst case |
41
42
  |---|---|---|---|
42
43
  | `cheap-7-v2` | Claude Sonnet 5 writes the plan; low-cost models from seven other labs review it, and all seven must sign off. Up to 7 rounds. | one OpenRouter key | $5.38 |
43
- | `plan-premium-7` | A Claude Code session writes the plan; a seven-lab panel of larger models (GPT-6 Astra, Claude Fable 5.1, Gemini 3.8 Flash, Muse Spark 1.2, Qwen 3.8 Max, DeepSeek V4 Pro, GLM-5.3) proposes, debates and reviews. | OpenRouter key + a Claude Code session | $21.59 |
44
- | `plan-highest-7` | Seven low-cost models propose and debate; four top models write whole architectures and are the only reviewers; one deep-dive seat checks the first draft against the task. **Untested with real models.** | OpenRouter key + a Claude Code session | $14.89 |
44
+ | `plan-premium-7` | A Claude Code session writes the plan; a seven-lab panel of larger models (GPT-6 Astra, Claude Fable 5.1, Gemini 3.8 Flash, Muse Spark 1.2, Qwen 3.8 Max, DeepSeek V4 Pro, GLM-5.3) proposes, debates and reviews. | OpenRouter key + a Claude Code session | $22.30 |
45
+ | `plan-highest-7` | Seven low-cost models propose and debate; four top models write whole architectures and are the only reviewers; one deep-dive seat checks the first draft against the task. **Untested with real models.** | OpenRouter key + a Claude Code session | $11.21 |
45
46
  | `local-ollama` | Every seat on your own machine, through Ollama. | Ollama and the models pulled | $0 |
46
47
 
47
48
  `plan-premium-7` and `plan-highest-7` cost more than the default cap, so give them a `--max-usd` at
@@ -419,7 +420,7 @@ Each outcome has its own code, so a script or CI job can branch on it:
419
420
  | 8 | Finished, but the final security review could not judge it (never a pass). |
420
421
  | 9 | Artifact gate: the task names files whose content it never includes (`BLOCKED-ARTIFACTS.md`). |
421
422
  | 10 | Preflight: a seat objected to the task description itself (`STOPPED-preflight.md`). |
422
- | 11 | PII gate (`--pii-gate hard-stop`): the input matches a PII shape or one of the key formats listed in `src/secret-patterns.js`. |
423
+ | 11 | Something in the input must not be sent: a prompt matches one of the key formats listed in `src/secret-patterns.js` (checked by default; `--allow-secret-shaped` overrides), or, under `--pii-gate hard-stop`, a PII shape. |
423
424
  | 12 | `policy.json` refused the chain. |
424
425
  | 13 | Another process is already running this run folder. |
425
426
  | 14 | The task file changed since the run started and `AMENDMENTS.md` does not cover it. |
@@ -460,7 +461,7 @@ than reporting the overspend afterwards.
460
461
 
461
462
  ```bash
462
463
  council --task tasks/x.md --max-usd 2 # this run stops at $2
463
- council --task tasks/x.md --max-usd none # no ceiling
464
+ council --task tasks/x.md --max-usd none # no ceiling (0 is refused: it used to mean this)
464
465
  export MAX_USD_PER_RUN=20 # change the default
465
466
  ```
466
467
 
@@ -612,7 +613,7 @@ for anyone with a subscription, since the project's cost story depends on it.
612
613
 
613
614
  ## Chains
614
615
 
615
- 51 chain configs live in `chains/`. Each one is plain JSON - the seat roster, which models fill
616
+ 52 chain configs live in `chains/`. Each one is plain JSON - the seat roster, which models fill
616
617
  which seat, the round cap, and whether proposals/debate/handoff stages run. They are meant to be
617
618
  copied and edited.
618
619
 
@@ -623,6 +624,12 @@ in the table at the top. A few more worth knowing:
623
624
  and Google keys.
624
625
  - **`plan-open-7`** - the `cheap-7-v2` panel, with a Claude Code session writing the plan instead
625
626
  of a paid seat.
627
+ - **`plan-daily-7`** - `plan-highest-7`'s stages at a lower price, for planning one feature:
628
+ Claude Opus 5.5 on your Claude subscription (an external seat), GPT-6 Luna and DeepSeek V4 Pro
629
+ write the architectures and alone vote; seven low-cost labs propose and debate. Two Claude Code
630
+ sessions answer its external seats: Opus 5.5 every stage whose label contains `opus5.5-sub`
631
+ (including `-retry` and `-reask1`/`-reask2` forms), Sonnet 5 the writer stages. $1.90 worst case.
632
+ **Run once with real models (2026-09-28); nothing about it has been measured.**
626
633
  - **`cheap`** - small models throughout. For testing the harness itself, not for real work.
627
634
  - **`plan-debate`** - five labs propose blind, debate each other's proposals anonymised, then a
628
635
  blind panel grades the integrated draft.
@@ -654,7 +661,7 @@ Read a chain's `description` field before running it; they say what they cost yo
654
661
  - **`quorum: { "minHeard": N }`** - opt-in, unanimous chains only, set in no shipped chain. A
655
662
  round's sign-off counts only when at least N reviewers gave a verdict (signed off or objected);
656
663
  a round short of it is recorded as `notQuorate`, the run does not pass, and the loop stops.
657
- - **Tiered councils (experimental, used by `plan-highest-7` and `mock-tiered`).**
664
+ - **Tiered councils (experimental, used by `plan-highest-7`, `plan-daily-7` and `mock-tiered`).**
658
665
  `seats.alternatives` names who writes the whole alternative architectures, separately from the
659
666
  proposers. `deep_dive` + `seats.deep_dive` add one non-voting seat with its own dollar cap
660
667
  (`usd`, required) inside the run's cap. `majority_guard.enabled` shows each author every
@@ -740,11 +747,11 @@ what each rule guards against and why. Copy the folders you want into your proje
740
747
  oldest date every entry was last checked) is printed by `council doctor` and `--dry-run`, with a
741
748
  warning once it is more than 60 days old. The spend cap and every estimate use these static
742
749
  prices. They are estimates, not invoices. Your provider's bill is the real number.
743
- - `plan-highest-7`, the tiered council, has not been run with real models, and none of its
744
- mechanisms (tiers, the deep-dive seat, the majority guard) has been measured. Its dry-run price
745
- is a projection.
746
- - Seats marked `external` (the writer seats in `plan-premium-7`, `plan-highest-7` and
747
- `plan-open-7`) pause the run until someone answers them, and are $0 only because a Claude Code
750
+ - `plan-highest-7` has not been run with real models, and `plan-daily-7` has run once
751
+ (2026-09-28). None of the tiered councils' mechanisms (tiers, the deep-dive seat, the majority
752
+ guard) has been measured. Their dry-run prices are projections.
753
+ - Seats marked `external` (the writer seats in `plan-premium-7`, `plan-highest-7`,
754
+ `plan-daily-7` and `plan-open-7`, and the Opus 5.5 anchor seat in `plan-daily-7`) pause the run until someone answers them, and are $0 only because a Claude Code
748
755
  session on a subscription writes them. Nothing checks who wrote the answer.
749
756
  - Chains with many labs and high round caps get expensive quickly. `plan-unanimous` at three rounds
750
757
  is fifteen critic calls; `plan-auto` runs several frontier models over multiple rounds. Price
@@ -758,6 +765,10 @@ what each rule guards against and why. Copy the folders you want into your proje
758
765
  which is the default, it does not detect a full rewrite**: anyone who can edit the file can
759
766
  recompute every hash. Set `AUDIT_HMAC_KEY_FILE` to a key kept outside the run folder if the log
760
767
  has to stand up to that.
768
+ - The outbound key scan matches **key shapes only**, the formats in `src/secret-patterns.js`; a
769
+ password written in prose is not detectable. Generic shapes (a password inside a URL or an
770
+ assignment) are checked in your inputs before the run; text the models write is checked only for
771
+ distinctive key formats.
761
772
 
762
773
  ## Privacy
763
774
 
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "plan-daily-7",
3
+ "description": "daily7 (the owner's name, 2026-09-27: cheap API seats, and the expensive Opus 5.5 seat runs on a local Claude Code session). Tiered council for planning ONE feature cheaply (Muad, 2026-09-27: \"Opus 5.5 on a local sub that does the important tasks from the highest7 setup ... all together ~1$ per council run\"). Same stages and flags as plan-highest-7 (alternatives, debate, decision records, deep dive, majority guard ON, canary, dispute, handoff); only the seats and the deep-dive cap differ. ANCHOR SEATS write the whole alternative architectures (seats.alternatives), answer the posts on them, and are the only panel that votes, unanimously (seats.critics): Claude Opus 5.5 on the owner's Claude subscription (provider external, model subscription:opus-5.5, lab opus5.5-sub: $0 API, a separate Opus 5.5 Claude Code session answers its paused stages), GPT-6 Luna and DeepSeek V4 Pro through OpenRouter. MASS SEATS (seats.proposers, seven labs from the strong and low-cost part of arena.ai's agent leaderboard, fetched 2026-09-27): Tencent Hy4 preview, Gemini 3.8 Flash, DeepSeek V4.1 Flash, GLM 5.2, Muse Spark 1.3, Qwen 3.8 Max, Mistral Medium 3.5 (kept on the owner's word) write the blind proposals and every debate post and reply, and post on the anchors' architectures (alternatives.debaters \"all\"); they argue, they never vote. DEEP-DIVE SEAT: DeepSeek V4.1 Flash, a mass-tier model (a different model from the voting V4 Pro, so chain-lint deep-dive-votes passes), under its own $0.50 cap: a feature-sized task is one 40,000-character call, and a $4 sub-cap would be larger than this chain's whole worst case. Criteria: Llama 4 Maverick, which holds no other seat. WRITER SEATS (skeleton, build, deep-dive-revise, revise-N, dispute, handoff) are a Claude Code session running Sonnet 5, which builds and holds no panel seat. The Opus 5.5 panel seat is a DIFFERENT Anthropic model in a separate session: the owner's plan-highest-7 exception (an Anthropic model reviews a draft another Claude writes), not selfReview - no model writes and votes. ROUTING, because a NEEDS file names only its label: every label containing opus5.5-sub goes to the Opus 5.5 session (alternative-, alt-debate-, alt-reply-, panel-N-, including the suffixed forms panel-N-opus5.5-sub-retry, -reask1, -reask2 and alternative-opus5.5-sub-retry); skeleton, build, deep-dive-revise, revise-N, dispute and handoff go to the Sonnet 5 session. Nothing checks who wrote an answer. Dry run on a one-page feature task (2026-09-27, GPT-6 Luna in place of GPT-6 Sol on the owner's word: \"GPT 6 Sol is too expensive, take GPT 6 luna\"): $1.90 worst case at 7 rounds; under the default $7 cap. No Kimi, no xAI. Run once with real models (2026-09-28); none of its mechanisms has been measured. Price your task with `npm run dry` and get an explicit go before any paid run.",
4
+ "maxRounds": 7,
5
+ "signoff": "unanimous",
6
+ "dispute": { "enabled": true, "stall_rounds": 2 },
7
+ "proposals": { "parts": 3, "maxTokens": 4000 },
8
+ "debate": true,
9
+ "decisions": { "enabled": true },
10
+ "alternatives": { "enabled": true, "debaters": "all" },
11
+ "deep_dive": { "enabled": true, "job": "sources", "usd": 0.5, "maxCalls": 24, "chunkChars": 40000 },
12
+ "majority_guard": { "enabled": true },
13
+ "canary": { "enabled": true, "sampleRate": 0.1 },
14
+ "handoff": true,
15
+ "estimate": { "promptTokens": 10000, "draftTokens": 10000, "critiqueTokens": 5000 },
16
+ "seats": {
17
+ "criteria": { "provider": "openrouter", "model": "meta-llama/llama-4-maverick", "maxTokens": 36000, "lab": "llama4-maverick" },
18
+ "skeleton": { "provider": "external", "model": "claude-code-session" },
19
+ "builder": { "provider": "external", "model": "claude-code-session" },
20
+ "reviser": { "provider": "external", "model": "claude-code-session" },
21
+ "handoff": { "provider": "external", "model": "claude-code-session" },
22
+ "proposers": [
23
+ { "provider": "openrouter", "model": "tencent/hy4-preview", "maxTokens": 36000, "lab": "hy4-preview" },
24
+ { "provider": "openrouter", "model": "google/gemini-3.8-flash", "maxTokens": 36000, "lab": "gemini3.8-flash" },
25
+ { "provider": "openrouter", "model": "deepseek/deepseek-v4.1-flash", "maxTokens": 36000, "lab": "deepseek-v4.1-flash" },
26
+ { "provider": "openrouter", "model": "z-ai/glm-5.2", "maxTokens": 36000, "lab": "glm5.2" },
27
+ { "provider": "openrouter", "model": "meta/muse-spark-1.3", "maxTokens": 36000, "lab": "muse-spark1.3" },
28
+ { "provider": "openrouter", "model": "qwen/qwen3.8-max-0902", "maxTokens": 36000, "lab": "qwen3.8-max" },
29
+ { "provider": "openrouter", "model": "mistralai/mistral-medium-3-5", "maxTokens": 36000, "lab": "mistral-medium3.5" }
30
+ ],
31
+ "alternatives": [
32
+ { "provider": "external", "model": "subscription:opus-5.5", "lab": "opus5.5-sub" },
33
+ { "provider": "openrouter", "model": "openai/gpt-6-luna", "maxTokens": 36000, "lab": "gpt6-luna" },
34
+ { "provider": "openrouter", "model": "deepseek/deepseek-v4-pro", "maxTokens": 64000, "lab": "deepseek-v4-pro" }
35
+ ],
36
+ "critics": [
37
+ { "provider": "external", "model": "subscription:opus-5.5", "lab": "opus5.5-sub" },
38
+ { "provider": "openrouter", "model": "openai/gpt-6-luna", "maxTokens": 36000, "lab": "gpt6-luna" },
39
+ { "provider": "openrouter", "model": "deepseek/deepseek-v4-pro", "maxTokens": 64000, "lab": "deepseek-v4-pro" }
40
+ ],
41
+ "deep_dive": { "provider": "openrouter", "model": "deepseek/deepseek-v4.1-flash", "maxTokens": 36000, "lab": "deepseek-v4.1-flash" }
42
+ }
43
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plan-highest-7",
3
- "description": "Tiered council, Muad's design (2026-09-26): the highest models for the highest tasks, the cheapest models for the mass work. MASS SEATS (seats.proposers, the cheap-7-v2 roster: GPT-5.6 Luna, Gemini 3.8 Flash, DeepSeek V4.1 Flash, GLM 5.3 Flash, Qwen 3.7 Plus, MiniMax M3, Mistral Medium 3.5) write the blind proposals and all the proposal debate posts and replies, and post on the anchors' architectures (alternatives.debaters \"all\"). ANCHOR SEATS: GPT-6 Astra and Claude Fable 5.1 (his deliberate exception: an Anthropic model reviews a draft a Claude Code session writes), DeepSeek V4 Pro and GLM-5.3 write the whole alternative architectures (seats.alternatives), answer the posts on them, and form the review panel that must sign off unanimously (seats.critics). The mass seats argue; only the anchors vote, so a cheap majority can never out-vote them. DEEP-DIVE SEAT (seats.deep_dive, DeepSeek V4.1 Flash, a mass-tier model: it must not share a lab with a voting anchor, chain-lint deep-dive-votes): one job, checking the first draft against every part of the task, 40,000 characters per call at a 36k output cap, up to 24 calls under its OWN $4 cap inside the run's; it does not vote, and the reviser answers its findings before the anchors' first review. The MAJORITY GUARD is ON (majority_guard, Muad's go 2026-09-26; off in every other shipped chain but mock-tiered): authors see each argument once with no lab letter or count, the reply prompts carry an evidence bar, and a withdrawal that does not quote the argument it concedes to is kept for the builder. Criteria move to Llama 4 Maverick, which holds no other seat. Same Claude Code writer seats and stages as plan-premium-7, including decision records and the dispute stage. No Kimi, no xAI. Untested with real models: price it with `npm run dry` and get an explicit go before any paid run.",
3
+ "description": "Tiered council, Muad's design (2026-09-26): the highest models for the highest tasks, the cheapest models for the mass work. MASS SEATS (seats.proposers, the cheap-7-v2 roster: GPT-5.6 Luna, Gemini 3.8 Flash, DeepSeek V4.1 Flash, GLM 5.3 Flash, Qwen 3.7 Plus, MiniMax M3, Mistral Medium 3.5) write the blind proposals and all the proposal debate posts and replies, and post on the anchors' architectures (alternatives.debaters \"all\"). ANCHOR SEATS: GPT-6 Astra and Claude Opus 5.5 (Muad's pick 2026-09-26, in place of Fable 5.1; still his deliberate exception: an Anthropic model reviews a draft a Claude Code session writes), DeepSeek V4 Pro and GLM-5.3 write the whole alternative architectures (seats.alternatives), answer the posts on them, and form the review panel that must sign off unanimously (seats.critics). The mass seats argue; only the anchors vote, so a cheap majority can never out-vote them. DEEP-DIVE SEAT (seats.deep_dive, DeepSeek V4.1 Flash, a mass-tier model: it must not share a lab with a voting anchor, chain-lint deep-dive-votes): one job, checking the first draft against every part of the task, 40,000 characters per call at a 36k output cap, up to 24 calls under its OWN $4 cap inside the run's; it does not vote, and the reviser answers its findings before the anchors' first review. The MAJORITY GUARD is ON (majority_guard, Muad's go 2026-09-26; off in every other shipped chain but mock-tiered): authors see each argument once with no lab letter or count, the reply prompts carry an evidence bar, and a withdrawal that does not quote the argument it concedes to is kept for the builder. Criteria move to Llama 4 Maverick, which holds no other seat. Same Claude Code writer seats and stages as plan-premium-7, including decision records and the dispute stage. No Kimi, no xAI. Untested with real models: price it with `npm run dry` and get an explicit go before any paid run.",
4
4
  "maxRounds": 7,
5
5
  "signoff": "unanimous",
6
6
  "dispute": { "enabled": true, "stall_rounds": 2 },
@@ -30,13 +30,13 @@
30
30
  ],
31
31
  "alternatives": [
32
32
  { "provider": "openrouter", "model": "openai/gpt-6-astra", "maxTokens": 36000, "lab": "gpt6-astra" },
33
- { "provider": "openrouter", "model": "anthropic/claude-fable-5.1", "maxTokens": 36000, "lab": "fable5.1" },
33
+ { "provider": "openrouter", "model": "anthropic/claude-opus-5.5", "maxTokens": 36000, "lab": "opus5.5", "estimate": { "critiqueTokens": 10000 } },
34
34
  { "provider": "openrouter", "model": "deepseek/deepseek-v4-pro", "maxTokens": 64000, "lab": "deepseek-v4-pro" },
35
35
  { "provider": "openrouter", "model": "z-ai/glm-5.3", "maxTokens": 64000, "lab": "glm5.3" }
36
36
  ],
37
37
  "critics": [
38
38
  { "provider": "openrouter", "model": "openai/gpt-6-astra", "maxTokens": 36000, "lab": "gpt6-astra" },
39
- { "provider": "openrouter", "model": "anthropic/claude-fable-5.1", "maxTokens": 36000, "lab": "fable5.1" },
39
+ { "provider": "openrouter", "model": "anthropic/claude-opus-5.5", "maxTokens": 36000, "lab": "opus5.5", "estimate": { "critiqueTokens": 10000 } },
40
40
  { "provider": "openrouter", "model": "deepseek/deepseek-v4-pro", "maxTokens": 64000, "lab": "deepseek-v4-pro" },
41
41
  { "provider": "openrouter", "model": "z-ai/glm-5.3", "maxTokens": 64000, "lab": "glm5.3" }
42
42
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "the-high-council",
3
- "version": "0.7.8",
3
+ "version": "0.7.9",
4
4
  "mcpName": "io.github.muad-yasin/the-high-council",
5
5
  "description": "The High Council: a multi-model planning harness. One builder, independent critics from other labs, checkable acceptance criteria, a debate on the record, and a hard stop.",
6
6
  "type": "module",
@@ -83,7 +83,7 @@
83
83
  "outcome": {
84
84
  "x-stability": "stable",
85
85
  "enum": ["consensus", "no_consensus", "degraded"],
86
- "description": "Verdict class, computed by src/outcome.js from the rest of this file. `degraded` wins: some seat dropped out, abstained, or no reviewer was heard, so the intended roster never reached a verdict. Otherwise `consensus` if passed, else `no_consensus`."
86
+ "description": "Verdict class, computed by src/outcome.js from the rest of this file. `degraded` wins: a seated lab produced nothing usable (it dropped out, see `dropouts[]`: in a tiered chain this may be a debating seat that never votes), a reviewer abstained, no reviewer was heard, or the round fell short of the quorum - so the run did not have the roster the chain intended. Otherwise `consensus` if passed, else `no_consensus`."
87
87
  },
88
88
  "lastCritique": {
89
89
  "x-stability": "stable",
@@ -234,7 +234,8 @@
234
234
  "model": { "type": "string" },
235
235
  "verdict": { "enum": ["signed_off", "objected", "passed", "unheard"] },
236
236
  "reason_code": { "anyOf": [{ "type": "null" }, { "$ref": "#/$defs/abstentionReason" }] },
237
- "reasked": { "type": "boolean", "description": "True if the seat was unheard once and asked again this round." }
237
+ "reasked": { "type": "boolean", "description": "True if the seat was unheard once and asked again this round." },
238
+ "carried": { "type": "boolean", "description": "Present and true when the seat was not asked this round: the draft was byte-identical to the last round's, where this seat was heard and clean, and only the unheard seat(s) were asked again (0.7.9). Absent otherwise." }
238
239
  }
239
240
  },
240
241
  "description": "Every critic's verdict in every review round, both sign-off modes. The per-round history that signoff[] (final round only) does not keep."
@@ -614,7 +615,8 @@
614
615
  },
615
616
  "passed": { "type": "boolean", "description": "True if the seat stated a pass (declined to give a verdict, with a reason). Not the same as the top-level `passed`." },
616
617
  "passReason": { "type": ["string", "null"] },
617
- "reason_code": { "anyOf": [{ "type": "null" }, { "$ref": "#/$defs/abstentionReason" }] }
618
+ "reason_code": { "anyOf": [{ "type": "null" }, { "$ref": "#/$defs/abstentionReason" }] },
619
+ "seat_index": { "type": "integer", "description": "The seat's index in the chain's seats.critics (0.7.9). A relay panel lists signoff[] in its review order, so array position is not the seat; replay and rematch diffs key on this. Absent on earlier reports." }
618
620
  }
619
621
  },
620
622
  "proposal": {
@@ -684,14 +686,14 @@
684
686
  },
685
687
  "dropped": {
686
688
  "type": "array",
687
- "description": "Experimental (0.7.8). Debate posts and author replies the stage's filters rejected, one row per stage, lab and reason; [] when none were. Absent in runs made before 0.7.8. Reasons: for stage 'debate' unknown_target, own_proposal, bad_stance, unreadable; for stage 'replies' unknown_target, not_own_proposal, not_posted_on, bad_action, unreadable. An 'unreadable' row counts whole replies that did not parse, not posts.",
689
+ "description": "Experimental (0.7.8). Debate posts and author replies the stage's filters rejected, one row per stage, lab and reason; [] when none were. Absent in runs made before 0.7.8. Reasons: for stage 'debate' unknown_target, own_proposal, bad_stance, unreadable, provider_error; for stage 'replies' unknown_target, not_own_proposal, not_posted_on, bad_action, unreadable, provider_error. An 'unreadable' row counts whole replies that did not parse, not posts; 'provider_error' (0.7.9) counts whole replies the provider ended with stop error, content_filter or refusal on the first try and on its one retry.",
688
690
  "items": {
689
691
  "type": "object",
690
692
  "required": ["stage", "by", "reason", "count"],
691
693
  "properties": {
692
694
  "stage": { "enum": ["debate", "replies"] },
693
695
  "by": { "type": "string", "description": "The lab whose post or reply was dropped." },
694
- "reason": { "enum": ["unknown_target", "own_proposal", "bad_stance", "not_own_proposal", "not_posted_on", "bad_action", "unreadable"] },
696
+ "reason": { "enum": ["unknown_target", "own_proposal", "bad_stance", "not_own_proposal", "not_posted_on", "bad_action", "unreadable", "provider_error"] },
695
697
  "count": { "type": "integer", "minimum": 1 }
696
698
  }
697
699
  }
package/src/api.js CHANGED
@@ -72,9 +72,9 @@ export function price({ chain, cwd = process.cwd(), rounds } = {}) {
72
72
  }
73
73
 
74
74
  // The ceiling is never silently off. undefined: the CLI's own default (MAX_USD_PER_RUN, else $7).
75
- // A positive number: that ceiling. 'none': no ceiling, said explicitly. The CLI and the MCP tool
76
- // read 0 as "no ceiling"; here 0 is refused, because a caller who wrote 0 almost certainly meant
77
- // "spend nothing", and reading it as "spend without limit" is the costliest possible guess.
75
+ // A positive number: that ceiling. 'none': no ceiling, said explicitly. 0 is refused, because a
76
+ // caller who wrote 0 almost certainly meant "spend nothing", and reading it as "spend without limit"
77
+ // is the costliest possible guess. Since 0.7.9 the CLI and the MCP tools refuse it too.
78
78
  function maxUsdArgs(maxUsd) {
79
79
  if (maxUsd === undefined) return [];
80
80
  if (maxUsd === 'none') return ['--max-usd', 'none'];
@@ -110,8 +110,12 @@ function runCli(args, { cwd, env, onOutput, signal }) {
110
110
  };
111
111
  child.stdout.on('data', take('stdout'));
112
112
  child.stderr.on('data', take('stderr'));
113
- child.on('error', reject);
114
- child.on('close', (code, sig) => resolvePromise({ exitCode: code, signal: sig, outputTail: tail.join('').split('\n').slice(-40).join('\n') }));
113
+ // Bug audit 2026-09-28 (area 4 MED-1): an abort rejected on 'error', before the child had exited,
114
+ // so a caller that resumed at once could meet a run still marked running. The abort now settles
115
+ // only on 'close'; any other spawn error rejects at once, as before.
116
+ let aborted = null;
117
+ child.on('error', err => { if (err?.name === 'AbortError') aborted = err; else reject(err); });
118
+ child.on('close', (code, sig) => aborted ? reject(aborted) : resolvePromise({ exitCode: code, signal: sig, outputTail: tail.join('').split('\n').slice(-40).join('\n') }));
115
119
  });
116
120
  }
117
121
 
@@ -120,7 +124,7 @@ function runCli(args, { cwd, env, onOutput, signal }) {
120
124
  * spend ceiling, or refused by a guard. Resolves in every one of those cases (read `status` and
121
125
  * `exitCode`); rejects only when the child process could not be started or was aborted.
122
126
  */
123
- export async function run({ chain, task, cwd = process.cwd(), maxUsd, rounds, draft, fromRun, context, piiGate, env, onOutput, signal } = {}) {
127
+ export async function run({ chain, task, cwd = process.cwd(), maxUsd, rounds, draft, fromRun, context, piiGate, allowSecretShaped, env, onOutput, signal } = {}) {
124
128
  const work = resolve(cwd);
125
129
  chainPath(chain, work); // a clear error here beats a CLI usage message
126
130
  if (typeof task !== 'string' || !task) throw new TypeError('task: a path to the task file');
@@ -137,8 +141,15 @@ export async function run({ chain, task, cwd = process.cwd(), maxUsd, rounds, dr
137
141
  if (piiGate !== 'warn' && piiGate !== 'hard-stop') throw new RangeError(`piiGate: 'warn' or 'hard-stop', got ${JSON.stringify(piiGate)}`);
138
142
  args.push('--pii-gate', piiGate);
139
143
  }
140
- const res = await runCli(args, { cwd: work, env, onOutput, signal });
144
+ // 0.7.9 outbound key scan: the one override (saved in run.json, so a resume keeps it).
145
+ if (allowSecretShaped === true) args.push('--allow-secret-shaped');
141
146
  const runDir = join(work, 'runs', id);
147
+ // Area 4 MED-1: an aborted run's stages stay on disk and can be resumed, so the rejection says
148
+ // which run it was (`runId`, and `runDir` once the folder exists).
149
+ const res = await runCli(args, { cwd: work, env, onOutput, signal }).catch(err => {
150
+ if (err && typeof err === 'object') Object.assign(err, { runId: id, runDir: existsSync(runDir) ? runDir : null });
151
+ throw err;
152
+ });
142
153
  return { runId: id, runDir: existsSync(runDir) ? runDir : null, ...res, ...(existsSync(runDir) ? statusOf(runDir) : { status: 'not_started', report: null }) };
143
154
  }
144
155
 
@@ -146,11 +157,12 @@ export async function run({ chain, task, cwd = process.cwd(), maxUsd, rounds, dr
146
157
  * Resume a paused run (after its external stage was answered) or one the spend ceiling stopped
147
158
  * (pass a higher maxUsd). Stages already on disk replay from disk and cost nothing again.
148
159
  */
149
- export async function resume({ runDir, cwd = process.cwd(), maxUsd, env, onOutput, signal } = {}) {
160
+ export async function resume({ runDir, cwd = process.cwd(), maxUsd, allowSecretShaped, env, onOutput, signal } = {}) {
150
161
  const work = resolve(cwd);
151
162
  const dir = resolve(work, runDir ?? '');
152
163
  if (!runDir || !existsSync(join(dir, 'run.json'))) throw new Error(`runDir: not a run folder with a run.json: ${dir}`);
153
- const res = await runCli(['--resume', dir, ...maxUsdArgs(maxUsd)], { cwd: work, env, onOutput, signal });
164
+ // Verify pass 2026-09-28 F1(b): continue a run the outbound key scan stopped.
165
+ const res = await runCli(['--resume', dir, ...maxUsdArgs(maxUsd), ...(allowSecretShaped === true ? ['--allow-secret-shaped'] : [])], { cwd: work, env, onOutput, signal });
154
166
  return { runId: basename(dir), runDir: dir, ...res, ...statusOf(dir) };
155
167
  }
156
168
 
package/src/chain-lint.js CHANGED
@@ -763,7 +763,7 @@ export function lintChain(config, filePath = '<chain>') {
763
763
  try { roster = resolveChainSeats(config || {}); } catch { /* malformed config: check the raw one */ }
764
764
  const walk = (node, path) => {
765
765
  if (!node || typeof node !== 'object') return;
766
- if (typeof node.provider === 'string' && 'model' in node) {
766
+ if (typeof node.provider === 'string') {
767
767
  const reasons = keyDestinationReasons(node);
768
768
  if (reasons.length) {
769
769
  findings.push({
@@ -806,6 +806,29 @@ export function lintChain(config, filePath = '<chain>') {
806
806
  });
807
807
  }
808
808
 
809
+ // extra-models-unpriced (bug audit 2026-09-27 H1): a priced seat's OpenRouter fallbacks
810
+ // (extra.models) may answer instead of its model. invoke() charges an unpriced answer at the seat's
811
+ // own price, which understates a dearer fallback; a price entry for each fallback keeps the cap honest.
812
+ {
813
+ const walk = (node, path) => {
814
+ if (!node || typeof node !== 'object') return;
815
+ if (typeof node.provider === 'string' && typeof node.model === 'string' && Array.isArray(node.extra?.models)
816
+ && node.provider !== 'mock' && node.provider !== 'external' && priceOf(node.provider, node.model)) {
817
+ for (const m of node.extra.models) {
818
+ if (typeof m === 'string' && !priceOf(node.provider, m)) {
819
+ findings.push({
820
+ kind: 'extra-models-unpriced',
821
+ message: `${path}: fallback model "${m}" in extra.models has no price in src/pricing.json, so when it answers the spend cap would charge it at ${node.model}'s price.`,
822
+ fix: `Add "${node.provider}/${m}" to src/pricing.json, or remove it from extra.models in ${filePath}.`,
823
+ });
824
+ }
825
+ }
826
+ }
827
+ for (const [k, v] of Object.entries(node)) if (k !== 'extra') walk(v, path ? `${path}.${k}` : k);
828
+ };
829
+ walk(seats, 'seats');
830
+ }
831
+
809
832
  // Tiered councils (2026-09-26): anchor seats, mass seats, a deep-dive seat. Each rule names a
810
833
  // config that would silently do nothing, or spend without a ceiling.
811
834
  const dd = config?.deep_dive;