From 698985feb1ab50f80f3f9bb4c6c9a44df0fa84ad Mon Sep 17 00:00:00 2001 From: jorge g Date: Sun, 15 Mar 2026 13:40:08 -0400 Subject: [PATCH] feat: add inherit model profile for OpenCode /model (#951) --- README.md | 5 ++- commands/gsd/set-profile.md | 4 +- docs/USER-GUIDE.md | 35 ++++++++-------- get-shit-done/bin/lib/core.cjs | 3 +- get-shit-done/bin/lib/verify.cjs | 2 +- .../references/model-profile-resolution.md | 2 + get-shit-done/references/model-profiles.md | 40 +++++++++++-------- get-shit-done/workflows/help.md | 3 +- get-shit-done/workflows/new-project.md | 10 +++-- get-shit-done/workflows/progress.md | 2 +- get-shit-done/workflows/set-profile.md | 5 ++- get-shit-done/workflows/settings.md | 7 ++-- tests/core.test.cjs | 15 ++++++- tests/verify-health.test.cjs | 28 +++++++++++++ 14 files changed, 111 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index 310c159af..dda6e04f0 100644 --- a/README.md +++ b/README.md @@ -528,7 +528,7 @@ You're never locked in. The system adapts. | Command | What it does | |---------|--------------| | `/gsd:settings` | Configure model profile and workflow agents | -| `/gsd:set-profile ` | Switch model profile (quality/balanced/budget) | +| `/gsd:set-profile ` | Switch model profile (quality/balanced/budget/inherit) | | `/gsd:add-todo [desc]` | Capture idea for later | | `/gsd:check-todos` | List pending todos | | `/gsd:debug [desc]` | Systematic debugging with persistent state | @@ -560,12 +560,15 @@ Control which Claude model each agent uses. Balance quality vs token spend. | `quality` | Opus | Opus | Sonnet | | `balanced` (default) | Opus | Sonnet | Sonnet | | `budget` | Sonnet | Sonnet | Haiku | +| `inherit` | Inherit | Inherit | Inherit | Switch profiles: ``` /gsd:set-profile budget ``` +Use `inherit` to follow the current runtime model selection (for example OpenCode `/model`). + Or configure via `/gsd:settings`. ### Workflow Agents diff --git a/commands/gsd/set-profile.md b/commands/gsd/set-profile.md index ab24458d1..ec35181dc 100644 --- a/commands/gsd/set-profile.md +++ b/commands/gsd/set-profile.md @@ -1,6 +1,6 @@ --- name: gsd:set-profile -description: Switch model profile for GSD agents (quality/balanced/budget) +description: Switch model profile for GSD agents (quality/balanced/budget/inherit) argument-hint: allowed-tools: - Read @@ -12,7 +12,7 @@ allowed-tools: Switch the model profile used by GSD agents. Controls which Claude model each agent uses, balancing quality vs token spend. Routes to the set-profile workflow which handles: -- Argument validation (quality/balanced/budget) +- Argument validation (quality/balanced/budget/inherit) - Config file creation if missing - Profile update in config.json - Confirmation with model table display diff --git a/docs/USER-GUIDE.md b/docs/USER-GUIDE.md index 3781b8967..5458cedc0 100644 --- a/docs/USER-GUIDE.md +++ b/docs/USER-GUIDE.md @@ -363,7 +363,7 @@ GSD stores project settings in `.planning/config.json`. Configure during `/gsd:n |---------|---------|---------|------------------| | `mode` | `interactive`, `yolo` | `interactive` | `yolo` auto-approves decisions; `interactive` confirms at each step | | `granularity` | `coarse`, `standard`, `fine` | `standard` | Phase granularity: how finely scope is sliced (3-5, 5-8, or 8-12 phases) | -| `model_profile` | `quality`, `balanced`, `budget` | `balanced` | Model tier for each agent (see table below) | +| `model_profile` | `quality`, `balanced`, `budget`, `inherit` | `balanced` | Model tier for each agent (see table below) | ### Planning Settings @@ -407,24 +407,25 @@ Disable these to speed up phases in familiar domains or when conserving tokens. ### Model Profiles (Per-Agent Breakdown) -| Agent | `quality` | `balanced` | `budget` | -|-------|-----------|------------|----------| -| gsd-planner | Opus | Opus | Sonnet | -| gsd-roadmapper | Opus | Sonnet | Sonnet | -| gsd-executor | Opus | Sonnet | Sonnet | -| gsd-phase-researcher | Opus | Sonnet | Haiku | -| gsd-project-researcher | Opus | Sonnet | Haiku | -| gsd-research-synthesizer | Sonnet | Sonnet | Haiku | -| gsd-debugger | Opus | Sonnet | Sonnet | -| gsd-codebase-mapper | Sonnet | Haiku | Haiku | -| gsd-verifier | Sonnet | Sonnet | Haiku | -| gsd-plan-checker | Sonnet | Sonnet | Haiku | -| gsd-integration-checker | Sonnet | Sonnet | Haiku | +| Agent | `quality` | `balanced` | `budget` | `inherit` | +|-------|-----------|------------|----------|-----------| +| gsd-planner | Opus | Opus | Sonnet | Inherit | +| gsd-roadmapper | Opus | Sonnet | Sonnet | Inherit | +| gsd-executor | Opus | Sonnet | Sonnet | Inherit | +| gsd-phase-researcher | Opus | Sonnet | Haiku | Inherit | +| gsd-project-researcher | Opus | Sonnet | Haiku | Inherit | +| gsd-research-synthesizer | Sonnet | Sonnet | Haiku | Inherit | +| gsd-debugger | Opus | Sonnet | Sonnet | Inherit | +| gsd-codebase-mapper | Sonnet | Haiku | Haiku | Inherit | +| gsd-verifier | Sonnet | Sonnet | Haiku | Inherit | +| gsd-plan-checker | Sonnet | Sonnet | Haiku | Inherit | +| gsd-integration-checker | Sonnet | Sonnet | Haiku | Inherit | **Profile philosophy:** -- **quality** -- Opus for all decision-making agents, Sonnet for read-only verification. Use when quota is available and the work is critical. -- **balanced** -- Opus only for planning (where architecture decisions happen), Sonnet for everything else. The default for good reason. -- **budget** -- Sonnet for anything that writes code, Haiku for research and verification. Use for high-volume work or less critical phases. +- **quality** -- Opus for all decision-making agents, Sonnet for read-only verification. Use when quota is available and the work is critical. +- **balanced** -- Opus only for planning (where architecture decisions happen), Sonnet for everything else. The default for good reason. +- **budget** -- Sonnet for anything that writes code, Haiku for research and verification. Use for high-volume work or less critical phases. +- **inherit** -- All agents use the current session model. Best when switching models dynamically (for example OpenCode `/model`). --- diff --git a/get-shit-done/bin/lib/core.cjs b/get-shit-done/bin/lib/core.cjs index 2da9c4cdb..dcc5221dc 100644 --- a/get-shit-done/bin/lib/core.cjs +++ b/get-shit-done/bin/lib/core.cjs @@ -405,9 +405,10 @@ function resolveModelInternal(cwd, agentType) { } // Fall back to profile lookup - const profile = config.model_profile || 'balanced'; + const profile = String(config.model_profile || 'balanced').toLowerCase(); const agentModels = MODEL_PROFILES[agentType]; if (!agentModels) return 'sonnet'; + if (profile === 'inherit') return 'inherit'; const resolved = agentModels[profile] || agentModels['balanced'] || 'sonnet'; return resolved === 'opus' ? 'inherit' : resolved; } diff --git a/get-shit-done/bin/lib/verify.cjs b/get-shit-done/bin/lib/verify.cjs index 7d4dc8e02..09edcb6b5 100644 --- a/get-shit-done/bin/lib/verify.cjs +++ b/get-shit-done/bin/lib/verify.cjs @@ -607,7 +607,7 @@ function cmdValidateHealth(cwd, options, raw) { const raw = fs.readFileSync(configPath, 'utf-8'); const parsed = JSON.parse(raw); // Validate known fields - const validProfiles = ['quality', 'balanced', 'budget']; + const validProfiles = ['quality', 'balanced', 'budget', 'inherit']; if (parsed.model_profile && !validProfiles.includes(parsed.model_profile)) { addIssue('warning', 'W004', `config.json: invalid model_profile "${parsed.model_profile}"`, `Valid values: ${validProfiles.join(', ')}`); } diff --git a/get-shit-done/references/model-profile-resolution.md b/get-shit-done/references/model-profile-resolution.md index 4e41aab9e..175825665 100644 --- a/get-shit-done/references/model-profile-resolution.md +++ b/get-shit-done/references/model-profile-resolution.md @@ -26,6 +26,8 @@ Task( **Note:** Opus-tier agents resolve to `"inherit"` (not `"opus"`). This causes the agent to use the parent session's model, avoiding conflicts with organization policies that may block specific opus versions. +If `model_profile` is `"inherit"`, all agents resolve to `"inherit"` (useful for OpenCode `/model`). + ## Usage 1. Resolve once at orchestration start diff --git a/get-shit-done/references/model-profiles.md b/get-shit-done/references/model-profiles.md index 3e9b326cf..e4d5f0649 100644 --- a/get-shit-done/references/model-profiles.md +++ b/get-shit-done/references/model-profiles.md @@ -1,23 +1,23 @@ # Model Profiles -Model profiles control which Claude model each GSD agent uses. This allows balancing quality vs token spend. +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` | -|-------|-----------|------------|----------| -| gsd-planner | opus | opus | sonnet | -| gsd-roadmapper | opus | sonnet | sonnet | -| gsd-executor | opus | sonnet | sonnet | -| gsd-phase-researcher | opus | sonnet | haiku | -| gsd-project-researcher | opus | sonnet | haiku | -| gsd-research-synthesizer | sonnet | sonnet | haiku | -| gsd-debugger | opus | sonnet | sonnet | -| gsd-codebase-mapper | sonnet | haiku | haiku | -| gsd-verifier | sonnet | sonnet | haiku | -| gsd-plan-checker | sonnet | sonnet | haiku | -| gsd-integration-checker | sonnet | sonnet | haiku | -| gsd-nyquist-auditor | sonnet | sonnet | haiku | +| Agent | `quality` | `balanced` | `budget` | `inherit` | +|-------|-----------|------------|----------|-----------| +| gsd-planner | opus | opus | sonnet | inherit | +| gsd-roadmapper | opus | sonnet | sonnet | inherit | +| gsd-executor | opus | sonnet | sonnet | inherit | +| gsd-phase-researcher | opus | sonnet | haiku | inherit | +| gsd-project-researcher | opus | sonnet | haiku | inherit | +| gsd-research-synthesizer | sonnet | sonnet | haiku | inherit | +| gsd-debugger | opus | sonnet | sonnet | inherit | +| gsd-codebase-mapper | sonnet | haiku | haiku | inherit | +| gsd-verifier | sonnet | sonnet | haiku | inherit | +| gsd-plan-checker | sonnet | sonnet | haiku | inherit | +| gsd-integration-checker | sonnet | sonnet | haiku | inherit | +| gsd-nyquist-auditor | sonnet | sonnet | haiku | inherit | ## Profile Philosophy @@ -37,6 +37,11 @@ Model profiles control which Claude model each GSD agent uses. This allows balan - Haiku for research and verification - Use when: conserving quota, high-volume work, less critical phases +**inherit** - Follow the current session model +- All agents resolve to `inherit` +- Best when you switch models interactively (for example OpenCode `/model`) +- Use when: you want GSD to follow your currently selected runtime model + ## Resolution Logic Orchestrators resolve model before spawning: @@ -62,7 +67,7 @@ Override specific agents without changing the entire profile: } ``` -Overrides take precedence over the profile. Valid values: `opus`, `sonnet`, `haiku`. +Overrides take precedence over the profile. Valid values: `opus`, `sonnet`, `haiku`, `inherit`. ## Switching Profiles @@ -91,3 +96,6 @@ Read-only exploration and pattern extraction. No reasoning required, just struct **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. diff --git a/get-shit-done/workflows/help.md b/get-shit-done/workflows/help.md index 6c11ab947..5e825bd38 100644 --- a/get-shit-done/workflows/help.md +++ b/get-shit-done/workflows/help.md @@ -301,7 +301,7 @@ Usage: `/gsd:plan-milestone-gaps` Configure workflow toggles and model profile interactively. - Toggle researcher, plan checker, verifier agents -- Select model profile (quality/balanced/budget) +- Select model profile (quality/balanced/budget/inherit) - Updates `.planning/config.json` Usage: `/gsd:settings` @@ -312,6 +312,7 @@ Quick switch model profile for GSD agents. - `quality` — Opus everywhere except verification - `balanced` — Opus for planning, Sonnet for execution (default) - `budget` — Sonnet for writing, Haiku for research/verification +- `inherit` — Use current session model for all agents (OpenCode `/model`) Usage: `/gsd:set-profile budget` diff --git a/get-shit-done/workflows/new-project.md b/get-shit-done/workflows/new-project.md index 4e52ca658..7db0e6c6b 100644 --- a/get-shit-done/workflows/new-project.md +++ b/get-shit-done/workflows/new-project.md @@ -159,7 +159,8 @@ AskUserQuestion([ options: [ { label: "Balanced (Recommended)", description: "Sonnet for most agents — good quality/cost ratio" }, { label: "Quality", description: "Opus for research/roadmap — higher cost, deeper analysis" }, - { label: "Budget", description: "Haiku where possible — fastest, lowest cost" } + { label: "Budget", description: "Haiku where possible — fastest, lowest cost" }, + { label: "Inherit", description: "Use the current session model for all agents (OpenCode /model)" } ] } ]) @@ -173,7 +174,7 @@ Create `.planning/config.json` with mode set to "yolo": "granularity": "[selected]", "parallelization": true|false, "commit_docs": true|false, - "model_profile": "quality|balanced|budget", + "model_profile": "quality|balanced|budget|inherit", "workflow": { "research": true|false, "plan_check": true|false, @@ -459,7 +460,8 @@ questions: [ options: [ { label: "Balanced (Recommended)", description: "Sonnet for most agents — good quality/cost ratio" }, { label: "Quality", description: "Opus for research/roadmap — higher cost, deeper analysis" }, - { label: "Budget", description: "Haiku where possible — fastest, lowest cost" } + { label: "Budget", description: "Haiku where possible — fastest, lowest cost" }, + { label: "Inherit", description: "Use the current session model for all agents (OpenCode /model)" } ] } ] @@ -473,7 +475,7 @@ Create `.planning/config.json` with all settings: "granularity": "coarse|standard|fine", "parallelization": true|false, "commit_docs": true|false, - "model_profile": "quality|balanced|budget", + "model_profile": "quality|balanced|budget|inherit", "workflow": { "research": true|false, "plan_check": true|false, diff --git a/get-shit-done/workflows/progress.md b/get-shit-done/workflows/progress.md index b07563642..83bf5825b 100644 --- a/get-shit-done/workflows/progress.md +++ b/get-shit-done/workflows/progress.md @@ -98,7 +98,7 @@ Present: # [Project Name] **Progress:** {PROGRESS_BAR} -**Profile:** [quality/balanced/budget] +**Profile:** [quality/balanced/budget/inherit] ## Recent Work - [Phase X, Plan Y]: [what was accomplished - 1 line from summary-extract] diff --git a/get-shit-done/workflows/set-profile.md b/get-shit-done/workflows/set-profile.md index 04ea06230..0f3c32115 100644 --- a/get-shit-done/workflows/set-profile.md +++ b/get-shit-done/workflows/set-profile.md @@ -12,9 +12,9 @@ Read all files referenced by the invoking prompt's execution_context before star Validate argument: ``` -if $ARGUMENTS.profile not in ["quality", "balanced", "budget"]: +if $ARGUMENTS.profile not in ["quality", "balanced", "budget", "inherit"]: Error: Invalid profile "$ARGUMENTS.profile" - Valid profiles: quality, balanced, budget + Valid profiles: quality, balanced, budget, inherit EXIT ``` @@ -69,6 +69,7 @@ Map profile names: - quality: use "quality" column from MODEL_PROFILES - balanced: use "balanced" column from MODEL_PROFILES - budget: use "budget" column from MODEL_PROFILES +- inherit: use "inherit" for all agents diff --git a/get-shit-done/workflows/settings.md b/get-shit-done/workflows/settings.md index 8829da90b..08859d263 100644 --- a/get-shit-done/workflows/settings.md +++ b/get-shit-done/workflows/settings.md @@ -48,7 +48,8 @@ AskUserQuestion([ options: [ { label: "Quality", description: "Opus everywhere except verification (highest cost)" }, { label: "Balanced (Recommended)", description: "Opus for planning, Sonnet for execution/verification" }, - { label: "Budget", description: "Sonnet for writing, Haiku for research/verification (lowest cost)" } + { label: "Budget", description: "Sonnet for writing, Haiku for research/verification (lowest cost)" }, + { label: "Inherit", description: "Use current session model for all agents (best for OpenCode /model)" } ] }, { @@ -136,7 +137,7 @@ Merge new settings into existing config.json: ```json { ...existing_config, - "model_profile": "quality" | "balanced" | "budget", + "model_profile": "quality" | "balanced" | "budget" | "inherit", "workflow": { "research": true/false, "plan_check": true/false, @@ -210,7 +211,7 @@ Display: | Setting | Value | |----------------------|-------| -| Model Profile | {quality/balanced/budget} | +| Model Profile | {quality/balanced/budget/inherit} | | Plan Researcher | {On/Off} | | Plan Checker | {On/Off} | | Execution Verifier | {On/Off} | diff --git a/tests/core.test.cjs b/tests/core.test.cjs index ae9ce8641..34b9a266f 100644 --- a/tests/core.test.cjs +++ b/tests/core.test.cjs @@ -147,7 +147,7 @@ describe('resolveModelInternal', () => { describe('model profile structural validation', () => { test('all known agents resolve to a valid string for each profile', () => { const knownAgents = ['gsd-planner', 'gsd-executor', 'gsd-phase-researcher', 'gsd-codebase-mapper']; - const profiles = ['quality', 'balanced', 'budget']; + const profiles = ['quality', 'balanced', 'budget', 'inherit']; const validValues = ['inherit', 'sonnet', 'haiku', 'opus']; for (const profile of profiles) { @@ -161,6 +161,14 @@ describe('resolveModelInternal', () => { } } }); + + test('inherit profile forces all known agents to inherit model', () => { + const knownAgents = ['gsd-planner', 'gsd-executor', 'gsd-phase-researcher', 'gsd-codebase-mapper']; + writeConfig({ model_profile: 'inherit' }); + for (const agent of knownAgents) { + assert.strictEqual(resolveModelInternal(tmpDir, agent), 'inherit'); + } + }); }); describe('override precedence', () => { @@ -195,6 +203,11 @@ describe('resolveModelInternal', () => { assert.strictEqual(resolveModelInternal(tmpDir, 'gsd-nonexistent'), 'sonnet'); }); + test('returns sonnet for unknown agent type even with inherit profile', () => { + writeConfig({ model_profile: 'inherit' }); + assert.strictEqual(resolveModelInternal(tmpDir, 'gsd-nonexistent'), 'sonnet'); + }); + test('defaults to balanced profile when model_profile missing', () => { writeConfig({}); // balanced profile, gsd-planner -> opus -> inherit diff --git a/tests/verify-health.test.cjs b/tests/verify-health.test.cjs index 69a9b5cc7..c06a8404a 100644 --- a/tests/verify-health.test.cjs +++ b/tests/verify-health.test.cjs @@ -255,6 +255,34 @@ describe('validate health command', () => { ); }); + test('accepts inherit model_profile as valid', () => { + writeMinimalProjectMd(tmpDir); + writeMinimalRoadmap(tmpDir, ['1']); + writeMinimalStateMd(tmpDir); + fs.writeFileSync( + path.join(tmpDir, '.planning', 'config.json'), + JSON.stringify({ + model_profile: 'inherit', + workflow: { + research: true, + plan_check: true, + verifier: true, + nyquist_validation: true, + }, + }) + ); + fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '01-a'), { recursive: true }); + + const result = runGsdTools('validate health', tmpDir); + assert.ok(result.success, `Command failed: ${result.error}`); + + const output = JSON.parse(result.output); + assert.ok( + !output.warnings.some(w => w.code === 'W004'), + `Should not warn for inherit model_profile: ${JSON.stringify(output.warnings)}` + ); + }); + // ─── Check 6: Phase directory naming (NN-name format) ───────────────────── test('warns about incorrectly named phase directories', () => {