From a2a49ecd1409cbfe5aad49428f557e3d2e155568 Mon Sep 17 00:00:00 2001 From: Tibsfox Date: Sun, 5 Apr 2026 14:03:17 -0700 Subject: [PATCH] feat(model-profiles): add adaptive preset with role-based model assignment (#1806) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- get-shit-done/bin/lib/model-profiles.cjs | 34 +++++++++---------- .../references/model-profile-resolution.md | 2 ++ get-shit-done/references/model-profiles.md | 34 +++++++++++-------- get-shit-done/workflows/settings.md | 2 +- tests/model-profiles.test.cjs | 23 +++++++++++-- 5 files changed, 61 insertions(+), 34 deletions(-) diff --git a/get-shit-done/bin/lib/model-profiles.cjs b/get-shit-done/bin/lib/model-profiles.cjs index cd3e80e4..4bb89f98 100644 --- a/get-shit-done/bin/lib/model-profiles.cjs +++ b/get-shit-done/bin/lib/model-profiles.cjs @@ -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']); diff --git a/get-shit-done/references/model-profile-resolution.md b/get-shit-done/references/model-profile-resolution.md index 17582566..d465f726 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 `"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 diff --git a/get-shit-done/references/model-profiles.md b/get-shit-done/references/model-profiles.md index 5357c3f3..6bd4420b 100644 --- a/get-shit-done/references/model-profiles.md +++ b/get-shit-done/references/model-profiles.md @@ -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`) diff --git a/get-shit-done/workflows/settings.md b/get-shit-done/workflows/settings.md index e850bb01..aaa829cb 100644 --- a/get-shit-done/workflows/settings.md +++ b/get-shit-done/workflows/settings.md @@ -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, diff --git a/tests/model-profiles.test.cjs b/tests/model-profiles.test.cjs index f3f99e3a..03d36f7d 100644 --- a/tests/model-profiles.test.cjs +++ b/tests/model-profiles.test.cjs @@ -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;