model_overrides / models.<phaseType> were silently inert for gsd-assumptions-analyzer,
gsd-code-reviewer, and gsd-code-fixer on Claude Code: resolveModelInternal honors them,
but the workflows spawned these agents with no model= param, so the resolved value
never reached the Agent tool and the agents inherited the session model — no warning.
Fix — thread each agent's resolved model at every spawn site (the established
plan-phase pattern; the architecture-consistent Claude mechanism, since 13 other
agents already thread their model):
- discuss-phase-assumptions.md: `resolve-model gsd-assumptions-analyzer --raw`
→ ANALYZER_MODEL, threaded.
- code-review.md + code-review-fix.md (re-review): `resolve-model gsd-code-reviewer --raw`
→ REVIEWER_MODEL, threaded.
- code-review-fix.md (both fixer spawns): `resolve-model gsd-code-fixer --raw`
→ FIXER_MODEL, threaded (same silently-inert bug, same file — folded in per review).
- quick.md review step: was reusing `{executor_model}` for gsd-code-reviewer (so the
reviewer's own override was ignored); init.quick now resolves `reviewer_model`
(gsd-code-reviewer) and the spawn threads it.
resolve-model --raw returns the bare model string (resolve-execution --raw would
return effort — wrong). The resolver maps these agents to phaseType discuss /
verification / execution, so models.<phaseType> apply too.
Scope: the three agents reachable from the two issue-named workflows + quick.md. The
wider systemic class (other agents in UNTOUCHED workflows with the same pattern) stays
documented on the issue for a maintainer-scoped structural decision (thread-at-source
vs embed-at-install like #2256), not widened here.
Docs: the stale "discuss — reserved, no subagent today" model-profile tables now list
gsd-assumptions-analyzer and the verification row includes gsd-code-reviewer, across
the English docs, the shipped gsd-core/references/model-profiles.md reference, and the
ja-JP / zh-CN / ko-KR / pt-BR locale mirrors.
Tests:
- tests/model-resolver.test.cjs: #2072 acceptance — model_overrides and
models.discuss/verification/execution resolve for all three agents.
- tests/model-routing-spawn-threading.test.cjs: every spawn of the three agents threads
a resolved model (fails pre-fix); a header-precise parity guard fails the suite if a
new un-threaded spawn of any of them regresses.
All 16 golden-install-parity fixtures + the workflow size baseline regenerated for the
changed shipped files (4 workflows + the reference doc); bin/lib is excluded from parity.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
8.0 KiB
How to configure model profiles
Choose the right model tier strategy for your project, then tune individual agents or entire phase types without writing a large override block. This guide starts with the simplest lever and works up to dynamic routing.
The four profiles (plus adaptive and inherit)
Set model_profile in .planning/config.json or via /gsd-config --profile <name>:
| Profile | Planner | Executor | Researchers | Verifier | Use when |
|---|---|---|---|---|---|
quality |
Opus | Opus | Opus | Sonnet | Production-quality work where cost is secondary |
balanced |
Opus | Sonnet | Sonnet | Sonnet | Normal development — the default |
budget |
Sonnet | Sonnet | Haiku | Haiku | Rapid prototyping, cost-sensitive contexts |
adaptive |
Opus | Sonnet | Sonnet | Sonnet | Resolves the same way as the other tiers under runtime-aware profiles; use when switching between runtimes frequently |
inherit |
(session model) | (session model) | (session model) | (session model) | Non-Anthropic providers (OpenRouter, local models) — all agents follow your current session model |
The table above shows a representative subset. All 33 shipped agents have explicit per-profile tier assignments in sdk/shared/model-catalog.json. For the full table see Model Profiles in the configuration reference.
Quick switch via command:
/gsd-config --profile balanced # Normal development
/gsd-config --profile budget # Prototyping or high-cost phases
/gsd-config --profile quality # Production release
/gsd-config --profile inherit # OpenRouter, local models
Or edit .planning/config.json directly:
{
"model_profile": "balanced"
}
Per-agent overrides (model_overrides)
If a single agent needs a different tier without changing the whole profile, use model_overrides:
{
"model_profile": "balanced",
"model_overrides": {
"gsd-executor": "opus",
"gsd-codebase-mapper": "haiku"
}
}
Valid values: opus, sonnet, haiku, inherit, or any fully-qualified model ID (e.g. "openai/o3", "google/gemini-2.5-pro").
model_overrides can be set per-project in .planning/config.json or globally in ~/.gsd/defaults.json. Per-project entries win on conflict; non-conflicting global entries are preserved.
Important for Codex and OpenCode: Those runtimes embed the resolved model into each agent's static config at install time. After editing model_overrides, re-run the installer for the change to take effect:
npx @opengsd/gsd-core@latest --codex --global # or --opencode, --kilo, etc.
GSD will also warn you if you forget: workflow entry commands (gsd init plan-phase, gsd init execute-phase, etc.) detect when .planning/config.json or ~/.gsd/defaults.json is newer than your installed agent files and print a one-line stderr reminder naming the changed file and the re-install command. The check is read-only and runs only on codex and opencode; Claude Code resolves models at spawn time and is unaffected. (#1688)
Per-phase-type models (models)
If you want to say "Opus for planning, Sonnet for everything else" without learning all 33 agent names, use the models block. It maps six phase types to tier aliases:
{
"model_profile": "balanced",
"models": {
"planning": "opus",
"discuss": "opus",
"research": "sonnet",
"execution": "opus",
"verification": "sonnet",
"completion": "sonnet"
}
}
Phase types and their agents:
| Phase type | Agents covered |
|---|---|
planning |
gsd-planner, gsd-roadmapper, gsd-pattern-mapper |
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 |
discuss |
gsd-assumptions-analyzer |
completion |
Reserved — no subagent today; accepted by schema for forward compatibility |
The models block accepts tier aliases only (opus, sonnet, haiku, inherit). For a fully-qualified model ID, use model_overrides per agent instead.
Combining models with a per-agent exception:
{
"model_profile": "balanced",
"models": {
"research": "sonnet"
},
"model_overrides": {
"gsd-codebase-mapper": "haiku"
}
}
All five research agents resolve to sonnet except gsd-codebase-mapper, which is pinned to haiku.
Dynamic routing — start cheap, escalate on failure
If you want to pay for cheaper tiers by default and only escalate when an agent fails a quality gate, enable dynamic_routing:
{
"dynamic_routing": {
"enabled": true,
"tier_models": {
"light": "haiku",
"standard": "sonnet",
"heavy": "opus"
},
"escalate_on_failure": true,
"max_escalations": 1
}
}
Each agent has a default tier (light, standard, or heavy). On the first attempt, GSD picks tier_models[default_tier]. If the orchestrator detects a soft failure (verification inconclusive, plan-check flagged, etc.), it re-spawns the agent one tier up. max_escalations caps the total retries.
Agents that already sit at heavy cannot escalate further.
Turning off escalation while keeping dynamic resolution:
{
"dynamic_routing": {
"enabled": true,
"escalate_on_failure": false
}
}
Every attempt uses tier_models[default_tier] regardless of outcome — useful when you want explicit tier-to-model mapping without the escalation behaviour.
dynamic_routing is disabled by default. Omitting the block or setting enabled: false preserves static resolution.
Using GSD on non-Anthropic runtimes
If you installed GSD for Codex, OpenCode, Antigravity CLI, or Kilo, the installer already set resolve_model_ids: "omit" in your config. This tells GSD to skip Anthropic model ID resolution and let the runtime choose its own default model. No manual setup is needed for the basic case.
If you want tiered models on Codex:
{
"runtime": "codex",
"model_profile": "balanced"
}
GSD resolves each tier alias to the Codex-native model and reasoning effort defined in the runtime tier map.
If you want per-agent model IDs on any non-Claude runtime:
{
"resolve_model_ids": "omit",
"model_overrides": {
"gsd-planner": "o3",
"gsd-executor": "o4-mini",
"gsd-debugger": "o3"
}
}
For the full runtime-aware profiles reference and the model_policy surface (provider-neutral presets added in v1.42), see Configuration reference — Model Profiles.
Resolution precedence (highest to lowest)
When multiple layers apply, the resolver picks the highest-priority entry:
1. model_overrides[<agent>] — per-agent; full IDs; targeted exception
2. dynamic_routing.tier_models[<tier>] — when enabled; escalates on soft failure
3. models[<phase_type>] — coarse phase-level tier
4. model_profile (per-agent column) — global tier strategy
5. Runtime default — when nothing else applies
Choosing the right lever
| You want | Use |
|---|---|
| One tier strategy for all agents | model_profile |
| Coarse phase-level tuning ("Opus for planning") | models.<phase_type> |
| Per-agent precision ("force Haiku on the codebase mapper") | model_overrides[<agent>] |
| A fully-qualified model ID for a specific agent | model_overrides[<agent>]: "openai/gpt-5" |
| Start cheap, escalate only on failure | dynamic_routing |
| All agents follow the session model (non-Anthropic provider) | model_profile: "inherit" |