Files
msd-core/gsd-core/references/model-profiles.md
Tom Boucher eb49ff98df fix(#4728): stop presenting the retired Gemini CLI as a supported runtime (#4743)
* fix(#4728): stop presenting the retired Gemini CLI as a supported runtime

#1928 removed the Gemini CLI runtime after Google sunset it on 2026-06-18, and
updated the ENGLISH docs. The locale mirrors and the runtime-loaded workflow
prose were not updated in the same change, and no gate asserts the ABSENCE of a
retired runtime, so both drifted quietly for a year.

The finding that shaped this change: English is already correct. docs/
ARCHITECTURE.md, CONFIGURATION.md, USER-GUIDE.md, how-to/install-on-your-runtime.md
and CLI-TOOLS.md carry zero runtime-axis Gemini references; the only English hits
anywhere are a Gemini 2.5 Pro MODEL line, the GEMINI_API_KEY row, and prose that
correctly documents the retirement. So the docs half of this is translation lag,
not a content decision, and every locale edit here is parity with an existing
English line rather than new wording:

  - install-on-your-runtime.md  English has NO `### Gemini CLI` section  -> deleted
  - USER-GUIDE.md :843          "…, Antigravity CLI, Kilo)"              -> substituted
  - ARCHITECTURE.md             English has NO Gemini CLI table row      -> row deleted
  - ARCHITECTURE.md :24         English holds `Kimi CLI` in that slot    -> Kimi CLI
  - context-monitor.md :3       "`AfterTool` for Antigravity CLI"        -> substituted
  - spike-and-sketch.md :93     "(Codex, Antigravity CLI, etc.)"         -> substituted
  - configure-model-profiles    "Codex, OpenCode, Antigravity CLI, or Kilo" -> substituted
  - COMMANDS.md                 English keeps only hyphen + Codex bullets -> colon bullet deleted
  - FEATURES.md                 source docs/features/multi-runtime-support.md:10
                                lists no Gemini CLI                       -> name removed

ARCHITECTURE.md:24 is the clearest case for reading English rather than
substituting blind: Antigravity ALREADY appears later in that list, so replacing
Gemini CLI with Antigravity would have named it twice. English holds Kimi CLI
there, so that is what the locales get.

The largest single class was hand-duplicated boilerplate. A "Text mode" paragraph
repeated across 34 runtime-loaded workflow files ends "…required for non-Claude
runtimes (OpenAI Codex, Gemini CLI, etc.)". No lint enforces that sentence and no
script syncs it, so every copy was edited. These files are read by the agent at
runtime, so they steer behavior rather than only informing a reader — which is why
this class matters more than its word count suggests.

The slash-command-form section is restructured in all four languages to match
English, which had already dropped its colon-form bullet. That bullet claimed the
colon form is "Gemini CLI only", which was false on its own terms independent of
the retirement: `/gsd:…` is GSD's canonical AUTHORING token, rewritten per runtime
at install time, and NO runtime registers it — VALID_COMMAND_STYLES is
{slash-hyphen, shell-var} and 18 of 19 runtimes declare slash-hyphen. Substituting
the runtime name would have left the claim false with Antigravity's name in it, so
the claim is gone, matching English.

Two anchor regressions were caught and fixed while doing that. zh-CN lost its
explicit {#slash-command-forms-hyphen-vs-colon} anchor while its TOC still linked
it; the anchor is restored. ko-KR and pt-BR never had an explicit anchor and rely
on the slug generated from the heading text, so shortening the heading broke their
own TOC links; those links now point at the new slugs. English's heading lost its
anchor while its TOC still links the old one — that latent English bug is
deliberately NOT copied.

Preserved, because `gemini` is not one thing here and a blanket sweep breaks the
product: ~/.gemini/antigravity{,-ide,-cli} and ~/.gemini as their parent;
~/.gemini/config (#3738); GEMINI.md; hookEvents "gemini"; GEMINI_API_KEY in all
four locales; every gemini-* model id and the Gemini 2.5 Pro references in
ko-KR/pt-BR/zh-CN (ja-JP genuinely lacks that line — the locales have diverged, so
a uniform patch would be wrong); the hook-event dialect notes, which are
RE-ATTRIBUTED rather than deleted because Antigravity inherits that dialect;
reapply-patches.md:93's legacy-install note; host-integration-capability-matrix.md
:27 and :342, which correctly record the sunset and Antigravity's contract;
whats-new-1.7.0.md and FEATURES.md:3506, which document the retirement itself; and
the generated launcher preamble, which belongs to epic #4632 — zero
_GSD_SHIM_NAME lines appear in this diff.

Coverage: a #4728 block in tests/gemini-runtime-removed.test.cjs asserts the
retired name is gone from STRUCTURAL POSITIONS (a level-3 heading, a table row's
first cell, a runtime-example parenthetical) rather than asserting the string is
absent, which would be wrong. It pairs those with positive PRESERVE assertions
over the same files — Antigravity's heading, ~/.gemini/antigravity, GEMINI_API_KEY,
AfterTool — so a patch that deletes too much fails as loudly as one that deletes
too little. The model-axis test pins both the presence in three locales and the
absence in ja-JP, so a later uniform patch that "helpfully" adds it back fails.
The new docs/ reads tripped lint-docs-guard-registration for the first time in
this file, so the test is registered in scripts/docs-guard-registry.cjs.

Not covered here, by design: nothing above would catch a Gemini-as-runtime
reference appearing in a NEW file tomorrow. That is the repo-wide drift guard,
#4729, which must land last — written now it would red on the very references this
change removes.

Fixes #4728

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(#4728): fix four review blockers, including a vacuous test and my own duplicate

A full matrix run on 31f12d7943 FAILED with 3 real failures, and an isolated
adversarial review returned BLOCK on four blockers. All of it was correct.

1. I committed the exact error I claimed to have avoided. The commit message
   boasted that ARCHITECTURE.md:24 proved the value of reading English rather
   than substituting blind, because Antigravity already appeared later in that
   list. Five hundred lines further down the SAME four files, my
   `Gemini:` -> `Antigravity:` substitution produced TWO consecutive
   `- Antigravity:` bullets, because an Antigravity bullet was already there.
   English (ARCHITECTURE.md:827) merges them into one. Now merged in all four
   locales, reusing each locale's existing words.

2. `--gemini` survived in the runtime-detection CLI flag list in all four
   locale ARCHITECTURE.md files. English:817 holds `--kimi` in that slot and
   already lists `--antigravity` later, so this is another place where
   substituting Antigravity would have duplicated it. Now `--kimi`.

3. Two runtime-loaded workflow files still enumerated Gemini one line ABOVE the
   line I had already corrected -- the "Adaptive (Recommended)" option in
   settings.md:192 and new-project/steps/auto-mode-config.md:95.

4. THE NEW TEST WAS VACUOUS for two of its five files. It matched only
   `non-Claude runtimes (` and `(e.g. `, and neither regex could reach the two
   lines the change actually fixed: health.md:52 reads `non-Claude (Codex, ...)`
   without the word "runtimes", and execute-phase.md:1028 has no parenthetical
   at all. The reviewer proved it by re-introducing Gemini at both lines and
   watching the assertion stay GREEN. That same blind spot is what hid finding 3.

   Replaced with a case-sensitive `/\bGemini\b/` walk over every
   `gsd-core/workflows/**/*.md`, which works because every LEGITIMATE gemini
   reference in that tree is spelled differently and cannot match: Antigravity's
   paths are lowercase with a slash (`~/.gemini/antigravity`), Google's model ids
   are lowercase and hyphenated (`gemini-3.1-pro-preview`), and the env vars are
   uppercase (`GEMINI_CONFIG_DIR`, `GEMINI_SESSION_ID`). A bare capitalised
   `Gemini` there means the retired RUNTIME is being named. The walk asserts it
   found at least 50 files so an empty walk cannot pass vacuously, and it now
   covers the nested `new-project/steps/` directory where finding 3 lived.

   Two allowlist entries, both by line CONTENT and both justified:
   reapply-patches.md's `Legacy: ... pre-#1928` note, and settings-advanced.md's
   `Known provider` menu. The second was escalated by the agent rather than
   decided: Section 8 of that file says model policy is defined "independently"
   of the runtime, so `(Claude / OpenAI / Gemini / Qwen)` is the PROVIDER axis --
   the same axis as the lowercase model ids -- and must keep working.

   Proven to fail, not just asserted: the predicate reports 0 offenders on the
   real tree and exactly 2 on a /tmp copy with Gemini re-injected at
   health.md:52 and execute-phase.md:1028.

Also from the review: a `| Gemini |` COLUMN survived in the locale FEATURES.md
comparison tables (English has none) -- removed from all three, with header,
separator and every body row kept aligned; two ENGLISH runtime-axis sites were
missed by my own parity standard (how-to/execute-a-phase.md:88 and
how-to/verify-and-ship.md:89, the latter doubly stale since #4716 retired the
Gemini reviewer lane); docs/USER-GUIDE.md:12 linked a dead anchor, which I had
found and deliberately left -- record-and-proceed on a known defect is exactly
what the rules forbid, so it is fixed; docs/COMMANDS.md:12 and all four mirrors
still claimed "the hyphen and colon forms are runtime-specific spellings" with
no colon form documented anywhere, so that false sentence is deleted; and ko-KR
had the installer rather than the user doing the targeting.

The other two matrix failures were the compact-content benchmark baseline, which
drifted because this PR changes byte counts, refreshed via the script's own
`--write` path rather than by hand; and this commit's emitted-drift-ack trailers.

Method note on the acks: the failing run measured growth against
origin/next@1110c3b4ee, which is the STALE LOCAL `next` ref -- gsd-test merges
into the local base branch, and this machine's `next` is seven commits behind
origin/next, which is checked out in the main worktree and so cannot be
fast-forwarded from here. The 32 trailers below are computed against the REAL
base (origin/next @ ca8d9d4459) by comparing each tracked file's blob size, which
is one more file than that run reported -- the extra is settings.md, grown again
by fix 3. docs-update.md and map-codebase.md are deliberately NOT acked: they
SHRANK, since there the fix deleted ", Gemini CLI" rather than substituting, and
acking a file no delta consumed is itself an error.

Refs #4728

Emitted-Drift-Ack-Growth: add-tests.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: add-todo.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: ai-integration-phase.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: check-todos.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: cleanup.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: complete-milestone.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: do.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: eval-review.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: execute-phase.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: execute-plan.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: health.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: import.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: inbox.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: manager.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: new-milestone.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: new-workspace.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: note.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: onboard.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: plant-seed.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: profile-user.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: quick.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: remove-workspace.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: secure-phase.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: settings.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: ship.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: smart-entry.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: ui-phase.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: ui-review.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: undo.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: update.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: validate-phase.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: verify-work.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* chore(#4728): add the changeset fragment

The PR body claimed one was present and it was not — caught by
scripts/changeset/lint.cjs reporting fail_missing_fragment, not by the
checklist, which is exactly why the lint exists.

Type Fixed: the diff is prose, and a docs-only fix uses Fixed since there is
no Documentation type.

Refs #4728

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: sim <sim@local>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 16:49:52 -04:00

15 KiB
Raw Blame History

Model Profiles

Model profiles control which Claude model each GSD agent uses. This allows balancing quality vs token spend, or inheriting the currently selected session model.

Profile Definitions

Agent quality balanced budget adaptive inherit
gsd-planner opus opus sonnet opus inherit
gsd-roadmapper opus sonnet sonnet sonnet inherit
gsd-executor opus sonnet sonnet sonnet inherit
gsd-phase-researcher opus sonnet haiku sonnet inherit
gsd-project-researcher opus sonnet haiku sonnet inherit
gsd-research-synthesizer sonnet sonnet haiku haiku inherit
gsd-debugger opus sonnet sonnet opus inherit
gsd-codebase-mapper sonnet haiku haiku haiku inherit
gsd-verifier sonnet sonnet haiku sonnet inherit
gsd-plan-checker sonnet sonnet haiku haiku inherit
gsd-integration-checker sonnet sonnet haiku haiku inherit
gsd-nyquist-auditor sonnet sonnet haiku haiku inherit

Per-Phase-Type Model Map (#3023)

.planning/config.json accepts a coarse per-phase-type map under the models key. Use this when you want tuning at the phase level ("Opus for planning and execution, Sonnet for the rest") without learning the agent taxonomy.

{
  "model_profile": "balanced",
  "models": {
    "planning": "opus",
    "discuss": "opus",
    "research": "sonnet",
    "execution": "opus",
    "verification": "sonnet",
    "completion": "sonnet"
  },
  "model_overrides": {
    "gsd-codebase-mapper": "haiku"
  }
}

Phase-type → agent mapping

Phase type Agents
planning gsd-planner, gsd-roadmapper, gsd-pattern-mapper
discuss gsd-assumptions-analyzer
research gsd-phase-researcher, gsd-project-researcher, gsd-research-synthesizer, gsd-codebase-mapper, gsd-ui-researcher
execution gsd-executor, gsd-debugger, gsd-doc-writer
verification gsd-verifier, gsd-plan-checker, gsd-integration-checker, gsd-nyquist-auditor, gsd-ui-checker, gsd-ui-auditor, gsd-doc-verifier, gsd-code-reviewer
completion (reserved — no subagent today)

Resolution precedence (highest to lowest)

  1. Per-agent model_overrides[agent] — full IDs accepted; targeted exceptions
  2. Phase-type models[phase_type] — tier alias only (opus / sonnet / haiku / inherit)
  3. Profile table — the per-agent column from the active model_profile
  4. Runtime default — when nothing else applies

Steps 2–4 select the tier; a model_profile_overrides.<runtime>.<tier> entry then maps that tier to a concrete model (#4192 — honored on the claude runtime as well, so pinning composes with tiering instead of replacing it).

Why two layers above the profile?

  • Profile is a global tier strategy (everyone runs balanced).
  • models is coarse phase-level tuning without learning agent names.
  • model_overrides is per-agent precision (e.g. force haiku on gsd-codebase-mapper for a fan-out).

The three layers compose: models defaults a phase, model_overrides carves an exception out of it.

Profile Philosophy

quality - Maximum reasoning power

  • Opus for all decision-making agents
  • Sonnet for read-only verification
  • Use when: quota available, critical architecture work

balanced (default) - Smart allocation

  • Opus only for planning (where architecture decisions happen)
  • Sonnet for execution and research (follows explicit instructions)
  • Sonnet for verification (needs reasoning, not just pattern matching)
  • Use when: normal development, good balance of quality and cost

budget - Minimal Opus usage

  • Sonnet for anything that writes code
  • Haiku for research and verification
  • Use when: conserving quota, high-volume work, less critical phases

adaptive — Role-based cost optimization

  • Opus for planning and debugging (where reasoning quality has highest impact)
  • Sonnet for execution, research, and verification (follows explicit instructions)
  • Haiku for mapping, checking, and auditing (high volume, structured output)
  • Use when: optimizing cost without sacrificing plan quality, solo development on paid API tiers

inherit - Follow the current session model

  • All agents resolve to inherit
  • Best when you switch models interactively (for example OpenCode or Kilo /model)
  • Required when using non-Anthropic providers (OpenRouter, local models, etc.) — otherwise GSD may call Anthropic models directly, incurring unexpected costs
  • Use when: you want GSD to follow your currently selected runtime model

Using Non-Claude Runtimes (Codex, OpenCode, Antigravity CLI, Kilo)

When installed for a non-Claude runtime, the GSD installer sets resolve_model_ids: "omit" in ~/.gsd/defaults.json. This returns an empty model parameter for all agents, so each agent uses the runtime's default model. No manual setup is needed.

To assign different models to different agents, add model_overrides with model IDs your runtime recognizes:

{
  "resolve_model_ids": "omit",
  "model_overrides": {
    "gsd-planner": "o3",
    "gsd-executor": "o4-mini",
    "gsd-debugger": "o3",
    "gsd-codebase-mapper": "o4-mini"
  }
}

The same tiering logic applies: stronger models for planning and debugging, cheaper models for execution and mapping.

Using Claude Code with Non-Anthropic Providers (OpenRouter, Local)

If you're using Claude Code with OpenRouter, a local model, or any non-Anthropic provider, set the inherit profile to prevent GSD from calling Anthropic models for subagents:

# Via settings command
/gsd:settings
# → Select "Inherit" for model profile

# Or manually in .planning/config.json
{
  "model_profile": "inherit"
}

Without inherit, GSD's default balanced profile spawns specific Anthropic models (opus, sonnet, haiku) for each agent type, which can result in additional API costs through your non-Anthropic provider.

Advisor Tool (Claude Code)

Claude Code (v2.1.98+) can pair the session's executor model with a stronger advisor model that it consults mid-generation for strategy and course-correction (Anthropic's advisor tool). This is a host-runtime feature, not a GSD setting — GSD selects each agent's executor model through the profile/tier system above; Claude Code supplies the advisor.

Set it once at the session level with /advisor <model> (or the advisorModel setting / --advisor flag). Subagents inherit the session advisor automatically, so every GSD subagent an orchestrator spawns gets the same advisor with no per-agent configuration. It composes cleanly with GSD's tiering: the profile keeps executors cheap where the work is mechanical, and the advisor adds a stronger reviewer inline on the turns that benefit.

Candidate pairings

Per Anthropic's advisor-tool docs the advisor must be at least as capable as the executor. Candidate pairings by profile — evaluate on your own workload; the quality/cost characterizations below are Anthropic-reported, not GSD guarantees:

Profile Typical executors Candidate advisor Rationale (per Anthropic docs)
budget Haiku / Sonnet Fable 5 or Opus A step up in intelligence over Haiku alone, at lower cost than switching the executor to a larger model
balanced Sonnet Fable 5 or Opus A quality lift at similar or lower total cost than Sonnet-solo on complex tasks
quality / adaptive Opus (planning), Sonnet Fable 5 or Opus Marginal on turns already at top capability; most valuable on the Sonnet-executor agents

Fable 5 is a valid advisor for Haiku 4.5, Sonnet 4.6/5, and Opus 4.8 executors, so it pairs with any tier a profile assigns.

When it's worth enabling

  • Worth it: long, multi-step agent loops where the plan matters but most turns are mechanical — e.g. execute-phase and debug. Anthropic's docs note advisor prompt-caching pays off at roughly three or more advisor calls, which these long loops make.
  • Skip it: short, one-shot agents (mappers, quick audits, single-file checks) — there is little to plan, and the advisor adds cost without a commensurate quality gain.

Constraint: session-level only (today)

The advisor is a single session-wide setting inherited by all subagents; there is no per-agent advisor selection, so GSD cannot vary the advisor by role the way it varies the executor model (e.g. "no advisor on the Haiku mapper, a Fable 5 advisor on the Sonnet executor"). Per-agent advisor control is tracked upstream at anthropics/claude-code#73072; until it lands, pick one session advisor that fits the most valuable agents in your run.

Dynamic Routing with Failure-Tier Escalation (#3024)

When dynamic_routing.enabled = true in .planning/config.json, the resolver picks a model from a tier-mapped table based on the agent's default tier (light / standard / heavy) and escalates to the next tier up on orchestrator-detected soft failure.

{
  "dynamic_routing": {
    "enabled": true,
    "tier_models": {
      "light":    "haiku",
      "standard": "sonnet",
      "heavy":    "opus"
    },
    "escalate_on_failure": true,
    "max_escalations": 1
  }
}

Agent default tiers (each agent in MODEL_PROFILES declares one):

Tier Agents Use case
light gsd-codebase-mapper, gsd-pattern-mapper, gsd-research-synthesizer, gsd-plan-checker, gsd-integration-checker, gsd-nyquist-auditor, gsd-ui-checker, gsd-ui-auditor, gsd-doc-verifier Cheap/fast — pure mappers, scanners, low-stakes audits
standard gsd-executor, gsd-phase-researcher, gsd-project-researcher, gsd-verifier, gsd-doc-writer, gsd-ui-researcher Default workhorse — research, writing, primary verification
heavy gsd-planner, gsd-roadmapper, gsd-debugger Deep reasoning — already at top, can't escalate further

Escalation flow (orchestrator-driven):

  1. Orchestrator spawns agent with attempt: 0 → resolver returns tier_models[default_tier]
  2. If orchestrator marks the result a soft failure, it re-spawns with attempt: 1 → resolver returns tier_models[next_tier_up]
  3. max_escalations caps total retries (default 1). Beyond the cap the resolver returns the cap-tier model so the orchestrator can log without burning further budget.
  4. Hard failures (exceptions) bypass escalation and surface immediately.

Precedence with other tier sources (highest → lowest):

  1. model_overrides[<agent>] — full ID, always wins
  2. dynamic_routing.tier_models[escalated_tier] — when enabled: true
  3. models[<phase_type>] — coarse phase-level (#3023)
  4. model_profile — global tier strategy

When dynamic_routing.enabled = false (default), behavior is identical to today.

Resolution Logic

Orchestrators resolve model before spawning. The full precedence ladder is (highest → lowest):

1. Read .planning/config.json
2. Check model_overrides[<agent>] (full IDs accepted; targeted exceptions)
3. If dynamic_routing.enabled, return tier_models[escalated_tier]
   (see §Dynamic Routing — escalation steps tier up per attempt counter)
4. If no dynamic_routing match, check models[phase_type] for a phase-type tier
   (see §Per-Phase-Type Model Map for the agent → phase-type mapping)
5. Check model_profile_overrides.<runtime>.<tier> for a per-tier model override
   (honored on the claude runtime too — #4192; verbatim unless it maps to the
   current tier alias)
6. If no phase-type slot, look up agent in profile table
7. Pass model parameter to Task call

model and effort resolve through different mechanisms at different times — they do not share the ladder above. model resolves at runtime, per spawn, from .planning/config.json; a config change takes effect on the next spawn. effort (claude runtime) has its own cascade (agent_overrides → routing_tier_defaults → default; see docs/CONFIGURATION.md § "Where effort actually reaches") and is baked at install time into the effort: frontmatter key of ~/.claude/agents/gsd-*.md — Claude Code's Agent tool has no per-spawn effort parameter, so per-agent frontmatter is the only channel. An effort config change has no effect until gsd_run effort sync --apply re-syncs the agent files. Codex agents instead pin model_reasoning_effort in ~/.codex/agents/*.toml at install time.

Per-Agent Overrides

Override specific agents without changing the entire profile:

{
  "model_profile": "balanced",
  "model_overrides": {
    "gsd-executor": "opus",
    "gsd-planner": "haiku"
  }
}

Overrides take precedence over the profile. Valid values: opus, sonnet, haiku, fable, inherit, or any fully-qualified model ID (e.g., "o3", "openai/o3", "google/gemini-2.5-pro"). fable is a Claude Code Agent-tool alias, not a GSD profile tier — it has no column in the profile table above.

On the Claude runtime, fully-qualified Claude model IDs are honored as explicit generation pins (#4192): an ID that names the current tier default (e.g. "claude-sonnet-5") resolves to its tier alias — the same model in the form the Agent tool always accepts — while any other ID (e.g. "claude-opus-4-7") resolves verbatim, with a warn-once stderr note that setups accepting only tier aliases will not honor a full ID. To pin a generation for a whole tier rather than one agent, set model_profile_overrides.claude.<tier> (see docs/CONFIGURATION.md — Runtime-Aware Profiles).

Switching Profiles

Runtime: /gsd-set-profile <profile>

Per-project default: Set in .planning/config.json:

{
  "model_profile": "balanced"
}

Design Rationale

Why Opus for gsd-planner? Planning involves architecture decisions, goal decomposition, and task design. This is where model quality has the highest impact.

Why Sonnet for gsd-executor? Executors follow explicit PLAN.md instructions. The plan already contains the reasoning; execution is implementation.

Why Sonnet (not Haiku) for verifiers in balanced? Verification requires goal-backward reasoning - checking if code delivers what the phase promised, not just pattern matching. Sonnet handles this well; Haiku may miss subtle gaps.

Why Haiku for gsd-codebase-mapper? Read-only exploration and pattern extraction. No reasoning required, just structured output from file contents.

Why inherit instead of passing opus directly? Claude Code's "opus" alias maps to a specific model version. Organizations may block older opus versions while allowing newer ones. GSD returns "inherit" for opus-tier agents, causing them to use whatever opus version the user has configured in their session. This avoids version conflicts and silent fallbacks to Sonnet.

Why inherit profile? Some runtimes (including OpenCode) let users switch models at runtime (/model). The inherit profile keeps all GSD subagents aligned to that live selection.