81 lines
3.6 KiB
Markdown
81 lines
3.6 KiB
Markdown
# 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).
|