feat: add inherit model profile for OpenCode /model (#951)
This commit is contained in:
@@ -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 <profile>` | Switch model profile (quality/balanced/budget) |
|
||||
| `/gsd:set-profile <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
|
||||
|
||||
@@ -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: <profile>
|
||||
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
|
||||
|
||||
@@ -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`).
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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(', ')}`);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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`
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
```
|
||||
</step>
|
||||
@@ -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
|
||||
</step>
|
||||
|
||||
</process>
|
||||
|
||||
@@ -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} |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
Reference in New Issue
Block a user