Scrum4Me/docs/plans/job-model-selection.md
Janpeter Visser 8c63ba377d
feat(PBI-67): model + mode-selectie per ClaudeJob-kind (#169)
* feat(PBI-67/ST-1297): datamodel-velden voor job-model-selectie

Voegt 8 nieuwe optionele velden toe verspreid over Product, Task en
ClaudeJob ten dienste van de override-cascade:

  task.requires_opus → job.requested_* → product.preferred_* → kind-default

Bestaande rijen krijgen NULL (Product/ClaudeJob) of false (Task) en
vallen daarmee terug op de kind-defaults uit de resolver (ST-1298).

Migration is additief: alleen ALTER TABLE ADD COLUMN, geen RENAME of
DROP. Bestaande factories en seed-script blijven werken zonder
aanpassing omdat alle nieuwe velden default-waardes hebben.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(PBI-67/ST-1299): job-config snapshot bij enqueue + worker-flag-runbook

T-789: Snapshot van resolved JobConfig in ClaudeJob.requested_*
bij elke job-creatie. Helper in lib/job-config-snapshot.ts laadt
product (preferred_*) en task (requires_opus) en draait de resolver
uit lib/job-config.ts (mirror van scrum4me-mcp/src/lib/job-config.ts —
zelfde matrix, sync-comment in bestand). Toegepast op alle 5
enqueue-locaties:

  - actions/user-questions.ts          (PLAN_CHAT)
  - actions/sprint-runs.ts × 3         (SPRINT_IMPLEMENTATION x2,
                                        TASK_IMPLEMENTATION loop)
  - actions/ideas.ts                   (IDEA_GRILL / IDEA_MAKE_PLAN)

Test-mocks uitgebreid met product.findUnique en task.findUnique zodat
de helper bij unit tests veilig terugvalt op kind-defaults (alle 563
tests groen).

T-790: Sectie 'Config doorgeven aan Claude Code' toegevoegd aan
docs/runbooks/worker-idempotency.md met CLI-flag-mapping en de
verwachte aanroep per kind. Forward-link naar
docs/runbooks/job-model-selection.md (volgt in T-794).

Plus: docs/plans/job-model-selection.md (de approved plan-doc).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(PBI-67/ST-1300): cost-attribution voor thinking-tokens + admin UI

T-792: token-stats + token-history rekenen actual_thinking_tokens nu
mee in de totale kosten (tegen input-rate, conform Anthropic billing).
COALESCE-veilig zodat oude rijen 0 bijdragen i.p.v. NaN. Nieuwe export
`getTokenStatsByKind` aggregeert tokens en kosten per ClaudeJob.kind
zodat we relatieve uitgaven van IDEA_GRILL/IDEA_MAKE_PLAN/PLAN_CHAT/
TASK_IMPLEMENTATION/SPRINT_IMPLEMENTATION kunnen zien.

T-793: admin/jobs Kosten-tabel toont:
  - Nieuwe kolom 'Thinking' (aantal verbruikte thinking-tokens)
  - Mismatch-marker (rood) als requested_model afwijkt van actuele
    model_id — duidt op een worker die de CLI-flag niet doorgaf.
    Tooltip toont aangevraagd model. Geen Sentry/log-noise.

Page-level cost-berekening volgt dezelfde formule (input_price ×
thinking_tokens). 563 tests groen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(PBI-67/ST-1301): runbook + CLAUDE.md updates voor model/mode-selectie

T-794: Nieuwe runbook docs/runbooks/job-model-selection.md met
override-cascade, kind-default-matrix, override-voorbeelden,
auditspoor en cost-attribution-formule. 107 regels.

T-795: CLAUDE.md hardstop-bullet voor 'Model/mode per ClaudeJob'
(verwijst naar nieuwe runbook) + patterns-quickref-rij voor
job-config resolver. CLAUDE.md blijft 139 regels (≤ 150).

T-796: docs:check-links groen — 108 files, geen broken links. Twee
externe-repo verwijzingen (scrum4me-mcp/...) ge-de-linked tot plain
text omdat de check-links script de zustertree niet traverseert; de
referenties blijven leesbaar.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 11:20:10 +02:00

152 lines
9.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Plan: model + mode-selectie per ClaudeJob-kind
## Context
`ClaudeJob` heeft 5 kinds (`TASK_IMPLEMENTATION`, `IDEA_GRILL`, `IDEA_MAKE_PLAN`, `PLAN_CHAT`, `SPRINT_IMPLEMENTATION`) maar er is **geen per-kind model-/mode-configuratie**. Alle jobs draaien op de Claude Code CLI default. `ClaudeJob.model_id` ([prisma/schema.prisma](../../prisma/schema.prisma)) wordt alleen post-hoc gevuld voor kostenberekening via [lib/insights/token-stats.ts](../../lib/insights/token-stats.ts) en `model_prices` (PBI-66).
Probleem: een grill-sessie verdient meer thinking-budget en geen file-edits, terwijl een task-implementation acceptEdits/bypassPermissions in een worktree wil. Nu is dat allemaal hetzelfde — wat leidt tot:
- Te dure runs (Opus voor triviale Haiku-waardige taken)
- Te schrale runs (Sonnet zonder thinking voor architectuurkeuze in `IDEA_MAKE_PLAN`)
- Geen cost-attribution per kind voor budgettering
- Geen product-level override voor klanten met eigen model-voorkeur
**Doel:** een resolver die per `ClaudeJob` bepaalt: model, thinking-budget, permission-mode, max_turns, allowed_tools — gebaseerd op kind-defaults met overrides per product en per job. De worker geeft de geresolveerde config door aan Claude Code via CLI-flags.
**Niet-doel:** de runtime vervangen door de Claude Agent SDK. Worker blijft Claude Code CLI; we bouwen erbovenop.
## Aanpak
### 1. Datamodel-uitbreiding
| Tabel | Veld | Type | Doel |
|---|---|---|---|
| `Product` | `preferred_model` | `String?` | Product-brede default (bv. "alle taken op Sonnet voor budget") |
| `Product` | `thinking_budget_default` | `Int?` | Idem voor thinking |
| `Task` | `requires_opus` | `Boolean @default(false)` | Per-task escalatie (cross-file refactor) |
| `ClaudeJob` | `requested_model` | `String?` | Snapshot van resolved model (audit) |
| `ClaudeJob` | `requested_thinking_budget` | `Int?` | Snapshot van resolved budget |
| `ClaudeJob` | `requested_permission_mode` | `String?` | Snapshot van resolved mode |
| `ClaudeJob` | `actual_thinking_tokens` | `Int?` | Werkelijk verbruikte thinking-tokens (cost-attribution) |
Migration is additief. Bestaande rijen krijgen `NULL` → resolver valt terug op kind-defaults.
### 2. Centrale resolver
**Locatie:** `scrum4me-mcp/src/lib/job-config.ts` (nieuw bestand in MCP-repo).
```ts
type JobConfig = {
model: 'claude-opus-4-7' | 'claude-sonnet-4-6' | 'claude-haiku-4-5-20251001'
thinking_budget: number // 0 = uit
permission_mode: 'plan' | 'default' | 'acceptEdits' | 'bypassPermissions'
max_turns: number | null // null = onbegrensd
allowed_tools: string[] | null // null = alle
}
function resolveJobConfig(job: ClaudeJob, product: Product, task?: Task): JobConfig
```
**Resolutie-volgorde** (eerste match wint):
1. `task.requires_opus === true` → forceer model = `claude-opus-4-7`
2. `job.requested_*` (al ingevuld door enqueue-laag)
3. `product.preferred_*`
4. **Kind-default** uit deze tabel:
| Kind | Model | Thinking | Permission | max_turns | allowed_tools |
|---|---|---|---|---|---|
| `IDEA_GRILL` | sonnet-4-6 | 12000 | `plan` | 15 | Read, Grep, Glob, WebSearch, AskUserQuestion |
| `IDEA_MAKE_PLAN` | opus-4-7 | 24000 | `plan` | 20 | Read, Grep, Glob, WebSearch, AskUserQuestion, Write |
| `PLAN_CHAT` | sonnet-4-6 | 6000 | `plan` | 5 | Read, Grep, AskUserQuestion |
| `TASK_IMPLEMENTATION` | sonnet-4-6 | 6000 | `bypassPermissions` | 50 | null |
| `SPRINT_IMPLEMENTATION` | sonnet-4-6 | 6000 | `bypassPermissions` | null | null |
`bypassPermissions` is verdedigbaar omdat task/sprint-implementatie altijd in een geïsoleerde git-worktree draait (zie [docs/runbooks/branch-and-commit.md](../runbooks/branch-and-commit.md)). Voor productie-omgevingen kan `Product.preferred_permission_mode = 'acceptEdits'` als opt-in.
### 3. `wait_for_job` response uitbreiden
Huidig: `wait_for_job` returnt `{ job_id, kind, context }`. Toevoegen: `config: JobConfig`.
Worker leest `config` en spawnt Claude Code subprocess met:
```
claude --model {config.model} \
--permission-mode {config.permission_mode} \
--thinking-budget {config.thinking_budget} \
[--max-turns {config.max_turns}] \
[--allowed-tools "{config.allowed_tools.join(',')}"]
```
Documentatie van vlaggen verwijst naar [Claude Code model-config](https://code.claude.com/docs/en/model-config). Als een vlag (nog) niet bestaat in de huidige CLI: skippen + log-warning, niet hardcrashen.
### 4. Audit + cost-attribution
Bij job-completion (in `update_job_status` MCP-tool):
- `actual_thinking_tokens` schrijven naar `ClaudeJob` (al beschikbaar in Claude Code result-payload)
- Bestaande `model_id`-update behouden (cost-berekening via `model_prices`)
Token-stats-laag ([lib/insights/token-stats.ts](../../lib/insights/token-stats.ts)) uitbreiden:
- Aggregeren per kind (nu per dag/product) — feature-gate tot ST-N nodig
- Thinking-tokens apart tonen (andere prijs dan output-tokens)
### 5. Documentatie
- **Nieuw:** [docs/runbooks/job-model-selection.md](../runbooks/job-model-selection.md) — de matrix + wanneer je override gebruikt
- **CLAUDE.md** Hardstop-bullet: "Model/mode per ClaudeJob: kind-default → product → task — zie runbook"
- **Patterns quickref** in CLAUDE.md: regel toevoegen voor `job-config.ts` resolver-pattern
## Voorgestelde PBI/story-breakdown
Voor de Scrum4Me-MCP `create_pbi` / `create_story` / `create_task` ronde na goedkeuring:
**PBI:** "Model + mode-selectie per ClaudeJob-kind"
| Story | Doel | Tasks (indicatief) |
|---|---|---|
| **ST-1: Datamodel + migration** | Velden op Product/Task/ClaudeJob | Schema wijzigen · migration · Prisma generate · seed/factories updaten |
| **ST-2: Resolver in scrum4me-mcp** | `job-config.ts` met kind-defaults + override-cascade | Resolver-functie · unit tests per kind · export voor MCP-tools |
| **ST-3: `wait_for_job` integratie** | Config in response + snapshot in `requested_*` | Tool-output uitbreiden · enqueue-laag snapshot · worker-flag-passing documenteren |
| **ST-4: Audit + cost-attribution** | `actual_thinking_tokens` opslaan + tonen | `update_job_status` uitbreiden · token-stats per kind · admin/jobs UI-kolom |
| **ST-5: Documentatie** | Runbook + CLAUDE.md updates | runbook schrijven · CLAUDE.md hardstop · patterns-row |
ST-1 → ST-2 → ST-3 zijn de kritieke pad-stories. ST-4 en ST-5 kunnen parallel met ST-3.
## Bestanden
| Bestand | Repo | Actie |
|---|---|---|
| `prisma/schema.prisma` | scrum4me | **Wijzigen** — 7 nieuwe velden |
| `prisma/migrations/<ts>_job_model_selection/` | scrum4me | **Nieuw** — additive migration |
| `scrum4me-mcp/src/lib/job-config.ts` | scrum4me-mcp | **Nieuw** — resolver |
| `scrum4me-mcp/src/lib/job-config.test.ts` | scrum4me-mcp | **Nieuw** — unit tests per kind |
| `scrum4me-mcp/src/tools/wait-for-job.ts` | scrum4me-mcp | **Wijzigen** — config in response |
| `scrum4me-mcp/src/tools/update-job-status.ts` | scrum4me-mcp | **Wijzigen**`actual_thinking_tokens` |
| `lib/insights/token-stats.ts` | scrum4me | **Wijzigen** — per-kind aggregatie + thinking-prijs |
| `actions/admin/jobs.ts` + UI-kolom | scrum4me | **Wijzigen** — model/mode tonen |
| `docs/runbooks/job-model-selection.md` | scrum4me | **Nieuw** — runbook |
| `CLAUDE.md` | scrum4me | **Wijzigen** — hardstop-bullet + patterns-row |
## Verificatie
Per story:
- **ST-1:** `npm run verify` slaagt na schema-wijziging; migration runt clean op test-DB
- **ST-2:** unit tests dekken alle 5 kinds × 4 cascade-niveaus (default/product/job/task)
- **ST-3:** integratietest: enqueue een `IDEA_GRILL` met product-override → `wait_for_job` returnt config met override toegepast
- **ST-4:** end-to-end: run een dummy-job, verifieer dat `actual_thinking_tokens` ingevuld wordt en dat token-stats het kostbedrag correct rekent (input + output + thinking-input rate)
- **ST-5:** `npm run docs:check-links` groen; CLAUDE.md ≤ 150 regels
End-to-end-validatie van het geheel:
1. Maak een nieuw idee → `IDEA_GRILL`-job → controleer dat de worker met `--permission-mode plan` en `--thinking-budget 12000` start
2. Approve het idee → `IDEA_MAKE_PLAN`-job → controleer Opus-aanroep met thinking 24000
3. Sprint starten → `SPRINT_IMPLEMENTATION` met `bypassPermissions` in worktree
4. Admin-jobs-pagina toont per job het gebruikte model + thinking-tokens
## Vastgelegde beslissingen (review-uitkomst)
1. **`bypassPermissions` als default voor implement-kinds** (TASK_IMPLEMENTATION, SPRINT_IMPLEMENTATION). Verdedigbaar door git-worktree-isolatie. `Product.preferred_permission_mode` blijft beschikbaar als opt-in voor productie-product
2. **Opus-cost-controle = per-task** via `Task.requires_opus`-flag. Géén product-budget, géén automatische Opus-escalatie. Ad-hoc beslissing per taak
3. **`PLAN_CHAT` runtime bevestigd: Claude Code CLI** — `wait_for_job` (`scrum4me-mcp/src/tools/wait-for-job.ts:386`) selecteert `IDEA_GRILL`, `IDEA_MAKE_PLAN` én `PLAN_CHAT` uit dezelfde queue. Resolver past 1:1, geen aparte runtime-route
4. **`wait_for_job`-response: pure additief** (geen `protocol_version`-veld). Worker negeert onbekende velden veilig; mismatch is operationeel zichtbaar via `model_id` in token-stats. Geen multi-tenant fleet → geen versioning-overhead nodig
---
Bij goedkeuring: PBI + 5 stories + ~20 tasks aanmaken via `mcp__scrum4me__create_pbi/story/task`. Volgorde: ST-1 → ST-2 → ST-3 → (ST-4 ‖ ST-5).