feat(model-profiles): add adaptive preset with role-based model assignment (#1806)

Add a fourth model profile preset that assigns models by agent role:
opus for planning and debugging (reasoning-critical), sonnet for
execution and research (follows instructions), haiku for mapping and
checking (high volume, structured output).

This gives solo developers on paid API tiers a cost-effective middle
ground — quality where it matters most (planning) without overspending
on mechanical tasks (mapping, checking).

Per-agent overrides via model_overrides continue to take precedence
over any profile preset, including adaptive.

Closes #1713

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Tibsfox
2026-04-05 14:03:17 -07:00
committed by GitHub
parent 6d5a66f64e
commit a2a49ecd14
5 changed files with 61 additions and 34 deletions

View File

@@ -7,23 +7,23 @@
* would be faster, use fewer tokens, and be less error-prone).
*/
const MODEL_PROFILES = {
'gsd-planner': { quality: 'opus', balanced: 'opus', budget: 'sonnet' },
'gsd-roadmapper': { quality: 'opus', balanced: 'sonnet', budget: 'sonnet' },
'gsd-executor': { quality: 'opus', balanced: 'sonnet', budget: 'sonnet' },
'gsd-phase-researcher': { quality: 'opus', balanced: 'sonnet', budget: 'haiku' },
'gsd-project-researcher': { quality: 'opus', balanced: 'sonnet', budget: 'haiku' },
'gsd-research-synthesizer': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku' },
'gsd-debugger': { quality: 'opus', balanced: 'sonnet', budget: 'sonnet' },
'gsd-codebase-mapper': { quality: 'sonnet', balanced: 'haiku', budget: 'haiku' },
'gsd-verifier': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku' },
'gsd-plan-checker': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku' },
'gsd-integration-checker': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku' },
'gsd-nyquist-auditor': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku' },
'gsd-ui-researcher': { quality: 'opus', balanced: 'sonnet', budget: 'haiku' },
'gsd-ui-checker': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku' },
'gsd-ui-auditor': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku' },
'gsd-doc-writer': { quality: 'opus', balanced: 'sonnet', budget: 'haiku' },
'gsd-doc-verifier': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku' },
'gsd-planner': { quality: 'opus', balanced: 'opus', budget: 'sonnet', adaptive: 'opus' },
'gsd-roadmapper': { quality: 'opus', balanced: 'sonnet', budget: 'sonnet', adaptive: 'sonnet' },
'gsd-executor': { quality: 'opus', balanced: 'sonnet', budget: 'sonnet', adaptive: 'sonnet' },
'gsd-phase-researcher': { quality: 'opus', balanced: 'sonnet', budget: 'haiku', adaptive: 'sonnet' },
'gsd-project-researcher': { quality: 'opus', balanced: 'sonnet', budget: 'haiku', adaptive: 'sonnet' },
'gsd-research-synthesizer': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku', adaptive: 'haiku' },
'gsd-debugger': { quality: 'opus', balanced: 'sonnet', budget: 'sonnet', adaptive: 'opus' },
'gsd-codebase-mapper': { quality: 'sonnet', balanced: 'haiku', budget: 'haiku', adaptive: 'haiku' },
'gsd-verifier': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku', adaptive: 'sonnet' },
'gsd-plan-checker': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku', adaptive: 'haiku' },
'gsd-integration-checker': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku', adaptive: 'haiku' },
'gsd-nyquist-auditor': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku', adaptive: 'haiku' },
'gsd-ui-researcher': { quality: 'opus', balanced: 'sonnet', budget: 'haiku', adaptive: 'sonnet' },
'gsd-ui-checker': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku', adaptive: 'haiku' },
'gsd-ui-auditor': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku', adaptive: 'haiku' },
'gsd-doc-writer': { quality: 'opus', balanced: 'sonnet', budget: 'haiku', adaptive: 'sonnet' },
'gsd-doc-verifier': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku', adaptive: 'haiku' },
};
const VALID_PROFILES = Object.keys(MODEL_PROFILES['gsd-planner']);

View File

@@ -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 `"adaptive"`, agents resolve to role-based assignments (opus/sonnet/haiku based on agent type).
If `model_profile` is `"inherit"`, all agents resolve to `"inherit"` (useful for OpenCode `/model`).
## Usage

View File

@@ -4,20 +4,20 @@ Model profiles control which Claude model each GSD agent uses. This allows balan
## Profile Definitions
| 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 |
| 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 |
## Profile Philosophy
@@ -37,6 +37,12 @@ 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
**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`)

View File

@@ -174,7 +174,7 @@ Merge new settings into existing config.json:
```json
{
...existing_config,
"model_profile": "quality" | "balanced" | "budget" | "inherit",
"model_profile": "quality" | "balanced" | "budget" | "adaptive" | "inherit",
"workflow": {
"research": true/false,
"plan_check": true/false,

View File

@@ -31,11 +31,12 @@ describe('MODEL_PROFILES', () => {
}
});
test('every agent has quality, balanced, and budget profiles', () => {
test('every agent has quality, balanced, budget, and adaptive profiles', () => {
for (const [agent, profiles] of Object.entries(MODEL_PROFILES)) {
assert.ok(profiles.quality, `${agent} missing quality profile`);
assert.ok(profiles.balanced, `${agent} missing balanced profile`);
assert.ok(profiles.budget, `${agent} missing budget profile`);
assert.ok(profiles.adaptive, `${agent} missing adaptive profile`);
}
});
@@ -65,7 +66,7 @@ describe('MODEL_PROFILES', () => {
describe('VALID_PROFILES', () => {
test('contains quality, balanced, and budget', () => {
assert.deepStrictEqual(VALID_PROFILES.sort(), ['balanced', 'budget', 'quality']);
assert.deepStrictEqual(VALID_PROFILES.sort(), ['adaptive', 'balanced', 'budget', 'quality']);
});
test('is derived from MODEL_PROFILES keys', () => {
@@ -96,6 +97,24 @@ describe('getAgentToModelMapForProfile', () => {
assert.strictEqual(map['gsd-executor'], 'opus');
});
test('returns correct models for adaptive profile', () => {
const map = getAgentToModelMapForProfile('adaptive');
assert.strictEqual(map['gsd-planner'], 'opus', 'planner should use opus in adaptive');
assert.strictEqual(map['gsd-debugger'], 'opus', 'debugger should use opus in adaptive');
assert.strictEqual(map['gsd-executor'], 'sonnet', 'executor should use sonnet in adaptive');
assert.strictEqual(map['gsd-codebase-mapper'], 'haiku', 'mapper should use haiku in adaptive');
assert.strictEqual(map['gsd-plan-checker'], 'haiku', 'checker should use haiku in adaptive');
});
test('resolution order: override > profile > default', () => {
// This tests the conceptual resolution — actual runtime test is in resolveModelInternal
const map = getAgentToModelMapForProfile('adaptive');
// Profile gives planner opus
assert.strictEqual(map['gsd-planner'], 'opus');
// An override would take precedence (tested via resolveModelInternal in model-alias-map tests)
// Default fallback is 'sonnet' (core.cjs line 1320)
});
test('returns all agents in the map', () => {
const map = getAgentToModelMapForProfile('balanced');
const agentCount = Object.keys(MODEL_PROFILES).length;