Installation and harness setup
Downshift by Tiago Vilas Boas. Updated . Source on GitHub · Versão em português
Downshift by Tiago Vilas Boas https://github.com/tiagovilasboas/downshift
Canonical guide for humans and coding agents installing Downshift on a user machine.
Related: config.md · session-models.md · harness-matrix.md · examples/README.md · Portuguese guides
Harness status: Claude Code is Completed; Codex and Cursor are Functional; Antigravity and KiroCrew are Experimental (see Experimental harnesses); OpenCode is Beta (adapter and route wire in this repository, plugin wiring in development). The sections below follow that order. Definitions and evidence: harness-matrix.md.
Health check
After install:
downshift doctor # version, state dir (~/.downshift), catalog source
downshift try "rename userId in auth.ts" claude-code
Optional adapter smoke (from a clone of this repo):
./examples/run-all.sh
Availability discovery and usage quota are configured separately. See session-discovery.md for model listings and quota.md for the native Claude Code statusline bridge, Codex transcript collection, freshness, and required-quota mode. A successful discovery is not proof of included credit, and installing a bridge is not proof that it has received a live usage observation.
Quickstart — zero to working hook in 2 minutes
Step 1 — Install (macOS / Linux):
curl -fsSL https://raw.githubusercontent.com/tiagovilasboas/downshift/main/install.sh | sh
The installer downloads the release archive and checksums.txt from the same GitHub release and verifies the sha256 before anything is installed; a mismatch or a missing checksum file stops it. DOWNSHIFT_SKIP_CHECKSUM=1 installs unverified and says so. Releases are also on the Releases page with the same checksums.txt.
Build provenance (optional). Releases built by the release job carry a GitHub build-provenance attestation. After the sha256 check, if the gh CLI is installed, install.sh runs gh attestation verify <archive> --repo tiagovilasboas/downshift and prints Build provenance verified. It never blocks on this by default: with no gh, no gh auth login, or a release older than the attestations, it prints a warning and continues on the sha256 alone. DOWNSHIFT_REQUIRE_ATTESTATION=1 makes any outcome other than "verified" an error that installs nothing; DOWNSHIFT_SKIP_ATTESTATION=1 skips the check. The installer ends by saying what was proven: "sha256 and GitHub build provenance" or "sha256 only" (an intact download, not proof of who published it). It accepts only a strict vMAJOR.MINOR.PATCH[-prerelease] version, downloads with curl --proto '=https' --tlsv1.2, and installs through a temporary name plus a rename. By hand: gh attestation verify downshift_<ver>_<os>_<arch>.tar.gz --repo tiagovilasboas/downshift.
install.sh still treats a non-flag argument as the release version. --explicit-upshift may come before or after that version. It writes "explicit_upshift": true into session-models.json without dropping harness lists that are already there. The same switch at runtime is DOWNSHIFT_EXPLICIT_UPSHIFT=1. Default is off. See session-models.md.
Step 2 — Preview, then wire the hooks:
downshift setup --dry-run # prints the diff for each harness found; writes nothing
downshift setup # asks first, backs up the files, then merges the hooks
For Claude Code, setup wires PreToolUse (matcher Agent|Task), PostToolUse, PostToolUseFailure and SubagentStop, sets the statusline when none exists, and sets "model": "opusplan" (Opus in plan mode, Sonnet for execution), "permissions": {"defaultMode": "plan"} (sessions start in plan mode) for keys you have not set; existing values are kept, and --no-session-policy skips both. Claude Code's built-in You should know mod ("enabledPlugins": {"cc-plugin-you-should-know@builtin": true}) is opt-in because each check is a billed background request to the session's model: setup prints a suggestion, and downshift setup --you-should-know writes it (/plugin disable cc-plugin-you-should-know@builtin turns it off) (main session policy). It also finds Cursor, Codex, Antigravity and KiroCrew, keeps unrelated keys and existing hooks, and is idempotent. downshift setup --undo restores the latest backup. KiroCrew (Experimental) has a dedicated setup command that generates its wrapper and registers it; see KiroCrew. The manual JSON is in Manual setup below.
Step 3 — Check the classifier on your own prompts:
downshift doctor # checklist: binary, hooks per harness, state, scores
downshift try "rename the userId variable" claude-code # what would be picked, and why
Step 4 — Confirm it's working. Open a new Claude Code session and ask it to spawn a subagent for a trivial task. You'll see a line like this in stderr:
downshift: TRIVIAL task → downshift to claude-haiku-4-5
That line means the hook fired and emitted a rewrite before the subagent started. Whether the harness applied it is a separate fact; downshift stats separates rewrite_emitted from honored, and the matrix says what is verified per plan.
Verify the rewrite was honored (important for beta exit validation):
- Ask Claude Code: "spawn a subagent to rename the userId variable in auth.ts"
- Open Claude Code's internal logs or monitor (check the subagent's actual model in the UI)
- Confirm it started on haiku, not the session's default frontier model
- This proves the hook's rewrite was not just emitted—it was actually applied by the harness
Step 5 — See what happened. downshift stats --days=1 shows decisions and tokens saved; downshift session --tree shows this session's subagents as a tree. In Claude Code, the plugin exposes /downshift:status; the optional manual alias /downshift is in The /downshift command.
Step 6 — (Optional) Watch it live. downshift watch redraws the current session's subagent tree (model, end status, tokens, why) every second, for every harness; downshift session --tree prints it once, and /downshift:status or /downshift shows it inside Claude Code. The older floating widget is still available: dsmon — live monitor widget.
Manual setup (Claude Code)
Use this instead of setup if you prefer to edit ~/.claude/settings.json yourself. The PreToolUse matcher is Agent|Task. Claude Code's Task schema accepts only family names (haiku, sonnet, opus, fable) in model. Downshift writes the catalog's family name for you and leaves the spawn alone when a model has none.
{
"hooks": {
"PreToolUse": [
{ "matcher": "Agent|Task", "hooks": [{ "type": "command", "command": "downshift claude-code" }] }
]
}
}
For child status and real usage, also add PostToolUse and PostToolUseFailure (downshift claude-code-post-tool-use) and SubagentStop (downshift claude-code-subagent-stop), as setup does.
Testing locally before connecting the hook
Before adding the hook to Claude Code, test the classifier and hook logic locally:
Test 1 — Classifier only (terminal):
downshift try "rename the userId variable" claude-code
downshift try "rearchitect the payment flow" claude-code
downshift try "implement the CSV export" claude-code
Test 2 — Full hook (JSON):
downshift claude-code < examples/claude-code-pretooluse-session.json
This mimics exactly what Claude Code will send. The output is the JSON the harness will apply, plus stderr feedback with the decision and a feedback ID for tracking.
See examples/ for sample payloads. claude-code-pretooluse-session.json carries a session_models list, so it shows a real rewrite.
⚠️ Claude Code Pro/Max/Teams/API only. Free plan has no real subagents and blocks network installs. See Plan compatibility before proceeding.
Install (Claude Code)
Status: Completed. Rewrite observed honored, usage and child end recorded, plugin and session policy (evidence, billing audit).
Build or install the single binary (no runtime, no dependencies):
# Recommended — one-liner installer (macOS / Linux, detects arch automatically)
# Installs to /usr/local/bin/downshift — no PATH changes needed
curl -fsSL https://raw.githubusercontent.com/tiagovilasboas/downshift/main/install.sh | sh
# Alternative — Go toolchain (any platform)
go install github.com/tiagovilasboas/downshift/cmd/downshift@latest
Go toolchain note:
go installplaces the binary in~/go/bin. Ifdownshift: command not found, add Go's bin to your PATH:export PATH="$HOME/go/bin:$PATH" # current session echo 'export PATH="$HOME/go/bin:$PATH"' >> ~/.zshrc && source ~/.zshrc # permanentThe
curlinstaller above avoids this by installing directly to/usr/local/bin.
Pre-built binaries for macOS (arm64/amd64), Linux (arm64/amd64), and Windows (amd64) are available on the Releases page.
Fastest path: downshift setup finds Claude Code, Cursor, Codex and Antigravity on this machine and merges every hook (plus the Claude Code statusline when none is set) into their hook files, using this binary's absolute path. It keeps unrelated keys and existing hooks, is idempotent, prints a diff, and saves a timestamped backup. --dry-run writes nothing, --yes skips the question, --undo restores the latest backup. To configure KiroCrew (Experimental), run downshift setup kirocrew --dry-run, then downshift setup kirocrew. It generates the wrapper, registers the native hook and installs the compact steering. See KiroCrew below.
Add the hook to ~/.claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Agent|Task",
"hooks": [
{ "type": "command", "command": "downshift claude-code" }
]
}
]
}
}
That is the wiring. From the next session on, every subagent spawn goes through the hook; whether the model is rewritten depends on the task (confident, no risk) and on the plan (harness matrix). On natural work most spawns are held, and that is by design.
Plugin install (Claude Code)
The same four hooks and the /downshift:status command ship as a Claude Code plugin whose root is this repository. It needs the downshift binary on PATH (step 1 above) and replaces step 2 for Claude Code only. downshift setup does this install for you when it can: it runs claude plugin marketplace add and claude plugin install downshift@downshift, then removes the Downshift hooks it wrote earlier into settings.json (the plugin carries them; keeping both would route every spawn twice). It installs the plugin only when claude is on PATH and a login shell resolves downshift to the same binary that runs setup, because the plugin's hooks call downshift by name; otherwise it keeps writing the settings hooks and says why. --no-plugin always keeps them in settings.json. The other harnesses still use downshift setup.
/plugin install downshift --marketplace tiagovilasboas/downshift # Claude Code 2.1.275+
or in two steps, /plugin marketplace add tiagovilasboas/downshift then /plugin install downshift@downshift. The plugin wires PreToolUse, PostToolUse, PostToolUseFailure, SubagentStop, and UserPromptSubmit (for Downshift Code Intelligence) from hooks/hooks.json (each entry only calls the binary), adds the downshift-setup, downshift-audit and cost-reducer skills, and the command below as /downshift:status. On Claude Code 2.1.287 or later it also loads the bundled cache-tax mod: a cold send on a context of 50k tokens or more is dropped once with its estimated rewrite price (resend to pay it, /cache-tax guard warn to only be told), and /keepwarm 90m keeps the cache warm through a break with one small model request per 50 idle minutes, which costs usage. If you already installed cache-tax@claude-code-mods, uninstall one copy; doctor warns. The marketplace also offers savvy-progress and filetree, one install each (downshift setup installs both on Claude Code; by hand: /plugin install savvy-progress@downshift, /plugin install filetree@downshift; see mods/README.md). It does not set opusplan, plan mode, the statusline or the You should know mod: those are settings, not hooks, and downshift setup writes them (You should know only with --you-should-know). Set "model": "opusplan" and "permissions": {"defaultMode": "plan"} in ~/.claude/settings.json yourself (or /model opusplan in a session), and /plugin enable cc-plugin-you-should-know@builtin for You should know (a billed request per check), if you want them without setup; see main session policy.
Use one channel. A plugin hook and a settings hook with the same command both run, so a session wired through both routes every spawn twice. downshift setup skips the hooks when it sees the plugin enabled in ~/.claude/settings.json (it still applies the session policy and statusline), and downshift doctor warns when it finds both. To switch from setup to the plugin, run downshift setup --undo first. Detection reads the user settings file only; a plugin enabled from a project's .claude/settings.json is not seen.
The /downshift command (Claude Code)
Optional when installed by hand. A slash command that shows what Downshift did in the current session (a tree of subagents, why the last one ran where it did, pause and resume):
mkdir -p ~/.claude/commands
cp commands/status.md ~/.claude/commands/downshift.md # from this repository
With the plugin it is already there as /downshift:status. Then in Claude Code: /downshift:status, /downshift:status --why, /downshift:status --pause, /downshift:status --resume. The optional manual copy above retains /downshift as a local alias. The command only runs downshift session through the Bash tool, which its allowed-tools line limits to that command. The same downshift session works in a terminal for any harness. See session-view.md.
Downshift Code Intelligence (DCI)
Downshift includes an optional code intelligence engine that discovers repository code, extracts Go AST and lexical symbols, scores relevance via BM25, and injects compact structural context (<=3,500 bytes UTF-8) via UserPromptSubmit in Claude Code.
By default, DCI is off. To enable it in a repository:
downshift dci enable active # Inject context into Claude Code prompts
downshift dci enable observe # Observe and record telemetry without injecting
downshift dci status # Show active mode and cache statistics
downshift dci doctor # Check permissions, discovery, and latencies
downshift dci benchmark # Run local performance and relevance benchmarks
To view DCI telemetry and metrics:
downshift stats --dci # Display DCI-specific prompts, injections, and latencies
See code-intelligence.md for full architectural details.
Install (Cursor)
Status: Functional. The rewrite is emitted on a real spawn; the harness applying it is unconfirmed, and it is discarded on Free and legacy Pro plans (evidence, gap diagnostic).
Cursor's hook protocol mirrors Claude Code's — same preToolUse interception, updated_input to rewrite the subagent's model. Build the binary, then add the hook to .cursor/hooks.json (project level) or ~/.cursor/hooks.json (global):
{
"version": 1,
"hooks": {
"preToolUse": [
{ "command": "downshift cursor", "matcher": "Task" }
]
}
}
The matcher: "Task" scopes the hook to subagent spawns only. Cursor watches the config and reloads it on save.
Install (Codex)
Status: Functional. The rewrite is emitted on a real spawn; the harness applying it is inferred, not observed (evidence).
Codex spawns subagents through a reserved spawn_agent tool under multi_agent_v2. You can't put a model on the provider-visible call, but a PreToolUse hook can inject the model and reasoning_effort into the tool input before the child starts — which is exactly where downshift runs.
Enable the feature flags in config.toml:
[features]
hooks = true
[features.multi_agent_v2]
enabled = true
Register the hook in .codex/hooks.json (project) or ~/.codex/hooks.json (global). The matcher covers both the Agent and namespaced spawn_agent tool names:
{
"hooks": {
"PreToolUse": [
{
"matcher": "(^Agent$|.*spawn_agent$)",
"hooks": [
{ "type": "command", "command": "downshift codex" }
]
}
]
}
}
Trust the hook before testing
Codex does not run newly configured local hooks until you review and trust their exact command. This is a Codex safety control, not a downshift error. After saving the configuration, start Codex and run:
/hooks
Review and trust the downshift codex hook, then start a new session before asking Codex to spawn a subagent. Until the hook is trusted, Codex skips it and the subagent keeps the session model unchanged.
On Codex, downshift routes two axes at once: the model tier and the reasoning effort (low for trivial work, up to high for frontier work). For a session that offers GPT-6 Sol and Luna, a trivial subagent can route from gpt-6-sol to gpt-6-luna at low effort — subject to the session allowlist and Codex accepting the rewritten input.
Downshift applies the selected reasoning effort, except when a guardrail holds the decision (the spawn keeps its requested effort) or when the decision is unconfident and does not rewrite the model. A code review is a frontier task, so a gpt-5.6-terra session routes its subagent to gpt-5.6-sol at high effort. gpt-6-astra is marked routing: "explicit_only" in the catalog. With the default explicit_upshift off, downshift does not select it. If the current subagent is already running on Astra, the model is preserved unchanged. Set "explicit_upshift": true in session-models.json, export DOWNSHIFT_EXPLICIT_UPSHIFT=1, or run install.sh --explicit-upshift to let upshift select it when it is inside this harness's credit set (or when this harness reported no credit set). Cursor claude-fable-5-1-thinking-high is the same kind of opt-in. Claude Code claude-fable-5-1 is not explicit_only; append that id to the session list to opt in. To mark any other model as explicit-only, add "routing": "explicit_only" to its entry in ~/.downshift/catalog.json (the state dir; ~/.harness-downshift/ is read only when it is the legacy dir in use).
A purely mechanical prompt such as Use a subagent to only list the .go files is routed to gpt-6-luna at low effort when that exact id is in the session allowlist.
Codex's
multi_agent_v2spawn schema is still evolving. downshift preserves the reserved fields and fails open, but pin the exact Codex build you deploy and keep an acceptance test on a real spawn.
Session allowlist
Downshift writes a model id only when that id is in the current session. The catalog supplies tier, cost, family, and effort for ids that are also in the session. It never adds an id the session does not have. If the session list cannot be determined, the hook leaves the current model unchanged.
Cursor, Claude Code, and Codex hooks send the active model, not the full picker list. Codex also sends a session ID; Downshift validates and hashes that ID before recording it, so local routing decisions can be grouped without persisting the raw session identifier. Record selectable model ids in ~/.downshift/session-models.json (legacy: ~/.harness-downshift/). A top-level harness list is a fallback; an exact sessions.<harness>.<session_id> list takes precedence. Lists are operator-curated: Downshift does not query the Codex model picker or infer account entitlement. The one exception is Claude Code: with no list from any source it infers the session from the current model (the catalog tier ladder below it, ending at that model), so it can downshift but never upshift past what the session already runs; DOWNSHIFT_INFER_SESSION=off disables this. If a hook payload includes session_models or available_models, that list is used and the file is skipped. Details and evidence: session-models.md. session-models.example.json is one Cursor session from 2026-09-27. It is an example, not the default for every user.
For Cursor quota, the hook can read a complete native export named by DOWNSHIFT_CURSOR_NATIVE_FILE offline before the canonical quota cache. A malformed or stale configured export holds routing. The external experimental extension was tested on Cursor 3.24.9 and could not access the built-in-only cursor API, even with an explicit proposed-API flag; no export was produced. Installing that extension does not currently enable collection on that build. See the runtime diagnostic. Provider membership remains authoritative; do not replace it with a fixed model list.
Experimental harnesses
Experimental (proof of concept). KiroCrew runs in policy mode only (block and retry, no in-place rewrite) and Antigravity's rewrite rides an
overwriteenvelope that is not a documented vendor field; each has one observed child on one build and neither records a child end. Every feature is still computed for them (layering rule,cmd/downshift/conformance_test.go), and the adapters, tests and hooks stay in the repository, but treat them as proofs of concept, not as supported installs. Install them only if you accept that.
Antigravity (Google DeepMind)
Status: Experimental. One observed child on desktop 2.21.1 (evidence); no child end recorded.
Antigravity executes subagents via invoke_subagent and supports arguments modification in its PreToolUse hook protocol via overwrite.
Register the hook in ~/.gemini/config/hooks.json:
{
"PreToolUse": [
{
"matcher": "invoke_subagent",
"command": "downshift antigravity"
}
]
}
When Antigravity attempts to spawn a subagent (e.g. inheriting the parent model or requesting pro), downshift antigravity intercepts the tool call, classifies the last entry in Subagents that has a non-empty Prompt (not necessarily Subagents[0]; trailing rows without Prompt are skipped), and rewrites that subagent's Model (flash_lite for mechanical tasks, flash for the mid tier) through an overwrite envelope that is not a documented vendor field, logging the routing event to ~/.downshift/events.jsonl.
KiroCrew (policy mode through the native Kiro CLI hook)
Status: Experimental. Block and retry observed once on Kiro CLI 2.29.0; no in-place rewrite (evidence).
For Kiro CLI 2.29, register Downshift through KiroCrew's persistent agent.kiro_hooks configuration. KiroCrew generates the agent spec consumed by Kiro CLI, whose native preToolUse hook runs before the tool: exit 0 allows it; exit 2 blocks it and returns stderr to the agent. There is no updated_input channel, so the adapter uses policy mode. It classifies a pending spawn with an explicit child model and:
- right tier already →
exit 0, allow silently; - confident tier mismatch →
exit 2, block with a message naming the model to respawn with (and, for a small tier, a reminder to trim context so the child fits the smaller window); - uncertain downshift, unknown model, or non-subagent tool →
exit 0, fail-open. The router never blocks a spawn on its own doubt.
Use the kirocrew-core MCP tool spawn_run with explicit task and model arguments. The native use_subagent / crew pipeline can omit the child model; those calls are outside routing coverage. Downshift never infers the child model from the parent's model or a UI title. The classifier is deterministic; the agent must retry a blocked spawn with the recommended model.
Automatic setup
Run these commands in your terminal. The preview shows the diff without writing; then run setup:
downshift setup kirocrew --dry-run
downshift setup kirocrew
Downshift writes a wrapper to ~/.downshift/bin/kirocrew-pre-tool-use.sh (.cmd on Windows), registers it under agent.kiro_hooks.preToolUse in the existing ~/.kiro/crew/config.json, and installs the compact steering. If the config file does not exist, KiroCrew is not set up yet; if writing the wrapper fails, the config is left untouched. downshift setup --undo restores both. Then restart KiroCrew to regenerate its agent spec and run downshift doctor.
Manual setup
KiroCrew’s Agent Dashboard shows agent run history and health from the ecosystem’s
runs.db. It does not read Downshift’s state directory, so it cannot show routing decisions or savings; usedownshift statsfor those.Important — two hook paths exist.
~/.kiro/agents/kirocrew.jsonis generated by KiroCrew and overwritten on every restart; edits there are lost. The durable registration path is~/.kiro/crew/config.jsonviaagent.kiro_hooks.~/.kiro/crew/hooks.jsonis a separate, informational hook registry (growingrun_countproves invocation; exit code does not block the tool path). Useconfig.jsonfor policy enforcement. Confirmed on Kiro CLI 2.29.0, 2026-10-10.
- Build the binary:
go build -o downshift ./cmd/downshift - Make the checked-in wrapper executable:
The wrapper invokes the repository'schmod +x scripts/kirocrew-pre-tool-use.shdownshiftbinary with thekirocrewsubcommand. Keep it beside this build in the repository checkout. - Merge this entry into
~/.kiro/crew/config.json, preserving existing configuration and hooks. Replace the example path with the absolute path to the wrapper in your checkout:
KiroCrew merges{ "agent": { "kiro_hooks": { "preToolUse": [ { "matcher": "*", "command": "/absolute/path/to/downshift/scripts/kirocrew-pre-tool-use.sh" } ] } } }kiro_hookswith its bundled hooks when generating the agent spec. It acceptscommandas an existing absolute executable path, preservescommandandmatcher, and dropstimeout_ms. Commands containing shell arguments are rejected. The wrapper carries the subcommand so it survives generation. Do not edit~/.kiro/agents/kirocrew.json: KiroCrew regenerates that file and your edits will be lost. - Populate the
kirocrewsession model list from the models actually selectable in your installed Kiro CLI. Follow session-models.md for ordering and discovery precedence. Without a known eligible target, policy mode allows the spawn unchanged. - Load kirocrew-routing-guide.md into the agent's instructions and restart KiroCrew so it regenerates its spec. Verify that the generated
preToolUseincludes the wrapper and existing bundled hooks. - Smoke-test the adapter with known available models (no spawn needed):
Replace the placeholder with a real current model. This checks adapter output only; it does not establish that the harness blocks execution.printf '%s\n' '{"tool_name":"spawn_run","tool_input":{"task":"rename a variable","model":"YOUR_AVAILABLE_FRONTIER_MODEL"}}' \ | ./scripts/kirocrew-pre-tool-use.sh # A confident mismatch with an eligible target returns exit 2 and model=...
~/.kiro/crew/hooks.json tool-call hooks are informational: they can execute after the tool starts, and their exit code does not block that path. A growing run_count proves invocation only. Do not use that registration as policy enforcement or keep an informational duplicate of the native policy hook.
Acceptance evidence and coverage
The linked instructions are the guia inferencial for the behaviour eixo. The native pre-tool policy hook is the sensor computacional for that same concern. Check a real spawn_run with a trivial task and an explicit frontier model: prove it is blocked before the child starts, then retry once with the recommended model. Verify the child's actual model in KiroCrew's native subagent record. Missing-model calls must pass through unchanged.
Spec regeneration and a restart followed by these checks are the sensor computacional for architecture fitness, paired with this setup guia. Routing decisions in the state directory's events.jsonl provide continuous local observations (default ~/.downshift; legacy ~/.harness-downshift). A blocked decision proves enforcement only when paired with the native result; it does not prove successful child execution, usage, or savings. A PostToolUse compliance observer remains separate maintainer tooling (DS-04); this setup does not install one. Native block, retry and served-model acknowledgement were observed on 2026-10-10 with Kiro CLI 2.29.0; see the build-specific evidence.
dsmon — live monitor widget
Prefer
downshift watch. The live session tree is built into thedownshiftbinary (no separate build, every harness): see session-view.md.dsmonstays buildable and is documented below.
cmd/dsmon is a separate command inside this repo — a floating terminal widget that shows model switches, harness, tier distribution and estimated tokens saved in real time. It reads ~/.downshift/events.jsonl and refreshes every 800 ms. Zero external dependencies; pure stdlib Go.
╭──────────────────────────────────────────────────────╮
│ dsmon downshift monitor │
│ harness kirocrew ◉ live │
│ switches today ──────────────────────────────────── │
│ 04:03 opus → haiku trivial -75% │
│ 04:02 haiku → opus complex ↑ │
│ 04:01 opus → sonnet medium -50% │
│ stats ────────────────────────────────────────────── │
│ 35 events 22↓ 2↑ 8✓ │
│ est. saved ~7K tokens ¹ │
│ ¹ estimated · actual tokens not yet tracked │
╰──────────────────────────────────────────────────────╯
04:03:51 · ctrl+c to quit
Build and run
# Build the monitor binary (separate from the hook binary `downshift`)
go build -o dsmon ./cmd/dsmon
# Run in the current terminal
./dsmon
# Open a floating terminal window (auto-detects iTerm2 / kitty / Ghostty / Terminal.app)
./cmd/dsmon/launch.sh
What it shows
| Field | Source | Notes |
|---|---|---|
| Harness | events.jsonl → harness field |
Last harness seen today |
| Switches | events.jsonl → verdict + model fields |
Last 7 routing decisions |
| Events / ↓ ↑ ✓ | events.jsonl |
Today's counts by verdict |
| Est. saved (tokens) | Tokens that ran on a cheaper model than requested, the shared telemetry.TokenSavings view |
Observed where the harness reports tokens; not a bill |
| Tokens | Observed child tokens, when the harness reports them | Not from a provider invoice |
Token consumption note. The PreToolUse routing hook does not receive token usage. On Claude Code, a separate SubagentStop hook (downshift claude-code-subagent-stop) reads the completed child’s transcript and records its reported token counts and model; see session-models.md. Current Downshift views attribute and compare those tokens, with no dollar amounts. Tokens moved to a cheaper model do not prove that fewer tokens were consumed or that the bill was lower. Check the provider’s billing dashboard separately for charges.
Grok CLI (config, not hook)
Grok CLI is the honest exception, and it's worth being precise about why.
Grok has subagents (spawn_subagent, up to ~8 in parallel) and it reads Claude Code / Cursor hook files. But its PreToolUse hook contract is allow-or-deny only — { "decision": "deny", "reason": "…" }. There is no documented updatedInput, so a hook cannot rewrite a subagent's model the way it can on Claude Code, Cursor, and Codex. A downshift hook on Grok could only block a spawn, which isn't the job.
There's a second wrinkle: as of this writing Grok Build ships one coding model (grok-4.6, with configurable reasoning), not a small/mid/frontier model ladder. So on Grok the routing isn't "swap the model" — it's dial the reasoning effort. Cheap work runs grok-4.6 at low effort; the hard tasks run it at high effort. Same principle, different knob.
Grok exposes both as first-class config, which is more robust than a runtime rewrite. Set reasoning per subagent role in ~/.grok/config.toml:
# Built-in read-only types → low reasoning (cheap, fast).
[subagents.personas.explore]
reasoning_effort = "low"
# Custom roles carry their own reasoning default.
[subagents.roles.reviewer]
description = "Reviews generated changes before commit"
default_capability_mode = "read-only"
reasoning_effort = "low"
[subagents.roles.architect]
description = "Cross-cutting design and migrations"
reasoning_effort = "high" # torque for the hard curves
# If your catalog gains cheaper/stronger models, pin them per type too:
# [subagents.models]
# explore = "grok-4.6"
Precedence is explicit spawn override → role default → persona default → parent session, so these pins hold unless the agent is told otherwise. downshift try "<prompt>" grok tells you which tier a task wants; map that tier to a role's reasoning effort in config once.
If a future Grok build adds
updatedInputtoPreToolUse(or a multi-model catalog), adownshift grokhook adapter becomes a drop-in — the classifier and policy are already harness-agnostic. Until then, config is the right and documented lever.
Plan compatibility — read before installing
Not every plan supports subagent model routing. This is a hard constraint at the harness level, not a bug in Downshift. Evidence matrix: harness-matrix.md.
Claude Code
| Plan | Subagents exist? | Model routing works? |
|---|---|---|
| Free | ❌ No real subagents | — |
| Pro / Max / Teams | ✅ Yes | ✅ Observed 2026-10-06 (API-reported child model) |
| API (direct) | ✅ Yes | Expected (same hook path); the 2026-10-06 evidence did not record the plan |
Claude Code free runs in a sandboxed container without network egress — go install will fail. The subagent feature itself only exists on paid plans.
Cursor
| Plan / pricing | Subagents exist? | Model routing works? |
|---|---|---|
| Free | ✅ Limited | ⚠️ updated_input.model silently ignored (known issue) |
| Pro (request-based / legacy) | ✅ Yes | ⚠️ model field in Task only accepts "fast" — override silently dropped |
| Pro / Ultra (usage-based) | ✅ Yes | ⚠️ Rewrite emitted; child model unconfirmed (P1.6) |
Cursor's preToolUse hook fires and Downshift runs, but the model rewrite is silently discarded on legacy and free plans. Tracked on Cursor forum. On usage-based Pro/Ultra plans where subagent model selection is expanded, Downshift emits the rewrite; whether the harness runs the child on that model is unconfirmed (no child-model proof yet — P1.6, matrix row Unconfirmed).
Codex
| Plan | Subagents exist? | Model routing works? |
|---|---|---|
Any (with multi_agent_v2 enabled) |
✅ Yes | ✅ Inferred 2026-10-02 (next spawn asked for the written model), not observed |
Requires [features] multi_agent_v2 and hooks = true in config.toml.
Summary
Downshift serves every harness above from the same core, and Claude Code is the primary harness: the most complete (inferred session model, usage and child-end hooks, /downshift:status, statusline) and the most verified. Pick it when you can choose.
Downshift is most effective on:
- Claude Code Pro / Max / Teams / API — full model routing via hook
- Codex with multi_agent_v2 enabled — model + reasoning_effort emitted; honored is inferred, not observed
- Cursor Pro/Ultra on usage-based plans — rewrite emitted; child model unconfirmed (P1.6)
On free plans or legacy Cursor pricing, the hook runs but model rewrites may be silently ignored by the harness. The tool fails open — the subagent still spawns, just without the model change.
Troubleshooting
downshift: command not found after go install
go install places the binary in ~/go/bin, which may not be in your PATH.
export PATH="$HOME/go/bin:$PATH" # current session
echo 'export PATH="$HOME/go/bin:$PATH"' >> ~/.zshrc && source ~/.zshrc # permanent
The curl installer (install.sh) avoids this by writing directly to /usr/local/bin.
The hook runs but the model is not being changed (Cursor)
On Cursor free plans and legacy request-based plans, updated_input.model is silently discarded by the harness — this is a known Cursor issue. The hook fires and the binary runs (you'll see output on stderr), but the model rewrite has no effect. See Plan compatibility.
No output on stderr — hook is not firing
Check the matcher. Claude Code's subagent tool is Agent (older builds: Task), so the matcher must be "Agent|Task", as setup and the plugin write it. Confirm the binary itself with:
# Should print a JSON allow decision immediately
echo '{"hook_event_name":"PreToolUse","tool_name":"Task","model":"claude-opus-5-5","tool_input":{"prompt":"test"}}' | downshift claude-code
If that prints JSON, the binary works. If the hook still does not fire in a live session, check that settings.json is valid JSON and that the path to downshift is the absolute path (or is in PATH).
go install fails on Claude Code free
Claude Code free runs in a sandboxed container with no network egress. Install the binary on your local machine first, then use the hook — the binary runs on your machine, not inside Claude Code's container.
Verdict shows UNKNOWN instead of DOWNSHIFT
UNKNOWN means no current model was provided. Pass the current model as the third argument to try:
downshift try "rename the variable" claude-code claude-opus-5-5
# → DOWNSHIFT
In the live hook this is handled automatically — the event carries the session model and the adapter reads it.
More docs
- Downshift configuration: state directory and local settingsGuide
- Downshift and Claude Code subagents: when routing can actGuide
- Downshift session view: subagent tree, reasons and pauseGuide
- Downshift report: routed versus control tokens per completed taskGuide
- Downshift evidence ladder: what each saving figure meansGuide
- Downshift companions: Headroom and Claude Code mods to pairGuide
- Downshift capability matrix: status and evidence per featureReference
- Downshift capability matrix, part 2: context and packagingReference
- Downshift harness matrix: Claude Code, Codex, Cursor statusReference
- Downshift value per harness: observed tokens vs list priceReference
- Downshift spawn state: switched, held or only observedReference
- Downshift offline simulation: routing without model callsGuide
- Downshift session models: which model IDs a hook may writeReference
- Downshift complexity weights: how a task becomes a modelReference
- Downshift architecture: deterministic hook and offline CLIDesign
- Downshift routed vs control: first Claude Code A/B resultsEvidence
- Downshift real usage: a Claude Code day and the last 30 daysEvidence
- Downshift Code Intelligence MVP: evidence for Claude CodeEvidence