* feat(#3023): per-phase-type model map in .planning/config.json Adds a new `models` block to .planning/config.json with six phase-type slots (planning / discuss / research / execution / verification / completion). Lets users express coarse tuning ("Opus for planning, Sonnet for the rest") without learning the agent taxonomy. Resolution precedence (highest → lowest): 1. Per-agent `model_overrides[agent]` (full IDs; targeted exception) 2. Phase-type `models[phase_type]` (NEW; tier alias) 3. Profile table (`model_profile`) (per-agent column) 4. Runtime default The three layers compose: `models` defaults a phase, `model_overrides` carves an exception. Phase-type values are tier aliases (opus/sonnet/ haiku/inherit) so the runtime-resolution chain (#2517) stays correct end-to-end without further branching. Implementation: - model-profiles.cjs: new AGENT_TO_PHASE_TYPE map + VALID_PHASE_TYPES set. Each agent in MODEL_PROFILES gets one phase-type assignment; tests assert coverage so adding a new agent without updating the table fails CI. - core.cjs (resolveModelInternal): inserts phase-type tier lookup between per-agent override and profile-derived tier. Skips runtime resolution when the resolved tier is 'inherit' (was previously gated only on profile === 'inherit'; phase-type can now produce inherit independently). - core.cjs (loadConfig): pass `parsed.models` through both code paths so resolveModelInternal can read it. - config-schema.cjs + sdk/src/query/config-schema.ts: dynamic-pattern validator accepts only the six known phase-types; unknown slots rejected at config-set time. Backward compat: configs without `models` behave exactly as today. Tests (15 cases, all structural-IR — no stdout grep): - Schema: AGENT_TO_PHASE_TYPE coverage, VALID_PHASE_TYPES exact match - Resolver: phase-type alone; per-agent override beats phase-type; phase-type beats profile; issue's full example; "inherit"; empty block is no-op; no block is no-op - Validation: each of the 6 slots accepted; unknown slot rejected; bare `models` (no slot) rejected Verification: - 15/15 pass on new regression test - 6808/6808 full suite (5 net new), 0 fail - lint-no-source-grep clean across 375 test files Closes #3023 * docs(#3023): document `models` per-phase-type config in user-facing docs Adds `models` block coverage to the three user-facing docs that ship with each release: 1. docs/CONFIGURATION.md - New "Per-Phase-Type Models" section between "Per-Agent Overrides" and "Non-Claude Runtimes" with: * full example mixing models + model_overrides * phase-type → agent mapping table * resolution-precedence pseudocode * accepted values (tier alias only) * "When to use which" decision matrix * validation behavior + example error - Added `"models": {}` to the Full Schema snippet - Added a row for `models.<phase_type>` to the config keys table (next to model_profile_overrides for adjacency) 2. docs/FEATURES.md - Added a row for models.<phase_type> in the Configurable Settings table (right under model_profile) - Cross-link to CONFIGURATION.md for the full surface 3. docs/USER-GUIDE.md - New task-oriented "Tuning model cost by phase" section above "Using Non-Claude Runtimes" — leads with the concrete config and shows the override pattern (one-shot phase + targeted exception) - Cross-link to CONFIGURATION.md Verification: - 29/29 pass on config-schema-docs-parity + docs-update + new feature test (parity-check passes, so the config-schema entry I added in the feature commit is now matched by a docs row) - 6808/6808 full suite pass - lint-no-source-grep clean Doc style follows the same pattern used by the existing model_profile, model_overrides, and model_profile_overrides sections — example-led, table-backed, cross-referenced. Each doc surfaces the feature at the right depth (reference / settings table / task guide). * fix(#3023): mirror phase-type tier in resolveReasoningEffortInternal (CR Major) CodeRabbit caught a real Codex correctness bug + 3 minor docs/test issues: 1. **Major (outside-diff)** — resolveReasoningEffortInternal in core.cjs derived its tier exclusively from the profile table, ignoring the models.<phase_type> override added in #3023. Failure mode on Codex: Config: model_profile=balanced, models.execution=opus, agent=gsd-executor resolveModelInternal: tier=opus → gpt-5.4 resolveReasoningEffortInternal: tier=sonnet → reasoning_effort=medium ↑ WRONG — should be xhigh (opus tier on Codex) The runtime received a mismatched (model, effort) pair. Mirrored the phase-type lookup from resolveModelInternal so both functions derive from the same tier source. 'inherit' phase-type returns null effort (no runtime entry maps to 'inherit'; let runtime decide). 2. Minor — .changeset/per-phase-type-models.md `pr: TBD` → `pr: 3030`. 3. Minor (outside-diff) — model-profiles.md "Resolution Logic" section omitted the new phase-type tier. Updated the 4-step block to a 5-step block including `models[phase_type]` between override and profile, plus a paragraph noting that `model` and `reasoning_effort` derive from the same tier source. 4. Nitpick — added 2 typo-safety tests: - models.research = "haiku3" (typo) → falls through to profile - models.research = "openai/gpt-5" (full ID) → falls through to profile Plus 5 new reasoning_effort tests covering the Major fix: - exported correctly - phase-type override flips both model AND effort to same tier - inherit phase-type returns null effort - per-agent override still bypasses phase-type for effort - claude runtime ignores models.* (no effort propagation) Verification: - 24/24 pass on regression test (15 original + 2 typo-safety + 5 effort + 2 outside-diff related) - 6815/6815 full suite (7 net new from 6808) - lint-no-source-grep clean The reasoning_effort tests are written semantically (phase-type override must produce the SAME effort as a profile-only opus config) rather than hard-coding tier-specific effort strings, so changes to the runtime tier map don't break them. * fix(#3023): phase-type override beats profile=inherit (CR Major round 2) CodeRabbit caught another precedence inversion: when { model_profile: 'inherit', models: { execution: 'opus' } } both resolvers short-circuited on `profile === 'inherit'` BEFORE the phase-type override could be honored. Result: model returned 'inherit' and reasoning_effort returned null — both contradicting the documented precedence where models[phase_type] wins over model_profile. Fix in resolveModelInternal: - Compute tier from phase-type FIRST. If phase-type is a valid alias, it wins. Otherwise, fall back to profile-derived tier OR 'inherit' (when profile === 'inherit'). - Gate the runtime-resolution branch on `tier !== 'inherit'` (was `profile !== 'inherit'`) so phase-type=opus can flip runtime mapping on even when profile=inherit. - Gate the inherit-return on `tier === 'inherit'` (was `profile === 'inherit'`). Fix in resolveReasoningEffortInternal: - Remove the `if (profile === 'inherit') return null;` early-return. - Compute tier from phase-type first, fall back to profile. If phase-type is explicitly 'inherit' OR the resolved tier is 'inherit', return null (no runtime entry maps to inherit). Tests added (5 new): - model: phase-type wins over profile=inherit (with explicit opus, with haiku for one phase + planner-without-slot still inheriting) - model: profile=inherit + no models block → all agents inherit (no regression on existing inherit semantics) - model: profile=inherit + models block but agent has no slot → that agent inherits, agents with slots get phase-type tier - effort: phase-type opus + profile=inherit → produces opus-tier effort, NOT null (the original bug) Verification: - 27/27 pass on regression test (24 prior + 3 model + 1 effort) - 6820/6820 full suite (5 net new) - lint-no-source-grep clean The effort test reads the expected value by running a profile-only opus config and comparing — semantic check, not hard-coded effort string. So runtime tier map changes don't break the test.
199 lines
8.1 KiB
Markdown
199 lines
8.1 KiB
Markdown
# 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.
|
|
|
|
```json
|
|
{
|
|
"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` | (reserved — no subagent today) |
|
|
| `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 |
|
|
| `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
|
|
|
|
### 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, Gemini 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:
|
|
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
```bash
|
|
# 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.
|
|
|
|
## Resolution Logic
|
|
|
|
Orchestrators resolve model before spawning:
|
|
|
|
```
|
|
1. Read .planning/config.json
|
|
2. Check model_overrides for agent-specific override
|
|
3. If no override, check models[phase_type] for a phase-type tier
|
|
(see §Per-Phase-Type Model Map for the agent → phase-type mapping)
|
|
4. If no phase-type slot, look up agent in profile table
|
|
5. Pass model parameter to Task call
|
|
```
|
|
|
|
The same precedence applies to `reasoning_effort` resolution on runtimes
|
|
that support it (Codex), so `model` and `reasoning_effort` always derive
|
|
from the same tier source — a `models[phase_type]` override flips both.
|
|
|
|
## Per-Agent Overrides
|
|
|
|
Override specific agents without changing the entire profile:
|
|
|
|
```json
|
|
{
|
|
"model_profile": "balanced",
|
|
"model_overrides": {
|
|
"gsd-executor": "opus",
|
|
"gsd-planner": "haiku"
|
|
}
|
|
}
|
|
```
|
|
|
|
Overrides take precedence over the profile. Valid values: `opus`, `sonnet`, `haiku`, `inherit`, or any fully-qualified model ID (e.g., `"o3"`, `"openai/o3"`, `"google/gemini-2.5-pro"`).
|
|
|
|
## Switching Profiles
|
|
|
|
Runtime: `/gsd-set-profile <profile>`
|
|
|
|
Per-project default: Set in `.planning/config.json`:
|
|
```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.
|