# 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).
