# Per-task model override via MCP + cheapest-model prompt guidance Date: 2026-06-09 ## Goal Let Claude pick the model for each task it generates (planning subtasks, improvement follow-ups, external task creation) directly at creation time via MCP, and instruct Claude — in the relevant prompts — to choose the *cheapest* model that can do the job well. ## Background - `TaskEntity.Model` (nullable) already exists and is resolved task → list-config → global default in `TaskRunner.ResolveConfigAsync`, then passed to the CLI as `--model` by `ClaudeArgsBuilder`. - Today the model can only be set *after* creation via `set_task_config` (`ConfigMcpTools.SetTaskConfig`). The creation tools (`CreateChildTask`, `SuggestImprovement`, `AddTask`) accept no model, so assigning one is a two-call dance. - `ModelRegistry.Aliases = ["sonnet","opus","haiku"]`; no cost ordering or validation helper exists. No schema change is required — only plumbing a `model` argument through the creation paths plus prompt edits. ## Decisions - **Validation:** strict alias-only. `model` must be one of haiku/sonnet/opus (case-insensitive); blank/null means "inherit" (no override); anything else throws an MCP error so Claude self-corrects immediately rather than the task failing later at CLI runtime. - **`AddSubtask` is out of scope:** it creates a `SubtaskEntity` (a checklist step), which is never independently executed — a model there is a no-op. - **Improvement-child prompt:** the child's model is fixed at filing time and it cannot re-pick, so only a one-line "this is an intentionally small/cheap unit — stay minimal" reminder is added. The real model-choice instruction lives in the main system prompt's SuggestImprovement guidance. ## Cost ordering & heuristic (single source: `ModelRegistry.ByCostAscending`) `haiku < sonnet < opus` - **haiku** — trivial/mechanical: doc tweaks, simple renames, small localized edits. - **sonnet** — normal coding work (default). - **opus** — complex architecture, cross-cutting changes, hard debugging. ## Changes 1. **`ClaudeDo.Data/Models/ModelRegistry.cs`** - `ByCostAscending = ["haiku","sonnet","opus"]`. - `string? NormalizeAlias(string? model)` — trim; null/blank → null; case-insensitive match → canonical lowercase alias; else throw `ArgumentException` with the allowed list. 2. **`TaskRepository.CreateChildAsync`** — add optional `string? model = null`; set `child.Model = ModelRegistry.NormalizeAlias(model)`. Single choke-point for both child-creation MCP tools. 3. **MCP creation tools** (add `model` param, document in `[Description]`): - `PlanningMcpService.CreateChildTask` → forward to `CreateChildAsync`. - `TaskRunMcpService.SuggestImprovement` → forward to `CreateChildAsync`. - `ExternalMcpService.AddTask` → `NormalizeAlias` then set `entity.Model`. 4. **Prompts (`PromptFiles.cs`)** - `PlanningSystemDefault` — instruct the planner to pass each `CreateChildTask` the cheapest capable model (with the ordering/heuristic). - `SystemDefault` (Out-of-scope improvements) — when filing via `SuggestImprovement`, pass the cheapest capable `model`. - `ImprovementChildDefault` — one-line minimality reminder. 5. **Tests** (no real CLI): - `NormalizeAlias`: valid aliases (any case), blank/null → null, unknown → throws. - `CreateChildTask` / `SuggestImprovement` / `AddTask` persist the model; invalid model is rejected. ## Out of scope - No DB migration. No locale changes (prompts and MCP descriptions are not localized). No UI changes (existing per-task model display already covers it).