The Council
Convene OpenAI Codex and Google Gemini as an advisory board. Both run in parallel via their CLIs — Codex with read-only codebase access, Gemini reviewing the fully-inlined request — and return independent analyses that Claude synthesizes.
Prerequisites
- Project has a
CLAUDE.mdfile in the working directory - At least one of the following CLIs installed and authenticated:
codexCLI (npm i -g @openai/codex)agyCLI (antigravityCLI)
Permission Setup
Before running any council scripts, request all necessary bash permissions upfront at the start of the session. This prevents permission prompts from interrupting the advisory flow mid-execution.
Tell the user:
The Council needs to run bash scripts to invoke external advisors. I'll request permission for all of them now so the flow isn't interrupted.
Then run all three scripts in sequence to trigger permission grants:
bash <skill_dir>/scripts/council_preflight.sh <working_directory>— CLI availability checkbash <skill_dir>/scripts/council_sync.sh <working_directory>— context syncbash <skill_dir>/scripts/council_invoke.sh(with--helpor a no-op) — advisor invocation
Once permissions are granted, proceed with the workflow. The user will not be prompted again for these scripts during the session.
Workflow
0. Preflight Check (First Invocation Only)
On the first council invocation in a session, run the preflight script to detect available advisors:
bash <skill_dir>/scripts/council_preflight.sh <working_directory>
Parse the output (key=value lines) and determine the operating mode:
| Codex Auth | Gemini Auth | Mode |
|---|---|---|
true |
true |
Full Council — both advisors in parallel |
true |
false |
Codex-only — single advisor mode |
false |
true |
Gemini-only — single advisor mode |
false |
false |
Abort — show installation instructions below |
Gemini backend resolution (v1.4.0): the invoke script picks the Gemini
backend automatically (COUNCIL_GEMINI_BACKEND=auto):
GEMINI_CLI_AVAILABLE |
GEMINI_API_KEY_SET |
Backend |
|---|---|---|
| true | true | gemini (gemini-cli, single-shot JSON — preferred) |
| true | false | agy (pty-wrapped, sandboxed, single-shot) |
| false | any | agy (pty-wrapped, sandboxed, single-shot) |
OpenRouter seats (1.6.0): preflight also reports OPENROUTER_API_KEY_SET and
OPENROUTER_AVAILABLE (key + curl + python3). They are optional extra advisors and
never change the mode above — a machine with only an OpenRouter key still aborts.
Note: gemini-cli's free oauth-personal auth stopped serving 2026-06-18 — the
gemini backend requires a paid Gemini API key (GEMINI_API_KEY or
GOOGLE_API_KEY, AI Studio) exported in the environment.
Preflight-vs-invoke caveat: preflight counts ~/.gemini/oauth_creds.json as
Gemini credentials, but the invoke script's auto mode only selects gemini-cli
when GEMINI_API_KEY or GOOGLE_API_KEY is set — a machine with only stale
oauth creds and no agy installed will preflight green but fail loud at invoke
(the resolved backend agy is not in PATH).
If no advisors are available, display this help and stop:
Neither Codex nor Gemini CLI is available. To use The Council, install at least one:
Codex: npm i -g @openai/codex && codex auth
Gemini: Install agy CLI and verify it is logged in via ~/.gemini/oauth_creds.json (runs automatically on first command)
If one advisor is missing, note which mode is active and proceed. Example:
Council running in Codex-only mode (Gemini CLI not found).
The preflight result is cached for 2 hours — subsequent invocations skip this step automatically.
0.5. Select the Panel (MANDATORY — before composing the prompt)
Read references/reviewer-selection.md and pick the panel by consequence, not by diff size:
| Tier | What it covers | Panel |
|---|---|---|
| Routine | ordinary diffs, bug fixes, refactors | Fable (low) + Codex + Gemini |
| Hard-to-reverse | architecture, migrations, auth/RLS/PHI, wire contracts, any written plan | Fable (low) + Codex + Gemini |
| Milestone | launch-gating work, initiative plans, anything an executor will build from | the above; Fable (high) at merge |
Codex + Gemini are mandatory on EVERY round unless Tom says otherwise (Tom 2026-09-19). Qwen and GLM are never a default seat — add --openrouter qwen / glm only when Tom asks in that conversation. the-council #6 ("Qwen the default reviewer", 2026-09-09) contradicted this and was reverted in 1.6.3.
Four decisions this step makes, all load-bearing:
- Fable runs alongside Council, not instead of it. The advisors reason about the text they are handed; Fable re-executes the claims and is the only reviewer that catches our own false statements. Low by default — its work is mechanical, and effort does not make a grep more true. Reserve high for the merge of a stage that ships a fix, where the question becomes "does this close the class, or only the named trigger?"
- Codex seat =
gpt-5.6-sol(Tom 2026-09-08; Astra retired for cost — it consumed usage and OpenRouter spend far faster with no measured review gain). On native failure the OpenRouter fallback isopenai/gpt-5.6-sol— never a lesser variant (not-pro, not a Codex-family id). Sol-era evidence: in the source sessiongpt-5.3-codexreturned REVISE andgpt-5.6-solreturned RESTRUCTURE on the identical prompt, with three critical findings no other reviewer produced. Model choice changed the verdict. Never resolve a model id from a truncated listing — list the full family and grep it. - Qwen (
qwen/qwen3.8-max, milestone tier) needsmax_tokens≥ 32000. At 9k it spent every completion token on reasoning and returned empty content —finish_reason: MAX_TOKENS. - Isolated workspace when the repo is busy. If another session or process is writing to the
target repo — or the review is of a document rather than a diff — run Council in a scratch
workspace with
CLAUDE.mdcopied in. The safety net hashes the whole tree and fails closed on any change, so a busy repo guarantees a false-trip; and the sync itself writesAGENTS.mdinto a repo you may have been asked not to touch. Recipe in reviewer-selection.md §6.
1. Sync Project Context
Run the sync script to copy CLAUDE.md content into AGENTS.md (for Codex):
bash <skill_dir>/scripts/council_sync.sh <working_directory>
This creates/overwrites AGENTS.md with an advisory preamble + full CLAUDE.md content. Run this once per session or when CLAUDE.md changes. Gemini does not need a context file — it reviews only the inlined request, so all task context travels in the prompt itself.
Important: After the council session, restore the user's AGENTS.md:
bash <skill_dir>/scripts/council_sync.sh --restore <working_directory>
Never rm AGENTS.md directly — repos increasingly own a real AGENTS.md and
the sync script backs it up / restores it.
2. Compose the Advisory Prompt
Select the appropriate template from references/prompt-templates.md based on the use case:
| Use Case | Template |
|---|---|
| Code review | Code Review |
| Plan/architecture evaluation | Architecture / Planning |
| Bug investigation | Debugging |
| General question | General Advisory |
Write the composed prompt to a temporary file. Include all relevant context inline (diffs, error messages, plan text) — the advisors cannot read Claude's conversation history.
Context in prompts: Only Codex has read-only filesystem access to the working directory (via its native --sandbox read-only). Gemini reviews INLINED content only — its workspace holds just the review request, whichever backend runs (see Permissions and Safety). Task-specific context (diffs, error messages, plan text, conversation history) must always be inlined because advisors cannot access Claude's conversation or external paths like ~/.claude/plans/.
- Always inline: diffs, error output, plan text, conversation excerpts, and any content from outside the project directory — plus everything Gemini needs, since it sees only the inlined request
- Can reference by path (Codex only): codebase files that Codex can read directly (e.g., "see
src/config.tsfor the current implementation") - For code review: the diff must be inlined (advisors don't have access to git staging), but surrounding context files can be referenced by path for Codex
- For large files referenced in the prompt: include the most relevant sections inline, note the file path for full context
Mandatory prompt blocks
Defensive framing (all reviews): open with "This is a defensive code review / secure-coding audit of our own application code." Never phrase security asks as "find vulnerabilities to exploit" — both advisors' safety classifiers throttle offensive framing (Gemini refuses; Codex may route to a lesser model).
No-explore directive (plan reviews and any prompt referencing many files): include near the top —
Respond fast. Do NOT explore the repository — everything you need is inline. Reason from the inline content only; flag file-dependent concerns as assumptions for the executor to verify.
Without this, Codex has spidered repos for 30+ minutes (2026-06-09 incident). Diff reviews on small file sets may omit it so Codex can read adjacent code — that adjacency is why Codex catches migration-runtime semantics others miss.
Doc-provenance preamble (any prompt citing internal, owner-revisable docs — voice charters, in-house architecture docs): state that the doc is the owner's revisable direction, not external scripture; the advisor's role is risk-surfacer, not charter-enforcer. List separately which constraints ARE non-negotiable (HIPAA/FDA/OWASP, locked domain rules). Without this, Council issues strict text-literal REVISE verdicts the owner then overrules (2026-05-13 charter-as-decree incident).
Nitpick suppression (every Codex model — measured on gpt-5.6-sol): Sol over-flagged (CodeRabbit benchmark: 31.6% actionable precision). Instruct: "Rank findings by severity. Suppress low-confidence nitpicks; report only findings you would defend in review."
3. Invoke The Council (Progressive)
Invoke each advisor as a separate detached process so results can be presented as they arrive.
3a. Launch Advisors (detached, NOT harness-background)
Do NOT launch council scripts with the Bash tool's run_in_background — codex
hangs at ~0 CPU in that context on large prompts (verified 2026-07-11).
Launch each advisor DETACHED from a normal foreground Bash call, which
returns immediately:
nohup bash <skill_dir>/scripts/council_invoke.sh --codex-only <prompt_file> <working_directory> \
> /tmp/council_codex_launch.log 2>&1 & disown
nohup bash <skill_dir>/scripts/council_invoke.sh --gemini-only <prompt_file> <working_directory> \
> /tmp/council_gemini_launch.log 2>&1 & disown
Usage plan (1.6.3, Tom 2026-09-19) — the banner now prints, per seat and BEFORE anything
launches, what the run is about to spend: input = the fixed native-Codex overhead (~24.5k
tokens: Codex's own instructions + tools + the configured MCP servers — measured, and
-c mcp_servers={} does not shrink it) + the prompt; requested output = the band the effort
level implies (Codex has no hard output cap — effort IS the lever; OpenRouter seats also carry
max_tokens); cost at OpenRouter list price per seat and a total. A seat whose estimated
input exceeds COUNCIL_MAX_INPUT_TOKENS (default 100,000) is REFUSED before launch — trim
the prompt, or COUNCIL_ALLOW_OVERSIZE=1 to launch loudly. After the run the report line of
every seat carries its ACTUAL tokens and cost: Codex's from its --json event stream
(codex_usage.log), the OpenRouter seats' from <seat>_usage.log. Quote these numbers to
Tom with the verdicts — the Codex seat is his ChatGPT quota, the rest is his OpenRouter money.
OpenRouter seats (1.6.0) — the Qwen seat and the GLM seat, BOTH only when Tom asks, or
any raw vendor/model id, over HTTPS with no CLI and no repo access. Launch them the
same way; one process can carry several seats:
nohup bash <skill_dir>/scripts/council_invoke.sh --openrouter-only --openrouter qwen <prompt_file> <working_directory> \
> /tmp/council_openrouter_launch.log 2>&1 & disown
Named seats resolve AT LAUNCH to the newest flagship on OpenRouter's live listing
(numeric version order; flash/turbo/preview/thinking variants ignored) — the banner
prints the id and its source (newest on the live listing / explicit id /
FALLBACK last-known id). Script-chosen ids carry the :floor routing suffix — OpenRouter
picks the cheapest host currently serving that exact model (same weights; hosts differ 2-3×
in price). COUNCIL_OPENROUTER_ROUTING=none sends the bare id, nitro picks the fastest;
explicit COUNCIL_*_MODEL pins and raw ids are never suffixed. <seat>_usage.log records
provider= so the saving is checkable per run. Never hand-roll a curl for these seats again; that is how
the truncated-listing wrong-model review happened. Responses stream, so a timeout
still salvages partial text (<seat>_partial.md), and every run writes
<seat>_usage.log with the served model, reasoning tokens and USD cost. Measured
2026-09-06 on a 26 KB diff at medium: Qwen ~$0.08, GLM ~$0.02, both well inside the
default timeout.
Each produces its own temp directory (.council-tmp/council_codex_YYYYMMDD_HHMMSS/
and .council-tmp/council_gemini_YYYYMMDD_HHMMSS/ inside the working directory).
Then poll for the response files (the launch log's last lines name them) — e.g. with a Monitor until-loop on file existence, or periodic checks. A liveness rule: if the advisor process shows under ~2s of CPU time after 120s, treat it as hung, kill it, and relaunch once.
For single-advisor modes (Codex-only or Gemini-only), launch only the available advisor as a single detached process.
3b. Poll and Present Progressive Results
After launching both advisors, poll for their response files (per §3a) rather than blocking on either. When the first advisor finishes:
- Read its response from the temp directory path printed in its output
- Present the early result to the user immediately:
## Early Result: {Advisor Name} ({model})
{advisor response}
---
*Waiting for {other advisor name} to complete...*
- Continue polling the second advisor
When the second advisor finishes, read its response and proceed to Step 3.5 (question detection) and then Step 4 (synthesis).
3c. Handling Failures
An advisor has failed only when the invoke script says so — it prints
(failed: <reason>) next to the response path and exits 1. When that happens:
Read the error log first — always read the
*_error.logfile from the temp directory before reporting failure:- Codex:
<working_directory>/.council-tmp/council_codex_*/codex_error.log - Gemini:
<working_directory>/.council-tmp/council_gemini_*/gemini_error.log
- Codex:
Read the response file — even failed runs may have partial output worth presenting
Report the script's reason string, not a guess. The validation engine's failure reasons (v1.4.0):
empty response— advisor ran but produced no text outputplaceholder— the script wrote a[COUNCIL-ADVISOR-FAILURE]placeholder (launch/backend error; the placeholder's first line names the specific cause)refusal— refusal phrasing at the start of the response with no verdict linenon-engagement— short verdict-less response to a large prompttimed out (COUNCIL_TIMEOUT)— advisor exceeded the per-advisor timeoutadvisor exited N without a verdict-bearing response— nonzero exit AND no verdict line in the response head
stderr noise (RESOURCE_EXHAUSTED, rate limit, 429...) alongside a substantive response is NOT a failure — it's an advisory warning recorded in
*_warnings.log; see Error Handling. Script-level launch errors (command not found, missing resolved-backend binary) surface in the launch log before any response file exists.If one advisor succeeds, present its response and note the other's failure with the actual reason
If both fail, present both error logs and suggest checking CLI authentication (
codex auth/gemini auth)
3d. Fallback
If detached launching is unavailable in your environment, fall back to the single blocking call:
bash <skill_dir>/scripts/council_invoke.sh <prompt_file> <working_directory>
Environment overrides:
CODEX_MODEL— default: from~/.codex/config.toml(standard: gpt-5.6-sol, Tom 2026-09-08)COUNCIL_CODEX_EFFORT— default:medium. Effort follows the review tier (Tom 2026-08-20):mediumroutine /highhard-to-reverse /xhighmilestone-only. Set explicitly per invocation when the tier calls for more;configdefers to config.toml. Blanket xhigh is retired — it burned native quota + OpenRouter spend on routine diffs.COUNCIL_TIMEOUT— default:600(seconds per advisor; raise to 900 for very large xhigh reviews)COUNCIL_GEMINI_BACKEND— default:auto(gemini|agy)COUNCIL_GEMINI_MODEL— optional model pin for either backendCOUNCIL_OPENROUTER_SEATS— same as--openrouter(flag wins):qwen,glm, or rawvendor/modelids, comma-separated. NeedsOPENROUTER_API_KEY.COUNCIL_OPENROUTER_EFFORT— default: followsCOUNCIL_CODEX_EFFORT(noneomits the reasoning block)COUNCIL_OPENROUTER_MAX_TOKENS— default:32000(Qwen starves below ~32k)COUNCIL_QWEN_MODEL/COUNCIL_GLM_MODEL— explicit id for a named seat; skips the live-listing lookupCOUNCIL_SNAPSHOT_EXCLUDES— comma-separated pathspecs excluded from the safety-net snapshotAGY_PRINT_TIMEOUT— default:8m(must stay below COUNCIL_TIMEOUT)
During the Advisory Window (MANDATORY)
- No writes anywhere under the working tree while any advisor runs. Not
scratch ledgers, not
.generatedregens, not fixes for findings the first-returning advisor reported. The safety net hashes everything; a one-line append fires exit 2, and applying fixes mid-window makes the still-running advisor review a stale tree (2026-06-18 + 2026-07-04 incidents). Do bookkeeping in the session scratchpad OUTSIDE the repo. - Run Council before CodeRabbit, never concurrently. Concurrent CodeRabbit cache writes trip the safety net, and 3 concurrent heavy reviews starved Codex to an empty-output death (2026-06-16). Cap concurrent heavy review processes at 2 (the two Council advisors).
3.5. Question Detection & Auto-Retry
After reading each advisor's response (during progressive polling or after completion), check whether the response contains questions directed at you rather than analysis. Advisors sometimes ask clarifying questions instead of providing their assessment.
Detecting Questions
Scan the advisor response for patterns indicating it needs clarification rather than providing analysis:
- Direct questions ("What is...", "Can you clarify...", "Which approach...", "Could you provide...")
- Requests for information ("I need to know...", "Please share...", "It would help to understand...")
- Conditional analysis ("If X then Y, but if Z then W — which is the case?")
Not all question marks are triggers. Rhetorical questions, questions posed as part of analysis ("Have you considered...?"), and section headers ("What could go wrong?") are normal advisory output. Only trigger retry when the advisor is unable to provide analysis without the answer.
Heuristic: If the response is short (under ~200 words) AND primarily consists of questions rather than analysis, treat it as a question response. If the response contains substantial analysis alongside questions, treat it as a normal response.
Auto-Answer and Retry Flow
When a question is detected in an advisor's response:
Extract the question(s) from the response
Attempt to answer from project context — search the codebase, CLAUDE.md, conversation history, and relevant files
Assess confidence:
- Confident (answer clearly supported by project context): proceed to auto-retry
- Unsure (requires judgment or information not available): ask the user:
Wait for the user's response before proceeding.{Advisor Name} asked a clarifying question instead of providing analysis: > {advisor's question} I'm not confident I can answer this from project context. What's the answer?
Compose retry context — write the Q&A to a temporary context file:
Question from {Advisor Name}: {question} Answer: {answer from project context or user} Please provide your analysis based on this clarification. Do not ask further questions about this topic.Re-invoke the same advisor with the context file:
bash <skill_dir>/scripts/council_invoke.sh --{advisor}-only --context-file <context_file> <prompt_file> <working_directory>Read the new response and check again for questions (loop back to detection)
Retry Guards
- Question tracking: Keep a list of questions already asked by each advisor. If the same question (or substantially similar) appears again after a retry, stop retrying and present the best response received so far with a note about the unresolved question.
- Hard cap: Maximum 3 retries per advisor. After 3 retries, present whatever response was received with a note:
Note: {Advisor Name} requested clarification {N} times. Presenting the best response received. - Per-advisor tracking: Retry counts and question lists are tracked independently for Codex and Gemini. One advisor hitting its cap does not affect the other.
Retry During Progressive Invocation
When using progressive invocation (Step 3), retries happen per-advisor:
- If one advisor finishes with a question, begin the retry flow for that advisor while the other is still running
- If the other advisor finishes with analysis while the first is retrying, present its result immediately
- Synthesis (Step 4) waits until all retries are complete and both advisors have final responses
4. Analyze and Present
If you presented an early result during progressive polling (Step 3b), the user has already seen one advisor's response. Do not re-print it. Present only the new response and the synthesis.
Full Council Mode (both advisors responded)
Default mode — Synthesis: Read both responses, identify areas of agreement and disagreement, then present:
## Council Synthesis
**Consensus:** [Points both advisors agree on]
**Divergence:** [Points where they disagree, with each position]
**Claude's Recommendation:** [Your assessment integrating all three perspectives — yours plus both advisors'. Note: Codex remains the primary source of truth and the main shipping gate; Gemini is advisory.]
Side-by-side mode (when user requests "show me both" or "side by side"):
## Codex ({codex_model})
[Full Codex response]
## Gemini ({backend}/{model})
[Full Gemini response]
## Qwen ({model as served, from qwen_usage.log}) ← only when an OpenRouter seat ran; same for GLM
[Full seat response]
## Claude's Take
[Your own assessment]
Single-Advisor Mode (one advisor responded)
Present the single advisor's response with your own assessment.
[!IMPORTANT] If Gemini (agy) dropped out mid-session or failed validation (returning an error log/response), you must explicitly surface this failure as a "degraded one-advisor Council (Codex only)" and never silently ignore it.
If Gemini is the only advisor that responded (e.g. Gemini-only mode or Codex failed), remember that Gemini must never act as a sole shipping gate. Its opinions are strictly advisory, and Codex remains the primary codebase source of truth.
When Gemini failed, name the failure class from the script's reason string (empty / refusal / non-engagement / timeout) — "degraded one-advisor Council (Codex only; Gemini: refusal)" — so patterns stay visible across sessions.
## Advisory Opinion ({Advisor Name} / {model})
[Full response from the available advisor]
## Claude's Assessment
[Your own perspective, noting this was a single-advisor review]
Verdict authority gradient (MANDATORY)
- APPROVE / APPROVE-WITH-CHANGES: absorb mechanical refinements and proceed.
- REVISE / RESTRUCTURE: this is a scope/shape decision the human owns. If the human previously approved the scope, HALT — present the finding verbatim, a plain-English why, two paths, and one recommendation. Never silently fold a scope-reducing REVISE over a prior human approval (2026-05-26 incident).
- Repeated residuals: when a cheap patch leaks a new residual every round and all residuals trace to one structural fact, stop patching and take the robust option the reviewer already named (2026-06-24 lesson).
The stopping rule (MANDATORY — three rounds is a ceiling, not a floor)
After each round, state the root cause of the residual in one sentence. If that sentence is the same as the previous round's, the next action is a DECISION, not another review round.
A 2026-08-04 launch-plan review ran three rounds and returned three RESTRUCTURE/REVISE verdicts while the residual never moved — the same root fact each time, wearing new vocabulary. The conclusion finally adopted was available at round one; rounds two and three confirmed it at real cost in time and scope.
This is the operational form of the repeated-residuals rule above and of the standing lesson that Council ratchets guards and never releases them — "is this safe?" has no failing state toward too-quiet. Without a stopping rule, a multi-advisor panel becomes a scope-inflation engine at exactly the moment you need to ship. See references/reviewer-selection.md §5.
Verifying advisor claims
Gemini findings that name specific code patterns MUST be verified with one grep/build before being surfaced as actionable — Gemini has fabricated multi-file "compilation blockers" with fake code blocks (2026-04-23). Sweeping identical cross-file claims are a pattern-match red flag; real bugs concentrate in 1–2 files. Trust grep over the advisor's quoted snippet. Codex remains the primary source of truth and the mandatory shipping gate: wait for its response even when every other layer is green.
5. Cleanup
CRITICAL: Do NOT clean up until ALL of the following conditions are met:
- All advisor responses (including retries) have been fully read into your context (i.e., you have used the Read tool on every response file and have the content in your conversation)
- Synthesis (Step 4) is complete and has been presented to the user
- Advisors launched detached (§3a) have each exited AND you have read all output files before cleanup
Why this matters: Response files live inside .council-tmp/. If you delete that directory before reading the files, the responses are lost permanently.
Once all conditions above are satisfied, clean up in this order (ORDER IS
LOAD-BEARING — the AGENTS.md backup lives inside .council-tmp/, so restore
MUST precede deletion):
bash <skill_dir>/scripts/council_sync.sh --restore <working_directory>(restores or removes AGENTS.md from its backup inside .council-tmp/)- Remove the prompt file.
rm -rf <working_directory>/.council-tmp/— LAST, after the restore and after every response file has been read into context.
Permissions and Safety
Both advisors run with OS-enforced read-only sandboxes on macOS. Neither can write to your project directory; tool calls that try to write fail at the OS layer.
- Codex:
--sandbox read-only— Codex's built-in OS-level filesystem deny-write. - Gemini (
agy) on macOS: wrapped insandbox-execusing a deny-write profile atscripts/council_sandbox.sb. The profile allows reads everywhere; allows writes only to~/.gemini/, the per-invocation.council-tmp/<...>/dir, system temp (/tmp,/private/tmp,/private/var/folders), and the agy-specific subdirs under~/Library/Caches/(agy/,Google/). All other writes (including the project tree) are blocked at the OS layer. - Gemini (
agy) on non-macOS:sandbox-execis unavailable. The script REFUSES to run agy unless the caller passes--allow-unsandboxed-gemini, in which case agy runs unsandboxed with a loud warning — the diff safety net below is the only protection in that mode. Or use--codex-onlyto skip Gemini entirely.
Diff safety net (defense in depth — runs on all platforms)
Before launching either advisor, the script snapshots $WORK_DIR (git: HEAD + status --ignored=traditional + sha256 of tracked + untracked + gitignored files; non-git: find + sha256). After both advisors return, the snapshot is repeated and diffed. The snapshot excludes .council-tmp/ (the script's own response files) and .antigravitycli/ (agy's per-workspace session-metadata directory, created via Apple APIs that bypass sandbox-exec and known not to be a security concern).
Any unauthorized change to the working tree causes the invocation to fail closed with exit code 2 and a [COUNCIL_SAFETY_NET] error banner pointing at the diff. This catches any escape from the sandbox — including writes to gitignored files like .env, dist/, or node_modules/, which the previous --exclude-standard snapshot logic would have missed (Council R1 fix, 2026-05-25).
Historical note (1.2.x ghost-write incident)
Versions 1.2.0 and 1.2.1 documented agy's read-only property as "stdin-EOF + --dangerously-skip-permissions together prevent tool approvals." That reasoning was wrong: agy --help says --dangerously-skip-permissions is "Auto-approve all tool permission requests without prompting" — it grants permission, not withholds it. A 2026-05-24 incident saw agy ghost-write 8 files inside $WORK_DIR during what was supposed to be an advisory Council R1. Upgrade to 1.3.0 or later to get the OS-level sandbox enforcement above.
When you see [COUNCIL_SAFETY_NET] fire
Either (a) an advisor sandbox escape (rare — investigate as a real security issue), or (b) genuine concurrent work in $WORK_DIR by you or another process during the invocation window. Inspect the diff file the banner points at; you'll get response files and a clean before/after snapshot regardless. Do not trust the advisor recommendations until you've audited the diff.
Model and Effort Configuration
- Codex: model from
~/.codex/config.toml(standard:gpt-5.6-sol, Tom 2026-09-08), overridable viaCODEX_MODEL. Reasoning effort is set EXPLICITLY by the script:-c model_reasoning_effort=mediumby default (COUNCIL_CODEX_EFFORTto change). Nitpick note: Sol had the highest review recall of any model measured but over-flagged nitpicks — the prompt templates keep the suppression instruction on for Astra until measured otherwise. - Gemini: backend-dependent. gemini-cli: model via
COUNCIL_GEMINI_MODEL→-m. agy:COUNCIL_GEMINI_MODEL→--model(runagy modelsfor ids; a Pro tier gives deeper reviews than the default Flash). - OpenRouter seats (
--openrouter qwen,glm): model resolved at launch as the newest flagship of the family on OpenRouter's live listing (override viaCOUNCIL_QWEN_MODEL/COUNCIL_GLM_MODEL); effort followsCOUNCIL_CODEX_EFFORTunlessCOUNCIL_OPENROUTER_EFFORTsays otherwise;max_tokens32000 by default. Advisory only — Codex remains the gate. - All advisors review INLINED content; only Codex additionally has read-only filesystem access to the working directory.
Fallback and extra seats via OpenRouter
Two different things share the OpenRouter account:
- The Codex FALLBACK — used only when the native Codex CLI fails (rate limit / quota — read the error log, never guess). Still a config.toml provider switch, see references/reviewer-selection.md §2.
- Extra SEATS — Qwen and GLM (both only when Tom asks), scripted since 1.6.0
via
--openrouter qwen,glm. No hand-built curl. §3 and §7 there.
| Seat | Model id | Notes |
|---|---|---|
| Codex fallback | openai/gpt-5.6-sol |
Never a lesser variant (id confirmed on OpenRouter's listing 2026-09-08). Sol-era evidence: 5.3-codex → REVISE vs 5.6-sol → RESTRUCTURE on an identical prompt, 2026-08-04. |
| Qwen (on Tom's ask) | newest qwen/qwen<ver>-max on the live listing + :floor (2026-09-06: qwen3.8-max-0902) |
Resolved by the script at launch; max_tokens 32000 default; starvation is detected and named. ~$0.08 per 26 KB diff review at medium. |
| GLM (on Tom's ask) | newest z-ai/glm-<ver> on the live listing + :floor (2026-09-06: glm-5.3) |
Unmeasured lens; ~$0.02 per 26 KB diff review at medium. Not part of any tier by default. |
Operational notes that cost a session to learn:
- Never resolve a model id from a truncated listing. List the whole
openai/orqwen/family and grep it. Ahead -20cut the 5.6 family off the list and produced the weaker review above. - A
403 "Key limit exceeded (total limit)"is a per-key spend cap, not an account balance — adding credit changes nothing. Raise the limit on the key itself at the URL in the error body. Inspect withGET /api/v1/key(usage/limit/limit_remaining). - Long reviews exceed a 2-minute foreground tool timeout: launch the seat
detached (
nohup … & disown) and poll for<seat>_response.md, exactly as §3a does for the advisors. The script streams and parses for you — never read<seat>_raw.jsonwhile curl is still writing it. - Always report
<seat>_usage.log: served model,cost_usd,reasoning_tokens. High reasoning tokens with empty content is budget starvation, not a refusal — the script names it (starved:); raiseCOUNCIL_OPENROUTER_MAX_TOKENSor lower the effort.
Error Handling
See Section 3c above for detailed failure diagnostics. Never guess error messages — always read the actual error log files before reporting failures. Key rules:
- An advisor has FAILED only when the script says so: reasons are
empty response,placeholder,refusal,non-engagement,timed out, oradvisor exited N without a verdict-bearing response. - Loud partial success: a nonzero advisor exit WITH a verdict line in the
response head is RETAINED, not failed — the script writes a
*_warnings.logadvisory and logs it loudly. Verify the response is complete before relying on it. - stderr noise (RESOURCE_EXHAUSTED, rate limit, 429...) with a substantive
response file is an ADVISORY WARNING (
*_warnings.log), not a failure — present the response normally and mention the warning. - On
[COUNCIL_SAFETY_NET]exit 2: readworktree_diff.txtFIRST. Known benign churn (.remember/,.tmp.driveupload/) is already excluded; any remaining diff is either a real advisor escape (investigate as a security issue) or your own concurrent edit (see During the Advisory Window). Advisor responses remain readable either way.
Learning from the Council (Generalize Knowledge)
After presenting the council synthesis, reflect on what the advisors revealed — gaps, blind spots, better approaches, or project-specific conventions that Claude missed.
Generalize these learnings into CLAUDE.md and AGENTS.md so future agents start with those lessons already loaded. Write down any information that would be useful for a future agent working in this repository, including:
- What the advisors caught that Claude missed
- Why it was missed (root cause, not just symptom)
- The corrected approach or pattern to use next time
- Any project-specific conventions the council surfaced
Template for a council learning entry:
## Council Learning — [date]
- **Insight:** [what the council revealed]
- **Caught by:** Codex / Gemini / Qwen / Fable / several
- **Root cause:** [why this was missed]
- **Lesson:** [concrete rule or pattern for future sessions]
Also record which SEAT caught it. Panel composition is a live decision (§0.5), and it should be tuned by evidence rather than habit. If one seat repeatedly produces nothing the others missed, drop it from that tier; if a tier keeps missing a class of defect, add the lens that would have caught it. The seat attribution table in references/reviewer-selection.md §1 is the running record — update it when a session materially changes the picture.
This closes the feedback loop: the council exposes blind spots → learnings become permanent project memory → future sessions start smarter.
When to Convene The Council
- User explicitly asks for it ("ask the council", "get a second opinion", "council review")
- High-stakes architectural decisions affecting multiple systems
- Debugging sessions stuck after multiple failed attempts
- Before finalizing major implementation plans
- Code review of security-sensitive changes