Install
Four ways in. They produce the same working tool — pick by how much machinery you want between you and the files.
| # | Method | Needs | Updating | Best for |
|---|---|---|---|---|
| 1 | Claude Code plugin | Claude Code, git access | one command (update_check.py --apply), or Claude Code's own auto-update once you switch it on |
most people |
| 2 | Installer script | PowerShell or bash | one command | no plugin system, or an offline zip |
| 3 | Manual copy | nothing at all | one command | locked-down machines, air-gapped, "just give me the files" |
| 4 | Run in place | nothing | git pull |
trying it once without installing |
Everything is plain Python using only the standard library. There is nothing to pip install,
nothing is compiled and nothing runs in the background. The one thing the kit sends on its own
is a version check against api.github.com, at most once a day, after a real round or at session
start — so you learn about a release, and the notice names the one command that installs it. With
automatic updates on, a script install also downloads that release from
github.com at session start.
python update_check.py --show-what-would-be-sent prints that request verbatim;
MODEL_ORCH_UPDATE_CHECK=0 switches it off. Everything else leaves your machine only when you run
a review, to the channels you chose.
Prerequisite: Python 3.9 or newer
Section titled “Prerequisite: Python 3.9 or newer”python --versionIf that fails, install Python from python.org and tick
"Add Python to PATH" during setup. On macOS and most Linux systems it is already there, sometimes
as python3.
Method 1 — Claude Code plugin (recommended)
Section titled “Method 1 — Claude Code plugin (recommended)”This repository is itself a plugin marketplace. In Claude Code:
/plugin marketplace add igorsaevets/ai-second-opinion/plugin install model-orchestration@review-channelsThen restart the session, or run /reload-plugins.
Updates. Switch them to automatic once — python <plugin folder>/update_check.py --auto-update on,
or /plugin → Marketplaces → review-channels → Enable auto-update (details:
Automatic updates). Until then, once a day the kit checks GitHub and, when a
release is out, tells you at session start (you and the assistant both see it) with the one
command that installs it:
python <plugin folder>/update_check.py --applyOn this install path that command runs claude plugin update model-orchestration@review-channels
for you — the same thing as typing /plugin update model-orchestration@review-channels inside
Claude Code — and asks you to restart the session. Claude Code can also update the plugin by itself,
but for a third-party marketplace like this one its auto-update is off by default (measured
2026-09-11 on claude 2.1.268: a stale install stayed stale until plugin update was run). To switch
it on: /plugin → Marketplaces → review-channels → Enable auto-update. DISABLE_AUTOUPDATER=1
in your environment also disables plugin auto-updates unless FORCE_AUTOUPDATE_PLUGINS=1 is set.
A private repository works exactly the same way — you only need normal git access to it.
Rolling it out to a team
Section titled “Rolling it out to a team”Put this in your team repository's .claude/settings.json and anyone who clones it is offered the
plugin automatically:
{ "extraKnownMarketplaces": { "review-channels": { "source": { "source": "github", "repo": "igorsaevets/ai-second-opinion" }, "autoUpdate": true } }, "enabledPlugins": { "model-orchestration@review-channels": true }}Method 2 — Installer script
Section titled “Method 2 — Installer script”Download the repository (green Code button → Download ZIP, or git clone), unpack it, and
from the root of it:
Windows (PowerShell):
.\install.ps1macOS / Linux:
./install.shIt copies the skill into your home directory and runs the doctor. If an install is already there,
it hands the whole job to upgrade.py — see Updating — so
"install" and "update" are the same command and the same tested code path.
If PowerShell refuses to run the script, it is the execution policy, not the script:
powershell -ExecutionPolicy Bypass -File .\install.ps1Method 3 — Just copy the files
Section titled “Method 3 — Just copy the files”No git, no plugin system, no installer, no elevated permissions. This is the whole procedure:
Copy this folder from the repository:
plugins/model-orchestration/skills/model-orchestration/To this location on your machine:
| Agent / system | Destination |
|---|---|
| Claude Code (Windows) | %USERPROFILE%\.claude\skills\model-orchestration\ |
| Claude Code (macOS/Linux) | ~/.claude/skills/model-orchestration/ |
Any agentskills.io agent (Cursor, Codex, Gemini CLI, Kimi, Qwen, etc.) |
~/.agents/skills/model-orchestration/ |
| opencode (Windows) | %USERPROFILE%\.config\opencode\skills\model-orchestration\ |
| opencode (macOS/Linux) | ~/.config/opencode/skills/model-orchestration/ |
Create the parent skills folders if they do not exist. When you are done, the destination
should contain SKILL.md, orchestrate.py, doctor.py and the references/ and systems/
folders — not another folder containing those. Copying to one of the rows is enough for the
agent that reads that path; copying to all rows makes the same install work for every agent on the
machine.
Copy-paste commands, if you prefer
Windows PowerShell, run from the unpacked repository root:
# Claude Code$dst = "$env:USERPROFILE\.claude\skills\model-orchestration"New-Item -ItemType Directory -Force (Split-Path $dst) | Out-NullCopy-Item "plugins\model-orchestration\skills\model-orchestration" $dst -Recurse -Forcepython "$dst\doctor.py"
# Any agentskills.io agent (Cursor / Codex / Gemini / etc.)$dst2 = "$env:USERPROFILE\.agents\skills\model-orchestration"New-Item -ItemType Directory -Force (Split-Path $dst2) | Out-NullCopy-Item "plugins\model-orchestration\skills\model-orchestration" $dst2 -Recurse -Force
# opencode$dst3 = "$env:USERPROFILE\.config\opencode\skills\model-orchestration"New-Item -ItemType Directory -Force (Split-Path $dst3) | Out-NullCopy-Item "plugins\model-orchestration\skills\model-orchestration" $dst3 -Recurse -Forcepython "$dst3\doctor.py"macOS / Linux, from the repository root:
dst="$HOME/.claude/skills/model-orchestration"mkdir -p "$(dirname "$dst")"cp -R plugins/model-orchestration/skills/model-orchestration "$dst"python3 "$dst/doctor.py"
# agentskills.iodst2="$HOME/.agents/skills/model-orchestration"mkdir -p "$(dirname "$dst2")"cp -R plugins/model-orchestration/skills/model-orchestration "$dst2"
# opencodedst3="$HOME/.config/opencode/skills/model-orchestration"mkdir -p "$(dirname "$dst3")"cp -R plugins/model-orchestration/skills/model-orchestration "$dst3"python3 "$dst3/doctor.py"Agent did not auto-discover the skill? Use the absolute path — it always works:
python "$HOME/.claude/skills/model-orchestration/orchestrate.py" --ask "your question"# opencode alt: python "$HOME/.config/opencode/skills/model-orchestration/orchestrate.py" --ask "..."Getting one folder onto a machine with no internet access: copy it on a USB stick. It is a few hundred kilobytes of text files. That is the entire deployment story.
Method 4 — Run it without installing
Section titled “Method 4 — Run it without installing”You can run the scripts straight out of the unpacked repository:
python plugins/model-orchestration/skills/model-orchestration/doctor.pyEverything works — the scripts resolve their own location and find their presets and reference files relative to themselves. The only thing you lose is that Claude Code will not know the skill exists, so you have to invoke the commands yourself rather than asking for a second opinion in plain language.
Updating an existing install
Section titled “Updating an existing install”One command, run from the copy you have. It looks up the newest release on GitHub, downloads the archive pinned to that release's commit, verifies it, backs up your folder, carries your settings across and runs the doctor:
python ~/.claude/skills/model-orchestration/update_check.py --apply # --dry-run to preview(Windows: %USERPROFILE%\.claude\skills\model-orchestration\update_check.py.) You do not have to
remember it: once a day the kit checks for a release — at session start and after a real round —
and prints exactly that command when there is one.
Automatic updates
Section titled “Automatic updates”1.119.0 adds no new install step and no new environment surface. The free-voice in-round rule reads optional integer env overrides (ORCH_FREE_INROUND_WINDOW, ORCH_FREE_INROUND_RETRY_429, ORCH_FREE_INROUND_RETRY_5XX) if present; nothing changes if they are not set. The four chat panel channels (claudeaiseat, cldesktopseat, cgptseat, cgptdeskseat) ship with enabled: false — they do nothing until an overlay turns them on, and their drivers are the author's local-only tooling.
--apply, the installer and upgrade.py switch automatic updates on — unless you switched
them off before (that choice is kept), or you pass --no-auto-update. From then on:
- Installer / manual copy: a SessionStart hook in
~/.claude/settings.json(it runs the Python that installed it, by absolute path) checks once a day and, when a release is out, installs it at session start — download, verify, back up, keep your settings — and saysupdated automatically: X -> Y. A failure is reported once and retried the next day; the log is~/.claude/model-orchestration.updates/auto-update.log. - Plugin install: Claude Code's own auto-update for the
review-channelsmarketplace — theautoUpdateflag on its entry in Claude Code'ssettings.json, the same switch as/plugin→ Marketplaces →review-channels→ Enable auto-update. Claude Code installs a release during a session (after your first message, within ~10 minutes) and showsPlugin updated; the next session runs it.DISABLE_AUTOUPDATER=1,DISABLE_UPDATES=1orCLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1switch that pass off unlessFORCE_AUTOUPDATE_PLUGINS=1is also set.
python <your install>/update_check.py --status # one VERDICT: OK or FAIL, each FAIL with its fixpython <your install>/update_check.py --auto-update off # or onBackups of a script install no longer sit next to it in ~/.claude/skills/ (Claude Code loaded each
one as another copy of the skill): they go to ~/.claude/model-orchestration.backups/, newest three
kept. --dry-run downloads and verifies, then shows
the full upgrade report without changing anything; --tag v1.62.0 picks a specific release. For a
plugin install the same command goes through claude plugin update (restart the session after);
for a git checkout it prints the git pull instead of overwriting anything.
What it trusts, stated plainly: the archive comes over TLS from github.com, pinned to the commit the
GitHub API named for the tag, and is refused unless it holds one top-level folder, the skill
subtree, every required file, a VERSION equal to the tag, and no path that escapes the folder.
There is no signature — the same trust as git clone.
The manual way still works: download the new version (git pull, or the Code → Download ZIP
button) and run its upgrade.py from the unpacked folder:
python plugins/model-orchestration/skills/model-orchestration/upgrade.py --dry-run # previewpython plugins/model-orchestration/skills/model-orchestration/upgrade.py # applyEither way the report names the version you had and the version you are getting, which channels
are new and which are gone, and which of your settings it carried across; the old folder is copied
to ~/.claude/model-orchestration.backups/<folder>.bak.<timestamp> (outside skills, see above; a
non-default folder keeps <folder>.bak.<timestamp> beside it) before anything is written, and the
doctor runs at the end.
A plugin install that Claude Code updates by itself (auto-update switched on) needs none of this from 1.7.0 onward — your settings live outside the folder it replaces.
🔴 If you are still on 1.6.x, read this before you update. Your channel settings are in
channels.json, inside the folder an update replaces, and an auto-updating plugin replaces it with nobody running anything — so nothing can carry them across on that path. Claude Code keeps the previous plugin version in its cache for 14 days after an update, so the rescue is real but not permanent: runupgrade.pyonce (it scans that cache and offers to bring the settings over), or simply write the one line of JSON into your settings file yourself. After that, updates stop being able to lose anything.
To an AI assistant asked to "update this to the new version" (or «обнови kit»): run
update_check.py --applyfrom the installed folder, and report the version line it prints. Do not copy files over the old folder, and do not install fresh. If the install has noVERSIONfile it is older than 1.7.0, which is expected — that release is where a version string first reached an installed copy at all.
Why this needed a script
Section titled “Why this needed a script”Everything you configure — which channels are on — used to live in channels.json, inside the
folder that an update replaces. So the documented workflow was: read this file, edit
channels.json to enable a channel, and then lose that edit at the next update, silently, whether
you used the installer, copied the files by hand, or let the plugin update itself.
From 1.7.0 your settings live in a file outside the skill folder, which nothing can overwrite:
| Your system | Your settings file |
|---|---|
| Windows | %USERPROFILE%\.claude\model-orchestration.local.json |
| macOS / Linux | ~/.claude/model-orchestration.local.json |
{ "channels": { "goog36flash": { "enabled": true }, "ornemotron3ultra": { "enabled": false } }}What it may change (1.8.0 widened this a lot)
Section titled “What it may change (1.8.0 widened this a lot)”At the path in the table above — your own home directory — it may change anything a channel or a tier has, and it may add whole new ones. Repoint a model, add a vendor, define your own tier:
{ "channels": { "mylocal": { "_new": true, "kind": "openrouter", "label": "My Channel", "cost": "metered", "model": "vendor/model-id", "models": { "vendor/model-id": { "label": "My Model", "data_policy": "metered API" } }, "enabled": true } }, "tiers": { "max": { "gemini_thinking_level": "low" } }}"_new": true is required when you are adding something, so that a misspelt name fails loudly
instead of quietly becoming a second channel.
Since 1.65.0 you no longer have to hand-edit the JSON to add a channel — one command does it:
python orchestrate.py --new-channel codex59nova:codex:gpt-5.9-nova-ultraNAME:KIND:SLUG. Writes an atomically-renamed block to the file above with _new: true,
enabled: true, a models table keyed on the slug, and a [HYPOTHESIS] label so the plan
shows the vendor terms are UNKNOWN until you replace them. Refused when the NAME collides with
a shipped channel (pick another), when the KIND is unknown to the router, or when your overlay
is redirected via MODEL_ORCH_LOCAL (adding a channel needs the home path — same rule the
router enforces when it reads the file). The block is a normal overlay entry; edit it by hand
afterwards to change label, data_policy, or to add fields the vendor needs (endpoint,
provider, etc). For a one-off try of an unlisted slug on an existing channel — no overlay
write — use --set <chan>=<slug> instead: accepted for network-API kinds as a HYPOTHESIS
(paid 4xx is the honest test); refused for CLI-fixed kinds (agy/opencode/hermes) with the
alternative named.
1.7.0 refused all of that, and it was wrong to. Your settings file and channels.json have the
same write permissions — anything able to change one can change the other — and channels.json was
the file nothing announced at run time. Refusing model here never stopped anybody; it pushed the
change into the quieter file. Both are now reported, in the plan, before a penny is spent.
Two conditions, and neither is about trusting you less.
1. A transport change has to be accepted once. Repointing a model, changing provider or
kind, adding a channel — anything that decides where a document goes — is applied, printed in
the plan with a 🔴, and then a paid round refuses to run until you have said once:
python routing.py --accept-settingsIt prints exactly what you are accepting and records it. Change any of it later and the refusal
comes back. Why: this file survives every update, so a single write to it — by a mistyped command,
or by an AI assistant acting on a poisoned instruction — would otherwise redirect a channel
forever, silently. Quiet settings (enabled, how hard it thinks, how much it may read) never
need this. If you want a permanent transport change without the step, put it in channels.json:
that is now reported field by field in the plan on every run, and an update will replace it, which
is the right property for that class.
2. A relocated settings file may only set the quiet knobs. If you point MODEL_ORCH_LOCAL at a
different path, only these are accepted from it: enabled, effort, reasoning,
thinking_level, max_tokens, fetch_tool, web, timeout, label, notes. A project's own
.claude/settings.json can set environment variables for the sessions run inside it, so a
repository you cloned can choose that path. Your home directory is yours. Move the file to the
default path and everything is accepted, subject to condition 1.
(cost is not on that list, though it looks cosmetic. It decides whether the plan warns you
that a channel is expensive, and which channels --ask fans out to.)
Five things make it hard to shoot yourself with:
- Every run prints the file's path and each value it changed, in the resolved plan, whether or not you were thinking about it. Changes to a transport are marked 🔴. A settings file you forget you wrote is worse than none.
- The plan also reports edits you made to
channels.json, by field, for the same reason — and reminds you that the next update will take them. - A name that is not a channel is refused, loudly, with the list of real ones. A typo in a config file otherwise looks exactly like a channel that is off for some other reason.
- A channel that was renamed still resolves, through the same alias table
--onlyuses. A strict "unknown name" refusal plus a rename upstream would otherwise stop the tool starting, on upgrade day, for people who did nothing wrong. doctor.pynames the fields you edited inchannels.json, and points atpython upgrade.py --migrate, which moves those edits out for you and changes nothing else.
After installing, on every machine, once
Section titled “After installing, on every machine, once”python ~/.claude/skills/model-orchestration/doctor.pyOn Windows use %USERPROFILE%\.claude\skills\model-orchestration\doctor.py.
It checks Python, every file, whether each script compiles, the model registry, whether your API key is present (it reports presence and length only, never the value), both command-line tools and their live versions, permissions, and the privacy gate.
Its last line is your exact run command with the real path already filled in. Do not copy a path out of any document, including this one — ask the doctor. Paths in documentation go stale; a probe cannot.
Verifying the behaviour, not just the install
Section titled “Verifying the behaviour, not just the install”python ~/.claude/skills/model-orchestration/selftest.pyRuns about fifty behavioural checks — partial installs degrade instead of crashing, channel selection is obeyed exactly, and nothing secret-shaped can reach a log or a console. Costs nothing and contacts no vendor. Worth running after any upgrade, and after any change an AI assistant makes for you.
Setting up the channels
Section titled “Setting up the channels”You need at least one channel — the tool runs whatever is available and tells you exactly what it skipped and why. A missing key or a missing CLI is a normal, non-fatal condition, not an error.
No key at all gets you --ask. The opencode CLI runs two free voices (and a free fallback) with no API key
and no account — npm install -g @opencode/cli and you are done. (The free Spark 1.3 voice it
used to give is gone since 2026-10-17.) For a full review panel,
OPENROUTER_API_KEY alone reaches most of the model families here, which is enough for the
disagreement this tool exists to produce. Everything else is optional.
| what it needs | what that unlocks | cost |
|---|---|---|
the opencode CLI v2 (npm install -g @opencode/cli) |
one free voice, in the cheap panel and in --ask (not on a day the free allowance was refused): ocmimo26flashfree (MiMo v2.6 Flash). ocglm53flashfree (GLM 5.3 Flash) and ocdeepseekv41flashfree (DeepSeek V4.1 Flash) start only when named; the Nemotron copy retired in 1.116.1, and ocstep5previewnv (Step 5 Preview on your NVIDIA key) runs only when named since 1.118.0. The free Spark 1.3 (ocspark13free) is gone since 2026-10-17. With an OpenCode Go plan, since 1.120.0, opencode also runs both Muse Spark Contributor voices (ocspark12go, ocspark13go) on the plan, once a workspace owner or admin turns on «Allow paid endpoints that train on request data» in the OpenCode Console, Settings › Privacy (README, «Spark on an OpenCode Go plan») |
free; the Go plan is an opencode subscription |
NVIDIA_NIM_API_KEY (free, below) |
two free voices on NVIDIA's own endpoint: DeepSeek V4.1 Flash (nvdeepseekv41flash, since 1.118.0; GLM 5.3 Flash, nvglm53flash, is its fallback) and Nemotron 3 Ultra (nvnemotron3ultra); Step 5 Preview, the voice of 1.114.0–1.117.2, by name only |
free |
nvkimik3 ships switched off: NVIDIA retired its free Kimi K3 on 2026-10-15. Kimi K3 answers in the standard panel through OPENROUTER_API_KEY (kimik3) |
— | |
OPENROUTER_API_KEY |
the largest group — Kimi, Qwen, Gemini, MiMo, Grok, GLM, DeepSeek, the Muse Spark voices where opencode has no Go plan and a free NVIDIA Nemotron, all on one account | metered per token, plus per web search; the Nemotron model itself is free |
MODEL_API_KEY |
the Muse Spark voices reached directly from Meta | metered API |
| the Codex CLI, signed in | codex |
your existing subscription |
| the Claude Code CLI, signed in | cclopus46 — Claude Opus, off by default; runs with permission prompts bypassed, see its section below |
your existing subscription |
| the Antigravity CLI, signed in | the agy Gemini channels |
free on a personal Google account, with weekly limits; Google AI plans raise them |
| the Grok Build CLI, signed in | grokbuild — Grok, and it opens pages itself |
an xAI account; SuperGrok plans raise the limits |
GEMINI_API_KEY |
Gemini on Google's own API — the best-grounded channel here, and the only one whose citations carry character spans. Off by default, see below | metered, free tier available |
XAI_API_KEY |
Grok on xAI's own API — adds X/Twitter search and reports the dollar cost of each call. Off by default | metered |
MIMO_API_KEY |
MiMo on Xiaomi's own API — its search opens whole pages instead of returning excerpts. Off by default | metered |
🔴 Do not count the channels from this file. The number is whatever channels.json enables,
and it has changed most weeks. Run python routing.py — it prints the live list and spends
nothing.
Direct-vendor alternatives to OpenRouter (off by default)
Section titled “Direct-vendor alternatives to OpenRouter (off by default)”The models below are reachable two ways: through OpenRouter (on by default here) or through the vendor's own API (off by default). They are not duplicates — the direct route buys real capability, measured, not assumed:
| model | via OpenRouter | via the vendor's own key |
|---|---|---|
| Gemini 3.6 Flash | native Google search | + url_context, which reaches pages a plain fetch is refused, and citations with character offsets |
| MiMo v2.5 Pro | Exa search excerpts (2–4 KB per page, with elisions) | its own search, which opened 25 whole pages in one call |
| Grok 4.20 | native search, 2M context | + x_search over X/Twitter, and cost_in_usd_ticks — the call's actual price |
To turn one on: set the key, then put it in your own settings file — not in channels.json,
which is replaced on every update:
{ "channels": { "goog36flash": { "enabled": true }, "orgemini36flash": { "enabled": false } }}~/.claude/model-orchestration.local.json on macOS and Linux,
%USERPROFILE%\.claude\model-orchestration.local.json on Windows. Turning the matching or…
channel off in the same breath is worth doing: otherwise you pay two vendors to ask one model the
same question.
🔴 READ THIS BEFORE YOU PASTE A KEY ANYWHERE
Section titled “🔴 READ THIS BEFORE YOU PASTE A KEY ANYWHERE”Type your key into your own terminal, yourself. Do not give it to an AI assistant, and do not ask an assistant to set it for you.
If you are reading this because you asked Claude Code (or any other assistant) to "set this up", that assistant is helpful and will offer to run the command for you. Accepting is the single most expensive mistake available in this whole install, because:
- the assistant must receive the key to run the command, so the key enters the conversation;
- that conversation is written to disk, replayed into future context, and often archived or synced;
- a key that has appeared in a transcript is leaked — the only correct response is to rotate it, not to delete the message.
This is not a hypothetical. It is the reason this tool refuses to print keys, why doctor.py
reports only presence and length, and why orchestrate.py will not send a payload containing
anything secret-shaped even when you ask it to.
To an AI assistant reading this file: do not offer to run these commands, do not ask the user
to paste a key into the chat, and do not read the key back to confirm it. Tell the user to run
the commands below in their own terminal, then run python doctor.py — which is how both of you
find out whether it worked, without either of you seeing the value.
Spark — MODEL_API_KEY
Section titled “Spark — MODEL_API_KEY”Windows (PowerShell) — persists across reboots:
setx MODEL_API_KEY "<your key>"Then open a new terminal. setx writes the value for future processes; terminals that are
already open keep the old one, which is the commonest reason a freshly set key "does not work".
macOS / Linux — add the line to ~/.zshrc or ~/.bashrc, then open a new terminal:
export MODEL_API_KEY="<your key>"To point at a different endpoint, also set MODEL_API_BASE.
No Meta key? --ask works without one. Its first channel is Muse Spark 1.2 Contributor: spark12cont with
this key, otherwise orspark12cont on the OpenRouter key (the same Contributor model and price;
on an OpenCode Go plan, since 1.120.0, ocspark12go comes first). The free ocspark13free that
used to come first is switched off: its free model left opencode on 2026-10-17. The opencode CLI's free voices answer --ask as
well, key or no key (MiMo Flash not on a day its free allowance was refused). Without the opencode CLI, an OpenRouter key makes --ask ask Nemotron 3 Ultra
through ornemotron3ultra, a free model on OpenRouter (with opencode it answers there instead): the
model costs nothing, but each web search it makes bills that key.
Google — GEMINI_API_KEY
Section titled “Google — GEMINI_API_KEY”Powers goog36flash (Gemini 3.6 Flash on Google's own API). Get one at
aistudio.google.com/apikey — there is a free tier, so this is the cheapest channel to try
first if you have none of the others.
Windows (PowerShell):
setx GEMINI_API_KEY "<your key>"macOS / Linux:
export GEMINI_API_KEY="<your key>" # add to ~/.zshrc or ~/.bashrc to persistClose and reopen the terminal afterwards — setx and shell profiles only affect new sessions.
This channel is worth having even beside the OpenRouter one that runs the same model: it uses
Google's own retrieval (google_search + url_context), which reaches pages a plain HTTP fetch
is refused, and its citations carry start_index/end_index into the answer text — so "which
sentence does this source support" is a lookup rather than a judgement. One caveat measured:
citations produced by google_search come back as vertexaisearch.../grounding-api-redirect/...
wrappers rather than the publisher's URL; only url_context citations are the real address.
Each wrapper does carry the publisher domain in its title, and the citation audit follows
the wrapper's redirect to recover the full URL — so these citations are checkable, which is not
what this paragraph said before 2026-08-08. Note the documentation's own example shows publisher
URLs in that field; the live endpoint does not. A response example in vendor docs is an
illustration, not a schema.
NVIDIA — NVIDIA_NIM_API_KEY
Section titled “NVIDIA — NVIDIA_NIM_API_KEY”Optional and free, since 1.114.0. One key gives two voices on NVIDIA's own free endpoint, both API
calls: DeepSeek V4.1 Flash (nvdeepseekv41flash, since 1.118.0; Step 5 Preview held this seat until
1.117.2) and Nemotron 3 Ultra (nvnemotron3ultra). GLM 5.3 Flash on the same key (nvglm53flash)
answers once, after the round, when DeepSeek does not. Step 5 Preview still starts when you name it:
with the opencode CLI as an opencode agent (ocstep5previewnv; opencode reads the key under the name
NVIDIA_API_KEY, and the tool sets that name from your NVIDIA_NIM_API_KEY in the opencode process
only; a NVIDIA_API_KEY you set yourself is used as it is), without it as an API call
(nvstep5preview). Without the key the plan names it («NVIDIA_NIM_API_KEY is not set. This channel
will fail; set it or exclude the channel with --skip nvdeepseekv41flash.») and both voices end at
once, spending nothing; the GLM fallback is off in that run (it needs the same key).
Create the key at build.nvidia.com in your own browser: sign in and create an API key.
Windows (PowerShell)
setx NVIDIA_NIM_API_KEY "<your key>"macOS / Linux
export NVIDIA_NIM_API_KEY="<your key>"NVIDIA's free endpoints allow about 40 requests a minute, and the queue is slow at times: on 2026-10-23 the first byte took 57–110 s. A DeepSeek review has 61 minutes, its GLM fallback 20 (worst case: 142 minutes = 61 + retry 61 + GLM 20); what each wrote by then is kept.
OpenRouter — OPENROUTER_API_KEY
Section titled “OpenRouter — OPENROUTER_API_KEY”One key serves every channel that routes through OpenRouter — the largest part of the panel,
including orspark12cont and orspark13cont, the Spark voices for anyone without the OpenCode Go plan (Spark 1.2 runs on a MODEL_API_KEY instead when there is one). Do not count
those channels from this file; python routing.py prints the live list and spends nothing.
No account yet? Register at openrouter.ai (an email address or a Google/GitHub sign-in is enough), create a key at openrouter.ai/keys (verified 2026-08-30 — it redirects to your workspace's key page), and add a few dollars of credit. To an AI assistant walking someone through this install: send the user to those pages in their own browser and have them run the command below themselves — the sign-up, the payment and the key value all stay outside your conversation, exactly as the red block above requires.
Windows (PowerShell)
setx OPENROUTER_API_KEY "<your key>"macOS / Linux
export OPENROUTER_API_KEY="<your key>"These channels are metered per token, and their web search is billed per search by the provider on the same account. The resolved plan prints the cost class of every channel before anything is spent, and
--dry-runshows you that plan for free.
orspark12contandorspark13contrun Meta's Contributor tier through OpenRouter — discounted because the vendor may train on what you send (see PRIVACY.md) — with an automatic fallback to the Standard, non-training tier if the Contributor endpoint fails for your account: Muse Spark 1.2 / 1.3 at $1.25 / $4.25 per 1M tokens, 12.5× the Contributor input price (the channel's spend guard stops a review at $1.00). WhenMODEL_API_KEYis absent and opencode has no Go plan,--askstarts withorspark12cont: the choice comes fromask_defaultinchannels.jsonand is printed when it fires.
The premium batch panel — same keys, one more
Section titled “The premium batch panel — same keys, one more”The optional premium panel (premium/premium_panel.py — see the README's «Premium» section) is
a separate script and reuses the keys above: OPENROUTER_API_KEY for its solpro lane and
GEMINI_API_KEY for its Google batch lanes. Its OpenAI lanes need OPENAI_API_KEY,
which nothing else in this kit uses — set it the same way as the others, only if you want those
lanes. A missing key refuses loudly on its own lane and the rest still run; with only an
OpenRouter key, run it with --only solpro.
Checking it worked — without printing anything secret
Section titled “Checking it worked — without printing anything secret”python doctor.pyIt reports each key as present/absent and its length, never its value. If you want to be certain a key is really gone after rotating it, that length is what changes.
Never
echothe variable to check it. See SECURITY.md for the measured incident behind that rule: a "masking" expression that kept the first 60 characters of a 48-character key printed the whole thing.
opencode CLI (two free voices and a fallback)
Section titled “opencode CLI (two free voices and a fallback)”Requires Node.js (v18 or newer). If npm is not on your PATH, install Node.js from
nodejs.org first — tick "Add to PATH" on Windows; on macOS brew install node; on Linux use your package manager.
npm uninstall -g opencode-ai # only if you have the old package: it is opencode v1npm install -g @opencode/cliopencode --version # must print 2.xThe package opencode-ai is the v1 line (1.19.2 on 2026-10-16). The harness speaks v2 (model#variant, run --standalone); on v1 those calls fail at once and the harness says so.
No key, no account, no sign-in for the opencode/ models whose names end in -free. This
installs the opencode CLI from opencode.ai; the cheap panel's two free
voices run through it: MiMo v2.6 Flash (ocmimo26flashfree) and Nemotron 3 Ultra
(ocnemotron3ultrafree). GLM 5.3 Flash (ocglm53flashfree) is MiMo Flash's fallback and
DeepSeek V4.1 Flash (ocdeepseekv41flashfree) starts only when named; all four ran live on
2026-10-18. They are free relays: one can disappear — opencode stopped serving its free Muse
Spark 1.3 on 2026-10-17 (provider.no-route) — and MiMo, GLM and DeepSeek draw on one daily
allowance of requests per network address, refused with provider.quota once it is used up,
until 00:00 UTC (the README's cheap panel section says what the harness then does). The run says
so for that voice and what to do; opencode models is the live list.
--ask goes to the free voices too, except one whose free allowance was refused earlier this
UTC day; with every key and CLI it asks 4 channels (Muse Spark, MiMo v2.6 Flash, Nemotron 3 Ultra,
DeepSeek V4.1 Flash), 3 on such a day. Since 1.115.1 its line «--ask goes to N channel(s): …» is read
off the final plan: it names who starts, and after «not started:» who does not and why («quota
group «opencode-free» refused earlier today», «one voice with never_read, and
--ask prints one line «ANSWER [ocspark12go on an OpenCode Go plan (since 1.120.0), otherwise one that needs a key: spark12cont (MODEL_API_KEY) or
orspark12cont (OPENROUTER_API_KEY), both paid. On a machine with neither, that line fails with
«MODEL_API_KEY not set»; that is not fatal, and the free voices still answer.
If the CLI is absent, the others work the same as before. The two free voices stay in the plan as
[RUN ] and each fails at dispatch with «binary not found», spending nothing (MiMo Flash's fallback
is OFF in that run; say "without opencode" to leave them out), and Nemotron 3 Ultra then answers the standard panel through OpenRouter
(ornemotron3ultra).
doctor.py reports whether the CLI is found and where. If yours is installed in an unusual
location, set OPENCODE_BIN to the full path.
Kimi Code CLI (retired: free Kimi K3 through NVIDIA)
Section titled “Kimi Code CLI (retired: free Kimi K3 through NVIDIA)”Not needed since 1.112.0. From 1.106.0 to 1.111.5 the cheap panel had a free Kimi K3 voice,
nvkimik3: the Kimi Code CLI (github.com/MoonshotAI/kimi-code)
talking to NVIDIA's free endpoint with an NVIDIA_NIM_API_KEY. NVIDIA retired that model on
2026-10-15 at 08:00 UTC: the endpoint answers HTTP 410 «end of life», on every key.
Since 1.112.0 nvkimik3 ships switched off and the cheap panel has no Kimi voice. Kimi K3 answers in
the standard panel through OpenRouter (kimik3, about $1.38 a run), and «only kimi» / --only kimi
starts that channel alone. If you installed the CLI for the free route, nothing uses it any more;
keep the NVIDIA_NIM_API_KEY: DeepSeek V4.1 Flash (Step 5 Preview until 1.117.2) and Nemotron 3 Ultra use it (NVIDIA). If you start nvkimik3 by name anyway, the run ends with the
410 and names the cause: the vendor has retired the model.
Muse Code CLI (Muse Wonder on a Muse Code subscription)
Section titled “Muse Code CLI (Muse Wonder on a Muse Code subscription)”Optional, since 1.111.0. With it, Muse Wonder (mcwonder10cont) runs on your Muse Code
subscription instead of a per-token key. Since 1.113.0 Wonder is in no panel: it starts only when
you name it ("only wonder", "add wonder", «вондер», or a channel's own name).
- The CLI and a sign-in. Install Muse Code from Meta's documentation
(dev.meta.ai/docs/muse-code) and sign in once with the
account that holds the subscription:
muse login. The plan must include Muse Wonder. - Check it with one real call (it uses a little of your plan):
python ~/.claude/skills/model-orchestration/orchestrate.py --ask "Reply with the word OK" --only mcwonder10cont(a plugin install has the same file in its plugin folder). It prints the answer,OK; if Muse Code is not signed in, the run says so and how to fix it. A command that only resolves the model name proves nothing:muse model-profile showprints a profile for any id, an invented one too. On Windows,muse sandbox windows checkshould printstatus=ready(the CLI's shell sandbox); if not,muse sandbox windows setup.
The harness finds the binary through MUSE_BIN, then (Windows) the muse-bin-<version>.exe named in
%LOCALAPPDATA%\Programs\muse\.muse-version, then muse on PATH. It never passes an API key to
this CLI: with META_API_KEY set, Muse Code bills that key instead of the plan, so the harness keeps
it out of the CLI's environment. The run has no file writes, no shell and no approval prompts (--disable-approval --disable-write --disable-shell); it is offered only the built-in web_search and web_fetch (-c run.toolset): the MCP servers in
your Muse Code settings still start, but the reviewer cannot call them.
Without Muse Code, "only wonder" / «вондер» stops before paying and names the paid Wonders:
wonder10 (MODEL_API_KEY, pay-as-you-go) and orwonder (OpenRouter, stops at $5 a run); name one
to run it. To seat Wonder in the standard panel again on your subscription, as before 1.113.0, put
this in ~/.claude/model-orchestration.local.json:
{ "channels": { "mcwonder10cont": { "explicit_only": false } } }The registry also holds Muse Code channels for Muse Spark 1.3 (mcspark13, its fallback
mcspark12) and Wonder Standard (mcwonder10). They ship switched off (the kit's Spark voices
run through opencode's Go plan or an API) and start when you name one or switch it on in your settings file; musecode /
«muse code» is a group word, and like every group it runs only the members that are on. Since
1.113.8 every Muse Code call passes --contributor-mode from the channel's model id: with contributor mode on in
Muse Code's own settings, a request for the Standard muse-spark-1.3 was answered by the
Contributor model (measured 2026-10-22).
Codex CLI
Section titled “Codex CLI”Install the Codex CLI and sign in with an account whose plan includes it:
codex --versioncodex loginNo key needed — it uses your subscription. If you do not have one, run with --skip codex.
Claude Code CLI (Claude Opus — off by default)
Section titled “Claude Code CLI (Claude Opus — off by default)”Install the Claude Code CLI and sign in with a Claude subscription:
npm install -g @anthropic-ai/claude-codeclaude auth loginclaude auth statusThe channel is cclopus46 and it ships disabled. Run it for one round with --only cclopus46,
or set "enabled": true on it in channels.json. No key is used: the channel removes
ANTHROPIC_API_KEY and ANTHROPIC_AUTH_TOKEN from the CLI's environment on purpose, because with
either set the CLI bills that key instead of your subscription.
🔴 Read before enabling. The CLI is launched with --permission-mode bypassPermissions: every
built-in tool — shell, file edits anywhere on your machine, web — and every MCP server configured
in your Claude Code run without a permission prompt, on a brief that is untrusted input. A
headless run cannot prompt, and without that mode the reviewer's fetches and commands were silently
denied (measured 2026-09-11). Your permissions.deny rules and hooks still apply in this mode;
SECURITY.md has the full picture. If that is not acceptable on the machine you run
this from, leave the channel off — the panel does not need it.
Grok Build CLI
Section titled “Grok Build CLI”grok --version # expect: grok 1.0.4 (…) [stable] or newergrok loginNo key needed — it authenticates with a subscription session, and the channel carries no API key
at all. If you do not have a Grok subscription, run with --skip grokbuild; a missing CLI is
reported and skipped, not fatal.
🔴 The binary is not on PATH on Windows. The harness looks in %USERPROFILE%\.grok\bin
before giving up, so a default install is usually found without configuration. If yours is
elsewhere, set GROK_BIN to the full path. python doctor.py prints which CLIs it found and
where.
What this channel is, and what it is not. It is an agentic coding CLI pointed at a review brief, and that is the whole difficulty: it runs the tool loop itself, opens pages itself, and takes several minutes. In exchange it produced the longest and most-cited answer of any channel in the panel it was first enabled for. Three things are configured for you and should not be undone:
--allow WebFetch. Without it the CLI silently denies any page fetch outside its own vendor's domain, and one denied tool discards the whole turn — you get a couple of hundred bytes of planning narration andstopReason: cancelled, with nothing saying why. Note the spelling:--allowwantsWebFetch,--toolswantsweb_fetch, and the wrong one is accepted while granting nothing.--disallowed-tools search_tool,use_tool. These are the CLI's gateway to whatever MCP servers you have configured, and--toolsdoes not bound them. They are removed rather than granted because a review brief is untrusted input and your MCP servers may hold credentials. Web search and page fetching are unaffected — they are built in, not MCP.- No file tools.
--cwdbounds where the agent starts, not whatread_filecan open, so the read tools are not granted at all.
Imports from Claude Code, Cursor and Codex switched off (since 1.111.2) — plugins excepted. Out
of the box Grok Build loads the MCP servers in your ~/.claude.json, your Claude Code skills, rules
(your global CLAUDE.md), agents and hooks, and the same parts of Cursor and Codex. For a review that
meant your own instructions went to xAI with every brief, and on 2026-10-13 one MCP server imported
from ~/.claude.json hung every run until the 40-minute timeout. The harness starts Grok with
GROK_CLAUDE_MCPS_ENABLED=0 and the 17 sibling switches
(GROK_<CLAUDE|CURSOR|CODEX>_<SKILLS|RULES|AGENTS|MCPS|HOOKS|SESSIONS>_ENABLED=0). Ten of them are in
xAI's settings reference; the SESSIONS and CODEX
ones exist only in the binary (measured on grok 1.0.52). Since 1.111.3 every run first asks
grok inspect --json (no model call, under half a second) whether this grok honoured them, and the
run log says «imports verified off». Since 1.111.4, if a newer grok ignores a switch and would load
an imported item, Grok is not started and the brief is not sent: the error names the surfaces, and
[compat.claude] / [compat.cursor] in ~/.grok/config.toml turn them off. A switch that is on with
nothing behind it is a 🔴 note on the run. Still loaded, because Grok has no switch for them: Claude Code plugins (with any
skills, hooks or MCP servers they bring) and the files in ~/.grok/rules/ (the preflight lists them).
A reviewer keeps Grok's own config: the MCP servers in ~/.grok/config.toml, your shell logins,
Grok's own skills. In --task mode Grok works in the task folder and reads that project's
AGENTS.md, as it would for you. If a run still times out, the error quotes Grok's own session
record (found by the --session-id the harness gives each run): the tool that started and never
finished, and the MCP servers that failed at start. Your own
grok sessions are unaffected; to keep them from importing too, set GROK_CLAUDE_MCPS_ENABLED=0 (and
any of the others) in your user environment, or mcps = false under [compat.claude] in
~/.grok/config.toml.
Antigravity CLI (Gemini)
Section titled “Antigravity CLI (Gemini)”agy --versionThen the one post-install step that is not optional:
python ~/.claude/skills/model-orchestration/patch_agy_permissions.py --dry-run # previewpython ~/.claude/skills/model-orchestration/patch_agy_permissions.py # applyWhy this is mandatory, measured 5 runs out of 5: running headless, this CLI cannot show a
permission prompt, so any tool still set to "ask" is auto-denied — and one denial silently
throws the entire run away. You get an empty answer, a status of SUCCESS, and exit code 0,
after dozens of successful tool calls. Nothing in the exit code, the status field or the elapsed
time reveals it. With the patch applied, the same document went from an empty answer to 49 tool
calls.
The script edits one settings file, is additive and idempotent, backs up before writing, and has
--revert. It also blocks metered crawling tools that bill per page with no ceiling — which is
why, for this CLI, the answer to a permissions problem is never --dangerously-skip-permissions.
(The Claude Code CLI channel is the deliberate exception — its own section above says what that
costs.)
doctor.py re-checks this on every run and tells you if it has been reverted.
The plain gemini CLI is not a substitute — on a personal-tier account it fails with an
ineligible-tier error.
Uninstall
Section titled “Uninstall”Delete the folder:
| Your system | Delete |
|---|---|
| Windows | %USERPROFILE%\.claude\skills\model-orchestration\ |
| macOS / Linux | ~/.claude/skills/model-orchestration/ |
If you installed as a plugin, /plugin uninstall model-orchestration@review-channels.
Nothing is written anywhere else — no registry keys, no services, no scheduled tasks. The only
other thing it creates is the output folder for each review, wherever you pointed --out.
If you ran patch_agy_permissions.py, it left a timestamped backup of the file it changed
alongside the original, and --revert restores it.