the-high-council 0.8.3 → 0.8.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +5 -5
- package/KNOWN-LIMITS.md +208 -0
- package/PERSONAS.md +26 -3
- package/README.md +114 -1128
- package/TROUBLESHOOTING.md +4 -3
- package/chains/local-ollama.json +3 -0
- package/chains/mock-budget.json +1 -0
- package/chains/mock-criteria-kinds.json +1 -0
- package/chains/mock-debate.json +3 -0
- package/chains/mock-dispute.json +4 -1
- package/chains/mock-external.json +3 -0
- package/chains/mock-layered.json +4 -1
- package/chains/mock-open.json +3 -0
- package/chains/mock-partitioned.json +3 -0
- package/chains/mock-patch-fallback.json +3 -0
- package/chains/mock-patch.json +3 -0
- package/chains/mock-proposals.json +3 -0
- package/chains/mock-questions-wait.json +3 -0
- package/chains/mock-questions.json +3 -0
- package/chains/mock-relay.json +3 -0
- package/chains/mock-security-review.json +3 -0
- package/chains/mock-tiered.json +1 -0
- package/chains/mock-unanimous.json +3 -0
- package/chains/mock.json +1 -0
- package/chains/plan-china-high-council.json +124 -149
- package/chains/plan-china-medium-council.json +133 -83
- package/chains/plan-claude-council.json +130 -43
- package/chains/plan-high-council.json +151 -137
- package/chains/plan-medium-council.json +119 -96
- package/chains/plan-mini-council.json +136 -39
- package/chains/plan-the-high-council.json +341 -0
- package/package.json +6 -5
- package/schemas/excerpts-v1.json +51 -0
- package/schemas/report-v1.json +518 -76
- package/schemas/spend-reservations-v1.json +8 -4
- package/src/accept-cli.js +54 -0
- package/src/accept-run.js +120 -0
- package/src/acceptance.js +128 -0
- package/src/anchor.js +24 -0
- package/src/answer-back.js +20 -11
- package/src/answered-by.js +48 -2
- package/src/api.js +22 -9
- package/src/argued.js +4 -4
- package/src/billed-facts.js +40 -0
- package/src/board-export.js +3 -2
- package/src/board-money.js +93 -0
- package/src/build-check.js +65 -0
- package/src/builder-kind.js +74 -0
- package/src/cache-integrity.js +10 -3
- package/src/cache-order-gate.js +69 -0
- package/src/cache-order.js +101 -0
- package/src/cache-prices.js +14 -0
- package/src/call-ceiling.js +158 -0
- package/src/call-gates.js +64 -0
- package/src/cap-input.js +3 -0
- package/src/cap-screen.js +48 -13
- package/src/caps.js +348 -0
- package/src/chain-lint.js +126 -89
- package/src/chain-name.js +15 -10
- package/src/chain.js +3490 -453
- package/src/claim-markers.js +145 -0
- package/src/claude-route.js +193 -0
- package/src/cli.js +1441 -476
- package/src/container-text.js +1 -1
- package/src/context-window.js +30 -0
- package/src/context-windows.json +40 -0
- package/src/cost-forecast.js +3 -1
- package/src/cost.js +596 -79
- package/src/council-replay.js +3 -1
- package/src/counted-bases.js +14 -0
- package/src/counted-spend.js +178 -0
- package/src/criteria-ledger.js +78 -2
- package/src/criteria-lock.js +1 -1
- package/src/decision-entry.js +90 -0
- package/src/decision-records.js +9 -1
- package/src/default-cap.js +4 -4
- package/src/denied-models.js +2 -1
- package/src/dissent-digest.js +10 -3
- package/src/draft-bounds.js +18 -0
- package/src/draft-disputes.js +42 -6
- package/src/dry-run.js +15 -17
- package/src/early-finish-eligible.js +25 -0
- package/src/ending-exchange-prompts.js +52 -0
- package/src/ending-matrix.js +166 -0
- package/src/ending.js +101 -0
- package/src/env-file.js +18 -10
- package/src/errors.js +1 -1
- package/src/escalation.js +191 -0
- package/src/excerpt-approval.js +213 -0
- package/src/excerpt-channel.js +70 -0
- package/src/excerpt-extract.js +136 -0
- package/src/excerpt-pause.js +141 -0
- package/src/excerpt-record.js +210 -0
- package/src/excerpt-resume.js +202 -0
- package/src/excerpt-scan.js +21 -0
- package/src/excerpt-slot.js +20 -0
- package/src/exchange-json-mode.js +64 -0
- package/src/exit-codes.js +7 -0
- package/src/fence-tests.js +115 -0
- package/src/fence.js +15 -0
- package/src/final-read-gate.js +3 -0
- package/src/final-read-room.js +33 -0
- package/src/final-read-words.js +6 -0
- package/src/final-read.js +295 -0
- package/src/gate-cli.js +123 -58
- package/src/gate-facts.js +15 -0
- package/src/gate-ledger.js +199 -1
- package/src/gate-relay-cli.js +211 -0
- package/src/gate.js +896 -91
- package/src/handler-notes.js +226 -0
- package/src/handler-retags.js +49 -0
- package/src/handler-sort.js +35 -0
- package/src/handoff-contract-text.js +32 -7
- package/src/handoff-from-run.js +25 -8
- package/src/handoff-prompt.js +29 -3
- package/src/handoff-status.js +85 -0
- package/src/held-roles.js +3 -3
- package/src/hidden-chars.js +0 -3
- package/src/host-block.js +39 -0
- package/src/integrity.js +7 -0
- package/src/judge-persona.js +153 -0
- package/src/lab-name-warnings.js +37 -0
- package/src/ladder-resume.js +33 -0
- package/src/ladder-steps.js +30 -0
- package/src/ladder.js +46 -0
- package/src/lanes.js +2 -2
- package/src/late-finding.js +139 -0
- package/src/layer-debate.js +228 -0
- package/src/layers.js +47 -5
- package/src/lints.js +7 -3
- package/src/mcp/contract.js +2 -1
- package/src/mcp/hold-budget.js +6 -0
- package/src/mcp/pause.js +32 -0
- package/src/mcp/price-raise.js +1 -1
- package/src/mcp/run-end.js +10 -5
- package/src/mcp/server.js +434 -175
- package/src/mcp/start-ladder.js +44 -0
- package/src/mcp/stop-decide.js +347 -0
- package/src/mcp/tree-decide.js +10 -3
- package/src/mcp/untrusted.js +106 -25
- package/src/milestones.js +26 -3
- package/src/mock-demo.js +1 -1
- package/src/near-json.js +331 -0
- package/src/no-reply-continues.js +32 -0
- package/src/objection-assign.js +93 -0
- package/src/objection-ids.js +63 -4
- package/src/objection-ledger.js +33 -2
- package/src/old-plan.js +3 -3
- package/src/outbound-scan.js +92 -10
- package/src/outcome.js +8 -7
- package/src/overseer-rounds.js +166 -20
- package/src/owner-pause.js +13 -0
- package/src/partial-deliverable.js +2 -2
- package/src/persona-form.js +105 -0
- package/src/persona-record.js +176 -0
- package/src/personas.js +14 -4
- package/src/plan-checks.js +34 -0
- package/src/policy.js +7 -7
- package/src/post-quotes.js +29 -0
- package/src/preflight.js +3 -1
- package/src/price-pin.js +102 -6
- package/src/price-raise.js +3 -3
- package/src/price-refused.js +9 -13
- package/src/price-table.js +53 -0
- package/src/pricing.json +1308 -98
- package/src/prompt-cache.js +57 -0
- package/src/prompts/alternatives.js +109 -0
- package/src/prompts/ambiguity.js +112 -0
- package/src/prompts/challenge.js +44 -0
- package/src/prompts/cold-read.js +34 -0
- package/src/prompts/core.js +281 -0
- package/src/prompts/debate.js +129 -0
- package/src/prompts/dispute.js +114 -0
- package/src/prompts/notes-lane.js +62 -0
- package/src/prompts/proposals.js +136 -0
- package/src/prompts/questions.js +45 -0
- package/src/prompts/recorded-082.js +262 -0
- package/src/prompts/recorded-083.js +470 -0
- package/src/prompts/recorded-084-a.js +220 -0
- package/src/prompts/recorded-084-a5.js +17 -0
- package/src/prompts/recorded-084-b.js +301 -0
- package/src/prompts/recorded-084-c.js +68 -0
- package/src/prompts/recorded-084-cache-order.js +49 -0
- package/src/prompts/recorded-084-d27.js +14 -0
- package/src/prompts/recorded-084-fixline.js +11 -0
- package/src/prompts/recorded-084-ledger-out.js +28 -0
- package/src/prompts/recorded-084-s14.js +33 -0
- package/src/prompts/recorded-084-s16.js +37 -0
- package/src/prompts/recorded-084-s8.js +26 -0
- package/src/prompts/recorded-084-s9.js +119 -0
- package/src/prompts/recorded-084-summary.js +15 -0
- package/src/prompts/scope.js +252 -0
- package/src/prompts/tiered.js +161 -0
- package/src/proposer-return.js +154 -0
- package/src/provider-preference.js +85 -0
- package/src/provider-reported.js +74 -0
- package/src/providers.js +617 -53
- package/src/quote-check.js +56 -10
- package/src/raise-flags.js +42 -0
- package/src/ratings-demand.js +9 -0
- package/src/reasoning-table.json +114 -3
- package/src/reasoning.js +17 -23
- package/src/repair-reply.js +94 -0
- package/src/replay.js +3 -2
- package/src/report-shape.js +180 -45
- package/src/resolve-run.js +87 -0
- package/src/resume-brief.js +13 -4
- package/src/resume-ceiling.js +7 -3
- package/src/retag-pick.js +40 -0
- package/src/return-path.js +53 -0
- package/src/review-notes-lane.js +2 -2
- package/src/roles.js +36 -1975
- package/src/route-layer.js +29 -0
- package/src/rulings.js +44 -0
- package/src/run-budget.js +16 -5
- package/src/run-contract-check.js +2 -0
- package/src/run-controls.js +378 -0
- package/src/run-files.js +7 -3
- package/src/run-preflight.js +12 -9
- package/src/run-state.js +1 -1
- package/src/run-status.js +141 -40
- package/src/scope-ledger.js +109 -0
- package/src/seat-key.js +53 -10
- package/src/seat-role.js +13 -9
- package/src/secret-ending.js +32 -0
- package/src/secret-pause.js +155 -0
- package/src/send-path.js +19 -396
- package/src/severity.js +322 -0
- package/src/short-exchange.js +10 -0
- package/src/sorting.js +229 -0
- package/src/spans.js +1 -1
- package/src/spend-finish.js +126 -0
- package/src/spend-reservations.js +42 -8
- package/src/spend.js +48 -14
- package/src/stage-cache.js +99 -12
- package/src/stage-contract.js +59 -15
- package/src/stage-instructions.js +51 -0
- package/src/stage-submission.js +90 -6
- package/src/stage-system.js +305 -21
- package/src/stale-guard.js +112 -0
- package/src/stale-stages.js +59 -0
- package/src/stall-book.js +89 -0
- package/src/start-cap.js +74 -30
- package/src/start-scope.js +13 -7
- package/src/stop-decision.js +172 -0
- package/src/stop-files.js +17 -45
- package/src/stop-gate.js +66 -0
- package/src/summary-add.js +92 -0
- package/src/summary-block.js +31 -0
- package/src/superseded.js +49 -7
- package/src/text-labels.js +20 -7
- package/src/text-requests.js +65 -0
- package/src/thin-contract-check.js +35 -3
- package/src/tools.js +3 -0
- package/src/top-layer.js +24 -0
- package/src/tree-abandon.js +1 -4
- package/src/tree-accept.js +5 -2
- package/src/tree-budget.js +25 -41
- package/src/tree-choice.js +9 -6
- package/src/tree-cli.js +9 -5
- package/src/tree-close.js +7 -3
- package/src/tree-decide-stop.js +10 -14
- package/src/tree-decide.js +19 -29
- package/src/tree-deps.js +1 -1
- package/src/tree-expansion.js +2 -1
- package/src/tree-gate.js +0 -3
- package/src/tree-member.js +4 -3
- package/src/tree-projection.js +5 -1
- package/src/tree-ready.js +3 -2
- package/src/tree-record.js +2 -2
- package/src/tree-recovery.js +1 -5
- package/src/tree-redirect.js +10 -1
- package/src/tree-report.js +2 -2
- package/src/tree-restore.js +2 -1
- package/src/tree-resume.js +20 -12
- package/src/tree-run.js +18 -8
- package/src/tree-split.js +2 -1
- package/src/tree-start.js +46 -19
- package/src/tree-transitions.js +8 -74
- package/src/tree-ways-on.js +8 -0
- package/src/ui/parse.js +7 -3
- package/src/ui/server.js +6 -4
- package/src/uncovered.js +72 -0
- package/src/unreplayed.js +49 -0
- package/src/verdict-diff.js +2 -2
- package/src/verdict-envelope.js +2 -1
- package/src/verdict-headline.js +157 -2
- package/src/verdict-stats.js +10 -8
- package/src/verify-first.js +96 -0
- package/src/version.js +48 -5
- package/src/writer-note.js +30 -0
- package/src/zdr-hosts.json +5855 -0
- package/src/zdr-refresh.js +34 -0
- package/src/zdr-table.js +85 -0
- package/src/zero-retention.js +146 -0
- package/types/index.d.ts +17 -5
- package/chains/advise-single-astra.json +0 -43
- package/chains/advise-single-deepseek.json +0 -43
- package/chains/advise-single-gemini.json +0 -43
- package/chains/advise-single-glm-flash.json +0 -40
- package/chains/advise-single-opus.json +0 -43
- package/chains/advise-single-qwen.json +0 -38
- package/chains/advise-single.json +0 -43
- package/chains/advise-standard.json +0 -85
- package/chains/daily7-sub-a.json +0 -161
- package/chains/mock-advise-deep.json +0 -51
- package/chains/mock-advise-quick.json +0 -36
- package/chains/mock-advise-single.json +0 -26
- package/chains/mock-advise-standard.json +0 -46
- package/chains/plan-china-mini-council.json +0 -181
- package/chains/plan-default-opro-council.json +0 -250
- package/chains/plan-mini-council-sol-session.json +0 -186
- package/chains/verify.json +0 -61
- package/src/advice-brief.js +0 -130
- package/src/advice-guards.js +0 -303
- package/src/advice-mask.js +0 -89
- package/src/advice-retention.json +0 -308
- package/src/advice-run.js +0 -187
- package/src/advice-send-context.js +0 -31
- package/src/advice-stop.js +0 -37
- package/src/advice-tier.js +0 -140
- package/src/advise-rate.js +0 -53
- package/src/advise.js +0 -449
- package/src/budget.js +0 -134
- package/src/buys.js +0 -52
- package/src/mcp/advice.js +0 -327
- package/src/send-path-refusals.js +0 -156
- package/src/send-profiles.js +0 -103
- package/src/tree-advice-parse.js +0 -54
- package/src/tree-advice-record.js +0 -178
- package/src/tree-advice-route.js +0 -189
- package/src/tree-advice.js +0 -87
package/.env.example
CHANGED
|
@@ -12,12 +12,12 @@ OPENROUTER_API_KEY=
|
|
|
12
12
|
ZAI_API_KEY=
|
|
13
13
|
|
|
14
14
|
# Default per-run spend ceiling in USD. Overridden per run by --max-usd.
|
|
15
|
-
# A run stops cleanly before any
|
|
16
|
-
# resumes with a higher ceiling. Unset means $
|
|
15
|
+
# A run stops cleanly before any call that, by THC's estimate, would pass its limit, and
|
|
16
|
+
# resumes with a higher ceiling. Unset means $12 for a chain with no paid seat;
|
|
17
17
|
# a paid chain asks you to type its limit (this value is only the suggestion).
|
|
18
18
|
# --max-usd none works for a chain with no paid seat only.
|
|
19
|
-
MAX_USD_PER_RUN=
|
|
19
|
+
MAX_USD_PER_RUN=12
|
|
20
20
|
|
|
21
21
|
# Optional settings (README "Setup" lists them all): COUNCIL_MAX_USD_LIMIT (a ceiling: the MCP server's tools cannot lift it and the limit you type for a paid run, or the total you type for a paid tree (`council tree start --max-usd`, `council tree decide --amend-cap`), cannot go above it), OLLAMA_API_KEY, AUDIT_HMAC_KEY_FILE.
|
|
22
|
-
#
|
|
23
|
-
#
|
|
22
|
+
# Seven settings are NOT read from a .env (the CLI prints a notice and ignores them there): set them in the real environment:
|
|
23
|
+
# COUNCIL_WORKDIR, COUNCIL_PERSONAS_FILE, COUNCIL_PII_GATE, COUNCIL_SPEND_CAPS, COUNCIL_CLAUDE_ROUTE, COUNCIL_ZERO_RETENTION and COUNCIL_ALLOW_LOOPBACK_KEY_HOST=1 (for a local proxy in front of a keyed provider).
|
package/KNOWN-LIMITS.md
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# The High Council: known limits
|
|
2
|
+
|
|
3
|
+
## Known limits, stated plainly
|
|
4
|
+
|
|
5
|
+
- **The panel signs one draft; later stages can change what you receive, and nobody re-reads it.** The dispute pass, the post-sign-off challenge and the final edit are model edits made after
|
|
6
|
+
the last review, and the dissent and cold-read blocks are written by the harness. The run labels this and does not repair it: `report.json` has `signed_text` and `delivered_text`
|
|
7
|
+
(hashes, `same_as_signed`, `changed_by`), `BOARD.md` says so when it applies, and `HANDOFF.md` opens with a banner when a model edit changed the text after the panel signed it (a run the panel did not sign keeps its own "not a signed-off plan" banner). A cold-read or dissent block the harness adds is named in `delivered_text.harness_notes` and gets no banner. The panel is not run
|
|
8
|
+
again on the changed text (that would be a further paid pass per run), and `run_status` says so after "every lab signed off" when the delivered text differs (`delivered_text.same_as_signed` in `report.json`).
|
|
9
|
+
- **An early finish needs a reviser that is your own session.** On a layered chain the reviser may end its reply with `EARLY-FINISH: <why>`, and the top layer then decides whether the run stops there; that
|
|
10
|
+
works only when the reviser seat is your Claude Code session (`external`). A chain whose reviser is an API seat has no early finish: the line is removed from its text and the log says so. An early finish is not
|
|
11
|
+
agreement: `passed` is false, the run exits 22, and every open objection is a named builder task at the end of `HANDOFF.md`.
|
|
12
|
+
- **A run paused before this version can re-run some stages when it resumes (a run paused on 0.8.3 or earlier, resumed on 0.8.4).** From 0.8.4 on, a stage is kept as saved when THC's wording changed between the pause and the resume. A stage saved by an earlier version carries no record of the version that wrote it, so where its saved prompt differs from the one it is asked now it is run again: the run log says `CACHE STALENESS WARNING`, and the new call is paid. To avoid it, finish such a run with the version that paused it. Even from 0.8.4 on, a stage whose prompt differs because a stage before it was asked again, was rewritten in a later sitting or was edited by hand is asked again and paid.
|
|
13
|
+
- **A resume after the chain or the task changed asks again every finished step it affects that the run reaches, and a paid chain pays for it again.** A saved stage is kept only when it was made under the chain file and the task the run has now; a stage made under others is asked again when the run reaches it. On a paid chain the limit screen says so before you type a limit: the screen counts the finished steps made under the earlier chain or task, says that each one the run reaches again is asked again, and that the ones that bill are paid again and what they cost before still counts toward the limit (it is a count of what was made earlier, not a promise about the path the run takes). On a chain with no limit screen (free seats or sessions of yours, or a tree member) one line in the run log says how many finished steps the change affects, with the money wording on a paid chain, and each seat that is a session of yours is asked again. A chain file that an update changed is such a change for a run paused before the update. A chain that an update retired is not found by a resume ("No such chain"): copy its file from the repository's chains/outdated/ folder on GitHub (the npm package does not carry that folder) into your project's chains/ folder, and its saved steps are kept when that copy matches the file the run started with. To avoid all of it, finish the run with the version that paused it, or start a new run.
|
|
14
|
+
- **A resume after an update keeps saved replies, with two exceptions.** A stage whose reply was repaired after it was saved, and, on a case-insensitive file system (macOS, Windows), the handoff file once it holds its lock block, no longer match their record, so they are asked again after an update and the new call is paid. And a stage is judged by the version and code stamp of the THC that wrote it: two builds that share one version string and carry no code stamp (a packaged binary) are not told apart.
|
|
15
|
+
- **A stall below the top layer passes the plan up a layer, and parts of that path have run only with mock seats.** On a layered chain with overseer rounds (your own chain; a ladder chain, which is every shipped planning chain from 0.8.4 on, passes nothing up ahead of its turn) the judges of the next layer up rule on the stuck objections ([AI-README](AI-README.md), "When a layered panel stalls"). These parts of the path have run only with mock seats, so with a real model they are unverified: the ruling, the writer's prompt after a ruling, a sign-off after the plan was passed up, a run that stops after the plan was passed up, and the stop for a stall that cannot be passed up (the top layer holds it; no pass is left; the chain keeps no answer-back list or does not ask its judges to rate; or the next pass is a ladder pass, which does not pass a plan up), which waits for your choice. The plan going up to layer 3 when layer 2 has used its revisions is the same path and has run only with mock seats too. An objection raised again without a quote is known by its judge and criterion alone, so it cannot be told from a new problem under the same criterion and counts as a live objection again; an objection that was passed up once is not passed up again in the same overseer round (a chain without overseer rounds counts the whole run as one round); one raised again with a quote that a ruling closed is recorded and not counted. The ruling prompt does not say who raised an objection, but with `answer_back` the top layer sees every judge's lab in its objection ledger.
|
|
16
|
+
- **A persona is a label, not a mask.** All seven shipped planning chains switch `seat_personas` on: `plan-the-high-council` in the form `voice`, the other six in the form `title`. With the chain switch on, each judge or proposer that carries a `persona` has a short block naming it in its own prompts: under the form `title` the block says the name is the title of the seat and changes nothing the seat does; under `voice` (experimental; of the shipped chains only `plan-the-high-council` uses it) every named seat, judge or proposer, speaks in the voice of the name. Nothing stops a judge from writing a persona name in its replies, its own or another seat's, and a voice style can identify a judge across rounds. In the exchange of a layer the other judges read objections with the raiser hidden, and with `answer_back` the top layer sees every judge's lab in its objection ledger. The run counts every persona name found in judge-written text in `report.json` (`seat_personas.names_in_text`) and removes none of them; a name in a proposer's own proposals, posts and replies is not scanned. The same rule holds for every name, alive or not: no setting gives one name less than another.
|
|
17
|
+
- **Where the judges rate their objections, a plan can be signed off while objections rated below 4 stay open.** A judge's objection rated below 4 does not hold a sign-off: it is recorded in `report.json` (`severity.swept`; one that was once rated 7 or above and has been re-tagged below 4 is listed in `severity.open_at_end` instead, because it stays open until it is fixed or withdrawn), it still enters the exchange, it does not go to the dispute stage and is not extracted as a claim, and the writer still reads it whenever another objection sends the plan back. The ratings are the judges' own and have not been checked against real model replies, so a judge that rates a real defect low lets it through. The first valid rating a judge gives an objection stands: a later, different rating by the same judge is recorded as an override and does not count. Only the top layer or the person's handler can change the rating in force, and a change across 4 in either direction changes whether the objection holds a sign-off. An objection a judge gives no rating holds a sign-off.
|
|
18
|
+
- **The zero-retention setting asks for zero-retention hosts only, and a request cannot rule out one kind of host by itself (0.8.4).** With `--zero-retention on` (or `COUNCIL_ZERO_RETENTION=on`) every call through OpenRouter names only the endpoints that OpenRouter's own list (https://openrouter.ai/api/v1/endpoints/zdr, saved on the date in `src/zdr-hosts.json`) gives for its model as zero-retention hosts, and uses no provider that the list marks anywhere as keeping prompts for abuse monitoring; a seat with no such host is named and the start is refused before anything is sent. OpenRouter's documentation (https://openrouter.ai/docs/guides/features/zdr, read 10 Nov 2026) says a request's `zdr` follows the account's own choice about those endpoints, so a request cannot exclude them on its own: an endpoint of that kind added under a provider name the list allows would match that name until the list is refreshed. The account setting "zero-data-retention only" in OpenRouter's privacy settings rules that out. The setting covers the seats called through OpenRouter only; a seat called straight to its lab is under that lab's own terms. The list is read against its date: more than 45 days after it, a start with the setting on is refused until the list is refreshed with `council refresh-zdr-list`, which fetches OpenRouter's list again (a public request; nothing of your project is sent) and keeps only what this version shipped with, so the refreshed file can narrow the list and never widen it: a host OpenRouter added after this version was released is not used, and a model whose hosts moved is refused, until a newer version ships its own list. The start names the date of the list it used.
|
|
19
|
+
- **The start and end lines around the plan are text, and a plan can contain them.** In a chain with `draft_end_marker` on (every shipped chain that reviews a plan has it), and in any chain with prompt caching on, whatever `draft_end_marker` says, a judge's prompt carries a start line before the plan and an end line after it, to mark where the plan is and where the harness's own instructions begin. The plan is shown as written, so a plan that contains the sentence of either line can show a judge a false boundary. The prompt cache takes its breakpoint from the prompt builder, not from the lines.
|
|
20
|
+
- **A call that writes a cache entry costs more than the same call without a cache mark, and the entry may never be read (0.8.4).** The marked part of the prompt is billed at the cache-write price, 1.25 times the input price for the 5-minute lifetime and 2 times for the 1-hour lifetime; a later call that reads the entry before it expires is billed less for that part than the plain price. Two of the cases that pay the write and get no read: a pass in which no judge of a layer raises an objection (the exchange does not run, so each judge's verdict call writes the entry and no exchange call reads it), and a later call that starts after the entry's lifetime has passed (it finds no entry and is billed a write again). For Claude called directly, the lifetime is counted from the start of a call, not from the end of its answer, so a slow answer uses up part of it.
|
|
21
|
+
- **A cache write can be counted twice in a call's cost (0.8.4).** When a reply says it wrote part of the prompt to a cache and counts those tokens inside the size of the prompt, and THC sent no cache mark with the call, THC prices those tokens at the input price and again at the write price, and a long prompt can be priced at a higher price step. The cost THC counts against your limit is then higher than the bill, never lower. Calls THC marks for caching are counted once. No shipped chain is known to meet this; a seat of your own chain that sets a caching option in its `extra` can.
|
|
22
|
+
- **A note after the run is added from a terminal.** The handling session adds a disagreement to the build list after a run has ended with `council note-add`; there is no MCP tool for it, so a handler that has no terminal cannot add one. The note is its own text, up to 600 characters, and it sorts nothing: the objections of the run stay as they were. `BOARD.md` is not re-rendered by a note. On a case-insensitive file system (macOS, Windows) the handoff stage file and HANDOFF.md are one file, so a note adds to the mismatch described above between the handoff file and its record.
|
|
23
|
+
- **A summary of the plan is added from a terminal.** The handling session adds a summary and index of the finished plan at the top of deliverable.md with `council summary-add <run folder> --summary-file <file>` (run it without the file to see what to write); there is no MCP tool for it, so a handler that has no terminal cannot add one. It needs a handling session, it can be added once, and the run's closing summary names it. The block says how the run ended in the words THC uses elsewhere; the rest of the file is as the run wrote it.
|
|
24
|
+
- **A ruling is given from a terminal (0.8.4).** `council ruling-add <run folder> --ruling "<text>"` asks for a ruling that the judges and the reviser read from the next pass on, and `council ruling-withdraw <run folder> <gate>` takes an approved one out of force from the next pass; there is no MCP tool for either, so a handler session without a terminal cannot give or withdraw one. Each waits at an approval for a day, and the run does not go on until it is answered or has expired: the person answers with `council gate answer`, or, for a request the handler session made itself (`--relayed`), the handler session relays the answer with `council relay-answer` and the person's own words, which are accepted for a ruling only if its text is in them. `AI-README.md` has the details.
|
|
25
|
+
- **A plan the panel did not agree on is accepted, or not, by a person or the handling session (0.8.4).** After a run that ended without agreement, `council accept <run folder> --reason "<why>"` (a person at a terminal) or step `accept` of the `stop_decide` tool (the handling session) records that the plan is accepted as it stands: who, when, why and which open objections stand. It agrees with no judge and changes no objection; the handoff keeps its banner and gets one line under it and its Status back. The session may accept on its own authority only when no stop of the run was the person's and the text is the text the panel last reviewed; otherwise the person accepts, at a terminal or by telling the handling session, which relays the person's own words (`person_words`) and is recorded as the person's, relayed. `AI-README.md` has the details.
|
|
26
|
+
- The spend cap is checked against an **expected cost**, not a worst case. Before each call THC prices the whole prompt as
|
|
27
|
+
input plus the output that kind of call usually writes (the sizes the dry run uses, never above the seat's `maxTokens`), adds
|
|
28
|
+
what it has counted and the expected cost of the calls in flight, and refuses the call if the total passes your limit. A re-ask,
|
|
29
|
+
a retry of a cut-off reply and an Anthropic seat's retry after thinking used its whole budget are checked at the expected cost of
|
|
30
|
+
the call they repeat. A call with no expected size (a merge, a full revise, the conflict decider, the allocator) and an answer-back call
|
|
31
|
+
(`-answered`, `-question`, which is not a re-ask) are checked at their worst case: the whole prompt as input plus the seat's entire
|
|
32
|
+
`maxTokens` as output. **A run can therefore end above your limit when its last calls write more than expected.** A
|
|
33
|
+
call checked at an expected size can write more than that, up to its whole `maxTokens`. A re-ask or a retry that costs
|
|
34
|
+
more than its expected cost is one case of this. A long call that passes a price step its estimate did not reach is
|
|
35
|
+
another: the check counts a call at the price step its estimated size reaches (the price-step limit below says what is
|
|
36
|
+
not verified). A call of a layer's exchange (a post or an answer, in a pass or in the exchange that closes a run that
|
|
37
|
+
ended without the top layer's sign-off) is a third: it is checked at the size the dry run prices it at (a seat's review
|
|
38
|
+
size: 5,000 output tokens on the shipped planning chains for most seats) and not at the seat's whole `maxTokens`. In the
|
|
39
|
+
author's bench runs the median post of several cheaper models was larger than that size; no call of the exchange that
|
|
40
|
+
closes a run has been measured. What a call reserves on disk before it is sent is still its worst case. A run that stops at its limit keeps
|
|
41
|
+
what it paid for: resume it, and a paid run asks for a new, higher total on the limit screen (a chain with no paid seat takes `--max-usd`).
|
|
42
|
+
- **A resumed run can count a paid call against your limit differently from the report (0.8.4).** A paid stage file that the resumed run never asks for again (an earlier sitting wrote it, and the new path no longer reaches its stage) is counted in the report and in `council --spend`, not against your limit, so the resumed run can spend that file's cost on top of the limit you typed. A reservation THC wrote before a call and never closed, beside a usage or charge file of the same call, counts against your limit at its worst case plus the file, while the report counts the call once, at the larger of the two: the limit has less room than the report suggests.
|
|
43
|
+
- The dry run (`--dry-run`, MCP `dry_run`) prices the task alone and does not read `--context`
|
|
44
|
+
documents, so for a run with context its estimate is too low. The spend cap is not affected: it
|
|
45
|
+
checks every real prompt, context included, before each call.
|
|
46
|
+
- A seat whose model has no price, neither in `src/pricing.json` nor in your project's `pricing.user.json`, cannot be held by a spend cap, because the cap would count every call to it as $0. Since 0.8.2 a run **refuses** such a
|
|
47
|
+
seat before it sends anything (exit 5; the message names the seat and, since 0.8.3, the way out: add a row for it to `pricing.user.json` in your project folder, for example `{ "openrouter/vendor/model": { "in": 1.0, "out": 4.0 } }`, in US dollars per million tokens; your row can add a missing price or raise a shipped one and can never lower a shipped one, and a file that cannot be read in full is refused as a whole), and
|
|
48
|
+
`--dry-run` shows the same refusal at $0. `mock`, `external` and `ollama` seats are exempt: `ollama` prices at an explicit $0 for any model name, because it is genuinely free to run.
|
|
49
|
+
What remains: the cap is only as good as the price table (it is dated, and the dry run says when it is old), and a model that answers in place of a priced one (an OpenRouter fallback in
|
|
50
|
+
`extra.models`) is charged at the priced seat's price, which can differ from its own.
|
|
51
|
+
- A call that fails **after** it reached the provider - the connection drops while a 200 reply is
|
|
52
|
+
being read, a 200 carries a body that is not JSON, or the request times out waiting for headers -
|
|
53
|
+
was probably already billed, and its real usage cannot be read. A body that is not JSON and a
|
|
54
|
+
request that timed out are not sent again: each is counted toward the per-run cap at its
|
|
55
|
+
worst-case projection, and recorded as `superseded/<label>.charge-<n>.usage.json`, which
|
|
56
|
+
`council --spend` and a resume count. A connection that dropped is sent again, at most three times:
|
|
57
|
+
for a seat that bills, three in a run (every stage and every re-ask of the run share them, and
|
|
58
|
+
they are kept in the run folder, so a resume you start does not give them back); for a seat that
|
|
59
|
+
does not bill (`mock`, `ollama`), which leaves no charge to count, three in a sitting (a resume
|
|
60
|
+
you start has three of its own). Every resume is a line in `run.log`, and a seat that does not
|
|
61
|
+
bill also gets a line in `network-resumes.jsonl`. The failure after the third stops the run
|
|
62
|
+
(exit 16, `STOPPED-error.json` with `causeClass` `transport`), and you can resume it. A stage that
|
|
63
|
+
two earlier attempts were already counted as possibly billed for is not sent again. Each attempt
|
|
64
|
+
of a seat that bills is gated by the cap, reserved before it is sent and counted on its own: at a
|
|
65
|
+
typical cost for the call (its expected cost when the dry run plans it, else a typical size for
|
|
66
|
+
what it writes, never more than one attempt's worst case), so the counted total can be
|
|
67
|
+
lower than the bill. The deep dive and the side commands that make one call
|
|
68
|
+
(`digest`, `handoff --from-run`, contract drafts) do not send a dropped connection again.
|
|
69
|
+
A 5xx answer means the request reached the provider; it is sent again, at most twice for each ask
|
|
70
|
+
(a 5xx on the second ask of a call that retries a cut-off reply starts the call again, so its first
|
|
71
|
+
ask is sent and paid again). The deep dive and the side commands that make one call (`digest`,
|
|
72
|
+
`handoff --from-run`, contract drafts) are not sent again after a 5xx. For a
|
|
73
|
+
seat that bills, every re-send is gated by the cap, reserved before it is sent and recorded in
|
|
74
|
+
`spend-reservations.jsonl`; a seat that does not bill leaves only a line in `run.log`. Such an
|
|
75
|
+
answer carries no usage figure, so for a seat that bills THC counts each 5xx attempt at a typical
|
|
76
|
+
cost for the call (its expected cost when the dry run plans it, else a typical size for what it
|
|
77
|
+
writes, never more than one attempt's worst case), records it as
|
|
78
|
+
`superseded/<label>.charge-<n>.usage.json` and marks it `reachedProvider`. A provider may not bill
|
|
79
|
+
a 5xx at all, so the counted total can be higher or lower than the bill. The dry run's upper
|
|
80
|
+
estimate leaves out these repeated attempts after a provider error, while the counted total
|
|
81
|
+
includes them. Those 5xx attempts
|
|
82
|
+
count for the rule above: when a stage is run again, for example by a resume, one with two of
|
|
83
|
+
them behind it is not sent again after a dropped connection. A 429 and a failure before anything
|
|
84
|
+
was sent are sent again free and unrecorded.
|
|
85
|
+
A 200 whose body is an error and carries no reply is the provider saying what happened, so THC
|
|
86
|
+
counts it toward the cap at the usage figure the body reports, else at a typical cost for that
|
|
87
|
+
call (the expected cost of the call, or of the call it repeats, and for a call the dry run does
|
|
88
|
+
not plan a typical size for what it writes; never more than one attempt's worst case), and
|
|
89
|
+
records it the same way.
|
|
90
|
+
- Some hosted reasoning models stop at a provider-side reasoning ceiling well below the seat's own
|
|
91
|
+
cap, before writing any answer (seen 2026-09-23: `deepseek/deepseek-v4.1-flash` via OpenRouter, 6 of
|
|
92
|
+
28 calls stopped at ~4.2k tokens, all reasoning, under a 36k cap). A bigger cap cannot fix that, so
|
|
93
|
+
such a reply is recorded as `REASONING_EXHAUSTED` and is not retried: the seat abstains, which
|
|
94
|
+
blocks unanimity and never counts as consent. Whether to change a chain's reasoning settings for it
|
|
95
|
+
is a roster decision, not something the harness does for you.
|
|
96
|
+
- **The Claude Code plugin installs from the repository root.** `claude plugin validate .` passes with one warning, and
|
|
97
|
+
`claude plugin validate --strict .` fails on it: "CLAUDE.md at the plugin root is not loaded as project context" (the
|
|
98
|
+
file is this project's own instructions and is not part of the plugin). A separate `plugin/` directory would not have it;
|
|
99
|
+
that layout was declined for 0.8.1 and comes back with the plugin directory listing. What Claude Code passes for an
|
|
100
|
+
unset optional dialog field was seen once, in a scratch plugin on Claude Code 2.1.289 (an empty string), not across
|
|
101
|
+
versions or on Claude Desktop's own client.
|
|
102
|
+
- **The reasoning fields were set from the providers' documents and OpenRouter's public model list,
|
|
103
|
+
not exercised in a live call** (src/reasoning-table.json; its `asOf` field is the date they were read). A provider that rejects a
|
|
104
|
+
field fails that seat's call with the provider's own error; the first real run of a changed chain is
|
|
105
|
+
the live test. The table turns stale after 60 days and a release test fails then. Seats that now
|
|
106
|
+
reason at high use their output cap faster, and a cap sized for a short answer (a questions or criteria
|
|
107
|
+
stage) can be spent on thinking: such a seat is retried once at a bigger cap and, past the limit, is
|
|
108
|
+
recorded with no answer. A direct Anthropic call is streamed (0.8.4), meant to get past Node's 300 s wait for response
|
|
109
|
+
headers. One real call has run on that path (Sonnet 5.5, Node 22.23.4, 24,408 output tokens in 168 s): its
|
|
110
|
+
headers came back within a second, and it ended before 300 s, so no real streamed call has yet been seen to
|
|
111
|
+
run past 300 s. Its retry stays bounded at 64,000 tokens. A silence inside the stream (Claude 5 does not
|
|
112
|
+
stream its thinking unless asked, and Anthropic documents no longest gap between events) is ended by Node's
|
|
113
|
+
own 300 s limit between pieces of data: the longest silence in that one call was 15 s, and nothing longer
|
|
114
|
+
has been measured.
|
|
115
|
+
- Prices in `src/pricing.json` are hand-maintained list prices. Its `asOf` date (2026-10-23: the
|
|
116
|
+
oldest date every entry was last checked) is printed by `council doctor` and `--dry-run`, with a
|
|
117
|
+
warning once it is more than 60 days old. A model whose provider has retired it or no longer
|
|
118
|
+
lists a price keeps the figure the table already had for it, and its row's `_source` says so:
|
|
119
|
+
the date records the read, not that figure. The spend cap and every estimate use these static
|
|
120
|
+
prices. They are estimates, not invoices. Your provider's bill is the real number. A row whose
|
|
121
|
+
price is known to change carries an `expires` date (Gemini 4 Argon, whose price doubles on
|
|
122
|
+
2026-12-01; Gemini 3.8 Flash and Gemini 3.6 Flash, whose prices double on 2027-01-01). Once a row's date has passed, `council doctor` and
|
|
123
|
+
`--dry-run` warn, but until the table is updated the projection and the cap keep the old price:
|
|
124
|
+
a call straight to the lab can then cost more than they project, while a call through
|
|
125
|
+
OpenRouter stops the run instead of overpaying.
|
|
126
|
+
- The retired `plan-highest-7` (in `chains/outdated/`) was never run with real models, and `plan-daily-7` ran once (2026-09-28) with the roster it had before the 7 Oct 2026 redesign. None of the tiered councils' mechanisms (tiers, the deep-dive seat, the majority
|
|
127
|
+
guard) has been measured. Their dry-run prices are projections.
|
|
128
|
+
- **`run_tests` runs a repository's own test files as trusted code, with no sandbox.** It belongs to the opt-in seat tools (`verify.tools` naming `run_tests`; no shipped chain does; a seat
|
|
129
|
+
may request it mid-stage only when the chain also sets `tools.seat_requests`). Since 0.8.2 the test process gets an allowlisted environment: `PATH`, `HOME` (`USERPROFILE`), the temporary-folder
|
|
130
|
+
variables, `LANG`, `LANGUAGE`, `LC_*`, `TERM`, `CI` and the Windows system variables (`SystemRoot`, `ComSpec`, `PATHEXT`, `WINDIR`). No provider key is in its environment, but a test is code you trust: it can still read the parent process's environment and files on disk (for example `/proc/<pid>/environ`). A project's tests that need another
|
|
131
|
+
variable (a database URL, say) will not see it. It can still read every file you can read, write files and use the network, so use it only on a repository you trust.
|
|
132
|
+
- **The symbolic-link containment of the MCP tools is untested on Windows.** Since 0.8.2 the tools refuse a run folder that holds a symbolic link and open run-folder files with `O_NOFOLLOW`. That flag does not exist on Windows, so there the
|
|
133
|
+
protection rests on the `lstat` checks alone, and it has been tested on Linux only: not on a real Windows machine, and the packaged `.exe`'s smoke test under Wine does not exercise it.
|
|
134
|
+
- Seats marked `external` (the decider and the writer seats of every planning chain, and the seats answered through your Claude subscription) pause the run until someone answers them, and are $0 only because a Claude Code
|
|
135
|
+
session on a subscription, or you on your own plan, writes them. Nothing checks who wrote the answer.
|
|
136
|
+
- Chains with many labs and high round caps get expensive quickly: every round asks every critic seat. Price
|
|
137
|
+
before you run.
|
|
138
|
+
- A panel that signs off is not a guarantee the output is correct. It means every critic seat
|
|
139
|
+
checked it against the stated acceptance criteria and found nothing. Criteria that are vague
|
|
140
|
+
produce sign-offs that mean nothing - the quality of the run depends heavily on the quality of
|
|
141
|
+
the criteria stage.
|
|
142
|
+
- Some chains pause for human input by design. They are not stuck.
|
|
143
|
+
- The audit log (`audit.jsonl`) detects an edited line or a cut-off end, but **with no HMAC key,
|
|
144
|
+
which is the default, it does not detect a full rewrite**: anyone who can edit the file can
|
|
145
|
+
recompute every hash. Set `AUDIT_HMAC_KEY_FILE` to a key kept outside the run folder if the log
|
|
146
|
+
has to stand up to that.
|
|
147
|
+
- The run folder of any run holds the absolute path of your working folder (`run.json`, `run.log`, `RESUME.md`): committing a run folder publishes it. `write_task` and `start_run` send text to the labs with no preview, only the key scan, the personal-data check (it stops the run at a finding unless you set `COUNCIL_PII_GATE` to `warn`) and the spend cap.
|
|
148
|
+
- The outbound key scan matches **key shapes only**, the formats in `src/secret-patterns.js`; a
|
|
149
|
+
password written in prose is not detectable. Generic shapes (a password inside a URL or an
|
|
150
|
+
assignment) are checked in your inputs before the run; text the models write is checked only for
|
|
151
|
+
distinctive key formats.
|
|
152
|
+
- **The contract record (`council contract`, `contract_read`, `contract_amend`) was checked only against mock providers and a pretend terminal.** Its drafting call was never made with a real model
|
|
153
|
+
(its wording, recorded in 0.8.2, has only been read by a mock seat). The person's answer is friction, not enforcement: a shell-capable agent can open a pseudo-terminal, as
|
|
154
|
+
with `council gate answer`. A contract is an agreement between a person and a build session's instructions; it does not make a build session follow it, and nothing here measures whether it helps.
|
|
155
|
+
- **The approval gate is friction, not enforcement.** A contract is approved at a terminal, a typed spending limit at a terminal or in the client's dialog, and the terminal answer can be typed by anything that runs as you. The gate's ledger is
|
|
156
|
+
tamper-evident, not tamper-proof: an edit before its last line is detected; the last line, the tail and a rewrite of both the ledger and its view are not.
|
|
157
|
+
- **A planning run can be asked to pause from outside, but only at the next pass boundary.** `council pause <run folder>` asks a running run to pause after the pass it is in; the run stops before the next pass is started, written or paid for. It refuses a run nobody is running and a tree member.
|
|
158
|
+
- GPT-6 Astra is reachable only through OpenRouter: there
|
|
159
|
+
is no subscription route.
|
|
160
|
+
- **A review cut-off in the security-review seat:** `src/security-review.js` cuts reviewer fields at 2,000 characters with a visible marker and no record.
|
|
161
|
+
- **Untested on case-insensitive file systems (macOS, Windows).** The fix for `handoff.md` and `HANDOFF.md` being one file (FX-9: `handoff --from-run` now also writes with an exclusive create, so it cannot replace an existing file whatever the disk's case rules) and the working-folder path checks were tested on Linux only. No pass on a Mac or a Windows machine was run for 0.8.1 or 0.8.2.
|
|
162
|
+
- **The dry run's expected figure does not price every cap.** A planning chain's expected figure prices a critic at the chain's own estimate, not at its output cap, so the larger caps of
|
|
163
|
+
0.8.1 and 0.8.3 (now 393,216 tokens for DeepSeek V4.1 Flash) appear in the dry run's upper estimate, not in the expected figure; the spend check prices a call at its expected size, so only a call with no expected size (a merge, a full revise, the conflict decider, the allocator)
|
|
164
|
+
is checked at its worst case with those larger caps, and such a call can stop a run before the expected total. The speed of DeepSeek V4.1 Flash at its 393,216-token cap is not measured, so whether a reply that long fits inside the 2-hour request deadline is not known; a call that deadline cuts is a paid timeout.
|
|
165
|
+
- **Some providers may refuse two shipped output caps.** GLM-5.3 Flash is set to the lab's 131,072 output tokens, which all but one of its zero-data-retention providers accept (24 of 25, list read 2026-10-23). DeepSeek V4.1 Flash is set to 393,216; 7 of its 9 zero-data-retention providers accept a cap of 360,000 (list read 2026-10-23), and the list was not read again at 393,216. Both counts come from OpenRouter's provider lists, not from a call, and the planning chains that seat these two models do not limit them to zero-data-retention providers, so the providers outside these counts were not checked. Whether OpenRouter routes around a provider that does not accept a cap, or refuses the call, is not verified.
|
|
166
|
+
- **One typed limit can start two runs if its run folder is copied before the run starts:** the limit is bound to the run id, the task and the settings, not to the folder, so a copy kept under the same run id (for example in another project's `runs/`) holds the same unused approval.
|
|
167
|
+
- **The retry of a direct Anthropic seat whose thinking used the whole cap may never fire.** It keys on a thinking-token count in the reply's `usage`
|
|
168
|
+
(`output_tokens_details.thinking_tokens`) that Anthropic's public Messages API reference does not list (thinking counts inside `output_tokens`); OpenRouter reports
|
|
169
|
+
`reasoning_tokens`, so it fires for an Anthropic seat sent through a single-vendor route (`"transport": "openrouter"`). Where the count is absent, a reply cut off at its cap is retried by the panel's ordinary cut-off rule (one bigger call),
|
|
170
|
+
not by this one. Not verified against the live API.
|
|
171
|
+
- **Some fields an agent reads about a run are not marked as untrusted text.** Since 0.8.4 the text of a refusal or an error is marked as a whole, except where the server composed the sentence itself and knows which value it read from a file or folder of the project: there only that value is marked and the server's own words are not. Still not marked, and not checked back to their source: a key of an answer that no tool lists as text (a new text key is listed by hand), the warnings a start returns about `policy.json`, the command and the sentences of a tree member's answer, `integrity_warning`, and an error the server throws instead of returning. Text there could carry an instruction. A stage bundle for a subagent says its referenced files are data.
|
|
172
|
+
- **A process killed in the middle of a paid call leaves that call charged at its worst case, not at what it cost.** Since 0.8.2 a call's worst case is written to
|
|
173
|
+
`runs/<id>/spend-reservations.jsonl` before the request goes out, and a settle line follows when the call returns. If the process dies in between (there are no signal handlers), the provider may bill
|
|
174
|
+
the call and no usage file exists, so the unsettled reservation stays counted at its worst case in a resume's cap, in `council --spend` and in the spend report. No command clears one: the figure errs high,
|
|
175
|
+
and goes when the run folder goes. Whoever can write the run folder can edit the file. A brief of non-ASCII text (CJK, say) is projected low in a planning chain (four characters a token).
|
|
176
|
+
- **A run paused under 0.8.1 on a chain or a prompt that 0.8.2 changed is not replayed when resumed:** the config fingerprint or the prompt hash differs (0.8.2 changed seven planning chains and the criteria and judge prompts of every chain), so its API stages are
|
|
177
|
+
paid for again and the answers already given to its external stages are set aside and asked again (the same holds for a run paused under 0.8.0 on a chain 0.8.1 changed). Finish such a run on the version that paused it, or start it again.
|
|
178
|
+
- **`npm test` turns red on dates, by design:** 2026-12-01 (the `gemini-4-argon` price row), 2026-12-23
|
|
179
|
+
(`src/reasoning-table.json`, older than 60 days; regenerated 2026-10-23), 2026-12-26 (`src/zdr-hosts.json`, the list of zero-retention hosts, more than 45 days after it was read from OpenRouter's list on 2026-11-10), 2026-12-27
|
|
180
|
+
(the OpenRouter price rows of `src/pricing.json`, more than 45 days after `scripts/refresh-openrouter-prices.mjs` last compared them with OpenRouter's list on 2026-11-11) and 2027-01-01 (the `gemini-3.8-flash` and `gemini-3.6-flash` price rows). Re-read the pages, re-date the file, then the suite is green again. The price rows of the direct providers
|
|
181
|
+
(Anthropic, OpenAI, Google, ...) carry their own dates in `_source` and are not covered by the 45-day tests.
|
|
182
|
+
- **DeepSeek V4 Flash on Together** is sent `chat_template_kwargs {thinking: true, reasoning_effort: "high"}` from vLLM's serving recipe; Together's own page for it was
|
|
183
|
+
not found, so the on-value is unconfirmed on Together.
|
|
184
|
+
|
|
185
|
+
- **The review notes lane (0.8.3) is unmeasured and has costs the dry run does not show.** With `reviewNotes` on, each judge may add up to 3 proposals and 3 improvement ideas per overseer round in the same JSON reply as its verdict. A reply cut off inside the notes loses a sign-off (a cut-off sign-off counts as an abstention) but keeps an objection that parsed, because the notes ride in the same reply; a seat whose reply was cut off after the notes ask is not asked for notes again in that overseer round. From overseer round 2 on, every judge call also reads a memory block of the last round's items and the writer's answers: paid input that neither dry-run figure prices (up to 10000 tokens per call; in a worst-case run about $6.59 for `plan-mini-council`, $10.02 for `plan-medium-council`, $30.90 for `plan-high-council`, $5.27 for `plan-china-medium-council`, $30.90 for `plan-the-high-council`, $7.06 for `plan-china-high-council` and $22.08 for `plan-claude-council`, at the table prices of 2026-11-11); the limit you type is what stops spending. The dry run's expected figure prices the writer's answers for one overseer round's items and the upper estimate prices every planned round; a handoff that answers several rounds' unanswered items reads more than the expected figure counts. On a chain whose handoff seat is not the plan's writer, the lane adds one more stage, `notes-answer`; on the seven planning chains the writer's answers ride in the handoff. Only exact-id duplicates are merged (an id is computed over the whitespace-normalised text), and "taken" is the writer's claim, which nothing checks.
|
|
186
|
+
- **The price limit on OpenRouter calls rests on one documented filter and one probe.** OpenRouter's documentation calls `provider.max_price` a filter but does not say what happens when no host is at or under it. One real call (18 October 2026, a limit far under the only host of its model) was refused with HTTP 404 "No endpoints found that satisfy the max price for this request" and cost nothing. That wording is the only one this program recognises: any other 404 on a call that carried the limit also stops the run (exit 24) but recommends nothing. A limit exactly equal to a host's price and a model with more than one host have not been run; if OpenRouter refuses a call there, the run stops at exit 24 and asks you. If OpenRouter's list price rises above a row in `src/pricing.json`, the run stops the same way instead of overpaying: add the model's current price as a row in `pricing.user.json` (an installed copy cannot refresh its price table; update THC, or in a clone run `node scripts/refresh-openrouter-prices.mjs --fetch <a folder outside the repository> --write`, then `node scripts/regen-derived-figures.mjs`), or skip the seat with `--skip-seat` where its stage can do without it.
|
|
187
|
+
- **A refusal caused by the Open Inference block has not been seen (0.8.4).** Every OpenRouter call asks OpenRouter to skip the Open Inference host, and that cannot be switched off. That OpenRouter keeps to it on a real call has not been seen: the host is named by its slug, `open-inference`, as OpenRouter's documentation asks, and no paid call has checked it. If that leaves a model with no host, OpenRouter's wording of the refusal is unknown (the one refusal this program has seen was about the price), so the run stops as it does for any other unrecognised 404: exit 24, recorded as `price_refused`, with no recommendation, and the words name the block as one possible cause. The stop is not named after the block.
|
|
188
|
+
- **Some models cost more for very long prompts, and the dry run's expected figure does not show that.** A few hosts raise their price above a prompt size such as 100,000 or 272,000 tokens. The price table records those steps in a row's `tiers`. The spend cap, the price limit sent with a call and the dry run's upper estimate each count a call at the price step its own prompt reaches; the dry run's expected figure still prices every row at its base figure, so for a very long prompt it reads low. OpenRouter's routing page (read 2026-11-01) calls `max_price` a filter ("route to any provider with a price of <= $1/m prompt tokens, and <= $2/m completion tokens or less") and does not say whether it compares a host's base price or its step price, so that is not verified.
|
|
189
|
+
- **A host preference rests on documented ordering and one undocumented interaction (0.8.4).** `provider-preference.json` in your project folder asks OpenRouter to try the hosts you name first for a model (`provider.order`, full endpoint tags such as `azure/us` or `wafer/mxfp4`) and leaves `allow_fallbacks` on, so any other host can still serve a call that none of yours can. OpenRouter's routing page (read 2026-11-01) says the router "will prioritize providers in this list, and in this order" and "will try them one at a time and proceed to other providers if none are operational"; it does not say how `order` combines with `provider.max_price`, the price filter every call carries. THC assumes the filter applies first and your order among the hosts that pass it; that has not been probed. The file changes only the order hosts are tried in, never a price: the spend cap counts the same figures with or without it. A base endpoint is named by its bare tag (`azure`, `openai`), and a bare tag also matches that provider's regional and variant endpoints (`azure/us`, `azure/eu`) unless you ignore them with `extra.provider.ignore`, which takes full tags too; it does not match a service-tier endpoint such as `openai/flex`. The proposal script prints, for every bare tag, the endpoints it would also match.
|
|
190
|
+
- **A relayed choice at a stop, or a relayed acceptance of a plan, is the handling session carrying the person's own words (0.8.4).** `stop_decide` over MCP with the choice `continue`, `finish` or `finish_now` and `person_words` answers a stopped run at any stop on the person's behalf, and step `accept` with `person_words` accepts a plan the panel did not agree on. `finish_now` ends the run at once, with no judge asked again (a final read already on disk for this text is kept and counts as read): inside the review rounds the run ends not agreed, and at a spending stop or a credential-shaped pause after them it keeps the verdict the rounds reached, and so does a panel that had signed off at the stop where no seat of the top layer could read the final plan; a relayed `continue` may carry `rounds`, which does what `--rounds N` does at the terminal (on a ladder run, the new total of top-layer reads). The stop at the typed limit of a ladder run (at any number) and the hard stop after seven overseer rounds off the ladder take no answer of the session's own: the gate refuses it. The tool cannot verify that the person gave those words: the gate ledger writes the answer as the session's (actor `main-session`, channel `session`, the words beside it), the run's record of the choice carries the actor `person`, marked `relayed`, with the same words, and every line that names who ended the run says the handling session relayed it, where a choice typed at a terminal shows the channel `cli` and the actor `unknown`.
|
|
191
|
+
- **A relayed raise of the spending limit is the handler session's word (0.8.4).** `council relay-answer`, and `resume_run` over MCP with `max_usd` and `person_words`, record a raised limit that the handler session says the person gave, with the person's own words quoted and the total in them (any MCP client can relay this way, not only a session with a shell). The tool cannot verify that the person gave those words or typed that figure: the record shows the channel (`session`), the actor (`main-session`, a role and not a session id) and the words. The other two ways to raise a limit prove less than they sound: an answer in an MCP client's dialog proves that a client answered, not that a person typed it, and the terminal check is friction, not enforcement (a shell-capable agent can open a terminal).
|
|
192
|
+
- **A price row you add is trusted as typed.** `pricing.user.json` in your project folder can add a model the shipped table lacks or raise a shipped price, never lower one. For a model the shipped table lacks, anything that can write your project folder can set a low price and make the spend cap under-count that model. A run's limit screen, and the screen of a paid rematch, replay, handoff, contract draft or digest, lists the seats priced by that file, with their figures, so a low figure is visible before you type a limit; `council tree start` shows no such screen and uses the file's figure without saying so (`council doctor` and `--dry-run` list the file's rows). The controlling session can raise such a row too, through `tree_decide`, but only for the model a stopped member of that tree was refused for at its price limit, and it never lowers one, and it is told to ask you first and record your yes as the reason (THC cannot check that it asked; nothing runs until you type `council tree resume`): the row is for every run in the project, the record names the session as the one that added it and the source it cited, and a price set too low lets the cap under-count that model on a direct provider.
|
|
193
|
+
- **`price_raise` cannot tell whether the session asked you (0.8.3).** The tool tells the session to show you the quoted text and get your yes in the chat before it confirms, but THC cannot see that, and the `reason` it records is text the session supplied (it is told to pass your words). What does not depend on the session: confirming spends nothing, and `resume_run` afterwards stops at the spending-limit gate again because a price row changed, and that gate is answered by you (the dialog in a client that can ask (Claude Desktop), which the client shows to you, or `council gate answer` in a terminal). The quote makes a network call to openrouter.ai from the MCP server process (the same list the terminal offer reads), and the server reads `pricing.user.json` once at start, so a quote can show a limit that is out of date if the file was edited by hand since.
|
|
194
|
+
- **The session's answers in a tree are the session's, and THC cannot see who is behind the tool call.** Through `tree_decide`, the controlling session can answer some stops of a tree (accept a finished run as it is, resolve a milestone in place, plan a sub-plan again, hand a stop back to you, raise the price of the model a stopped member was refused for, approve the first frontier of a split when every child gets its full ceiling). The record names the session, its channel and its written reason, and never writes such an answer as yours; money (a top-up, the cap, a raised limit on runs, depth or children), closing, abandoning, a new root plan and every personal-data or key-scan answer stay yours, at a terminal (for a new root plan the session may write the draft of the decision: you read it in full there and approve it by typing the stop id, or ask for a change). Approving a frontier lets the children's grants be issued from the tree's cap (the approval itself moves no money); the session is told to show you the quoted text and record your yes in your words before it does (THC cannot check that it asked), and nothing starts until you type `council tree resume`; the record carries the free amount and the grants the code computed when it asked (sessionConditions), which are THC's own figures, not a check by a second party, and the grants themselves are still issued against the free amount at registration. The check is a guardrail between cooperating tools: a program that can call the tool is taken to be the session.
|
|
195
|
+
- **A stop answered through `stop_decide` is the session's, and THC cannot check its advisor.** The handling session can continue or finish a run that stopped at an overseer-round cap, at a layer's revision bound, at a layer's silent seat or where an objection stood unchanged and could not be passed up (a stall: with no pass left only finish is offered); the choice is recorded with who gave it (the handling session, on the session channel), its written reason and the advisor state it states, with the person test of the session and of its advisor (both verdicts, and whether the choice was made over a disagreement). `advisor_said` and the advisor's verdict are stored as the session's statement and never checked. An approval of excerpts, the spending limit, a scan hit and a conflict between objections are refused to it: they are yours. At a stop only the person decides (the hard stop after seven overseer rounds off the ladder, and on a ladder run the stop at the limit the person typed, at any number) it gives no answer of its own, and the gate refuses one: it relays the person's own choice instead (the relayed choice above). The owner's pause is the person's too: the session continues it by resuming the run, and ends it only with the person's words, `finish` or `finish_now`.
|
|
196
|
+
|
|
197
|
+
- **The handoff lists the dependencies the plan names; it does not check them.** When the plan has the build session install or download something, `HANDOFF.md` ends its own part with a "Dependencies to verify" section: one line per item, the name and version copied from the plan, a line for every choice the plan leaves open, and an instruction to check each name before installing. 0.8.3 only lists and asks. It makes no registry lookup and no network call, a model writes the list and can leave an item out or get one wrong, and nothing in the harness confirms the build session did the check. The check is yours.
|
|
198
|
+
- **Who answered a seat that a coding-agent session fills is the session's own statement and is never checked (0.8.3).** `report.json` and the BOARD say which client answered and which model the session declared, and count the panel's companies from that. A session can say anything, and a session that declares no model is shown as not known, not as the company the chain names. Nothing in a run depends on it.
|
|
199
|
+
- **A smaller plan of a tree gets your goal only from a heading of the task (0.8.3).** When a tree splits a plan, each smaller plan is given the sections of your task that its plan names, plus every section headed Non-goals, Non-goal, Exclusions, Out of scope or Not in scope. In a task that has headings, a goal written only above the first heading sits in no section, so no smaller plan receives it: put the goal under a heading of its own. A task with no headings at all, or whose only headings are exclusions headings, is given whole.
|
|
200
|
+
- **With per-provider spend caps on (`COUNCIL_SPEND_CAPS`), a recursive-council tree does not run (0.8.4).** `council tree start`, `council tree resume` and a tree member's start are refused before anything is sent while the setting is on: a tree's limit is one figure per member, not one per provider. Leave the setting empty to run a tree with one typed limit. A dry run of a tree says so and still prices it.
|
|
201
|
+
- **With per-provider spend caps on, the paid side commands do not run (0.8.4):** `council handoff`, `council contract draft`, `council digest`, `--rematch` and `--replay` are refused before anything is sent while `COUNCIL_SPEND_CAPS` is set; each asks for its own single limit. Their dry runs say so and still price the call.
|
|
202
|
+
- **The Desktop plugin cannot set per-provider limits.** Its limit field sets one limit for the whole run. When the operator has set `COUNCIL_SPEND_CAPS`, leave that field blank: each provider's limit is then typed at that provider's own gate.
|
|
203
|
+
- **The dry run cannot include excerpts.** A handling session may add text of your project during a run (an excerpt); every later seat reads it as paid input, and `--dry-run` and `council doctor` cannot know how much. The spending limit is still checked against the prompt that is actually sent, before every call, at an expected cost (see the limit on the spend cap).
|
|
204
|
+
- **Text requests and session reads are answered through the stage-answer tool.** A seat's request for project text, and the files the handling session read for its own answer, are recorded only when the session answers through `submit_stage` (`excerpt_answers`, `session_reads`). A reply written as a file in the run folder records every listed request as unanswered, and records no reads.
|
|
205
|
+
- **The approval screen keeps every text it showed you.** With `--approve-excerpts`, the file `excerpt-approval-<n>.txt` in the run folder holds every text you were asked about, the refused ones included: refused means not sent to any seat, not erased. The folder is on your machine, and the gate's record hashes the screen as shown, so the file stays as it is.
|
|
206
|
+
- **A passage of a project file over 2000000 bytes cannot be quoted.** The harness keeps at most 2000000 bytes of a file for the seats, a whole file or a range alike; that is the limit it already puts on a file a client names. A narrow range of a larger file is cut and sent. A range or a whole file that holds more than that is recorded as not cut, and an answer that names such a whole file or range is refused when it is submitted, so it can be narrowed.
|
|
207
|
+
- **Excerpts are not shown in a descending run.** A chain in descending mode does not show the text a handling session adds during the run, and the files the session read are not recorded; the run log says so.
|
|
208
|
+
- **Text added during a run is checked for keys only.** Project text the handling session adds during a run (an excerpt) is checked for key-shaped strings before any seat is shown it. It is not checked for personal data (email addresses, IBANs, card numbers): that check reads the task at the start of the run. With `--approve-excerpts` the approval screen says so for each text.
|
package/PERSONAS.md
CHANGED
|
@@ -10,8 +10,9 @@ onto a debate-stage prompt - it changes how a seat *talks*, never what it's grad
|
|
|
10
10
|
- **Van Gogh**
|
|
11
11
|
- **Parzival**
|
|
12
12
|
|
|
13
|
-
These are historical and
|
|
14
|
-
trademark.
|
|
13
|
+
These five are historical, religious and literary figures, chosen deliberately because none of them is
|
|
14
|
+
anyone's trademark. They are the default set of `role.persona`, a voice for a debating seat; the seat
|
|
15
|
+
names of `plan-the-high-council` are a different set, described below. That said, using any recognizable name as a software judge's voice is itself an
|
|
15
16
|
editorial choice: a public audience may read it as reverence, satire, or an implied endorsement by
|
|
16
17
|
association, in either direction. That reading is not intended, but it's real, and this file says
|
|
17
18
|
so plainly rather than leaving it for someone to notice on their own.
|
|
@@ -25,7 +26,8 @@ here is a secondary association in a reader's mind, not a legal one - worth havi
|
|
|
25
26
|
not worth revisiting a decision already made.
|
|
26
27
|
|
|
27
28
|
A `role.persona` key that is not in the set in use is sent as a bare name, with no voice
|
|
28
|
-
directive
|
|
29
|
+
directive; `council doctor` and a run's pre-flight list warn about each seat that names one (rule
|
|
30
|
+
`persona-unknown-key`, with the keys that do work). The retired `plan-premium-7` (in `chains/outdated/`) seats two such keys, `hypatia` and `avicenna`, next to the five
|
|
29
31
|
defaults: without a persona file of your own they reach the debate prompt as those names alone.
|
|
30
32
|
|
|
31
33
|
**You can replace the whole set.** `loadPersonas()` in `src/personas.js` reads an operator-supplied
|
|
@@ -53,3 +55,24 @@ safely carry. `.gitignore` covers a `.council/` directory as the suggested place
|
|
|
53
55
|
and your own `COUNCIL_PERSONAS_FILE` - that's a convention, not an enforced one: the environment
|
|
54
56
|
variable accepts any path, and only the `.council/` directory specifically is protected from an
|
|
55
57
|
accidental `git add`.
|
|
58
|
+
|
|
59
|
+
## The seat names of `plan-the-high-council`
|
|
60
|
+
|
|
61
|
+
`plan-the-high-council` switches `seat_personas` on, in the form `voice`, and gives each of its fifteen proposer and judge seats a `persona` name. These are not the five above, and the names are not chosen for being free of anyone's rights: they are real people and figures of faith, and some of them are alive.
|
|
62
|
+
|
|
63
|
+
- Living people: Angela Merkel and Andrej Karpathy.
|
|
64
|
+
- A novelist: Frank Herbert.
|
|
65
|
+
- Figures of scripture and tradition: Noah, Abraham, Moses, Melchizedek, Matthew and Husayn ibn Ali.
|
|
66
|
+
- Historical figures: Van Gogh, Aristotle, Einstein, da Vinci, Archimedes and Plato.
|
|
67
|
+
|
|
68
|
+
Under the form `title`, which six of the seven shipped planning chains use, a name is only the title of a seat: the seat's prompt says the name changes nothing the seat does. Under the experimental form `voice`, which `plan-the-high-council` uses, a seat also gets a few plain sentences about the person's spirit and acts. Those sentences were settled with the maintainer and written from general knowledge; at the release review their reported speech, their counts and superlatives and what they say of who did something first or why were checked against sources, and what no source carried was cut or corrected, or kept on the maintainer's word. They carry no quotation marks. For the two living people they use the public record only and say nothing about inner life beyond what the person has said in public. The Merkel sentence gives in English her words "Wir schaffen das", said at her summer press conference at the Bundespressekonferenz on 31 August 2015 (the federal government's transcript of that press conference has them). No setting gives one name less than another.
|
|
69
|
+
|
|
70
|
+
To use a form for one run without editing the chain file, give `--persona-form title` or `--persona-form voice` on the `council` command (the MCP tools `start_run` and `dry_run` take `persona_form`). It works on a chain that already switches `seat_personas` on, and a run keeps its form when it is resumed.
|
|
71
|
+
|
|
72
|
+
Nobody named here has seen, approved or taken part in this project. A name is a title, not a claim that the person agrees with a plan or that a model speaks as they would.
|
|
73
|
+
|
|
74
|
+
If the top layer of a layered chain holds one seat that carries Melchizedek or Karpathy, `council doctor` and the run's pre-flight list warn that the other name is unused; the run still starts.
|
|
75
|
+
|
|
76
|
+
## Two carriers: debating seats and judges
|
|
77
|
+
|
|
78
|
+
A persona reaches a prompt by one of two routes, and a seat never uses both. A debating seat (`seats.proposers`) takes `role.persona`: a name resolved against this set, with its voice, used in the debate stage only; or the field `persona`, a plain name that is only a title. A judge (`seats.critics`) takes only the field `persona`, together with the chain switch `seat_personas`: a plain name that is never looked up in this set, written into the judge's own system prompts (see AI-README, `persona`). The chain's form is `title` or `voice` for everyone: under `title` every named seat is told its name is the title of its seat and changes nothing it does, and a `role.persona` voice on any proposer is refused (with the switch on it is refused under `voice` too, since the voice then comes from `persona`; the route exists only with the switch off). The judge route never uses the voices on this page.
|