* docs(cleanup): archief verouderde plannen, backlog en root-duplicaten
- 6 plans naar docs/old/plans/ (PBI-11/75/78, user-settings-store, Local github setup, lees-de-readme — laatste was verkeerde repo)
- docs/backlog/ naar docs/old/backlog/ (pre-MCP statische registry; live werk loopt via Scrum4Me-MCP)
- 6 root-level duplicaten naar docs/old/ (functional, {pbi,story,task}-dialog, product-backlog, backlog)
- 2 landing plans (niet uitgevoerd) krijgen archived: true frontmatter — blijven op plek maar uit INDEX
- scripts/generate-docs-index.mjs: skip docs/old/** + skip archived: true
- CLAUDE.md: rijen docs/backlog/, docs/plans/<key>-*.md, docs/manual/ weg; Track B-sectie verwijderd
- README.md / CHANGELOG.md / docs/plans/v1-readiness.md: link-fixes naar nieuwe locaties
Verify groen (lint + typecheck + 718 tests). docs/INDEX.md geregenereerd.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(cleanup): registreer handmatige verplaatsingen en fix referenties
- 4 plans verplaatst naar docs/old/plans/ (M10-qr-pairing-login, auto-pr-deploy-sync, docs-restructure-ai-lookup, v1-readiness)
- 3 archive-plans verplaatst naar docs/old/plans/ (archive-map nu leeg)
- ST-1114-copilot-reviews + 3 research-docs naar nieuwe docs/Ideas/ map
- Duplicaat docs/old/2026-04-27-m8-realtime-solo.md verwijderd (origineel zit in docs/old/plans/)
- Link-fixes naar nieuwe locaties:
- CHANGELOG.md → docs/old/plans/v1-readiness.md
- docs/runbooks/deploy-control.md → docs/old/plans/auto-pr-deploy-sync.md (2x)
- docs/runbooks/worker-idempotency.md → docs/old/plans/auto-pr-deploy-sync.md
- docs/plans/docs-restructure-pbi-spec.md → docs/old/plans/docs-restructure-ai-lookup.md (4x text + 2x href)
- docs/INDEX.md geregenereerd (96 docs, was 100)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
194 lines
8.1 KiB
Markdown
194 lines
8.1 KiB
Markdown
---
|
||
title: "Worker idempotency & job-status protocol"
|
||
status: active
|
||
audience: [ai-agent, contributor]
|
||
language: nl
|
||
last_updated: 2026-05-09
|
||
when_to_read: "Vóór het implementeren of debuggen van Claude-CLI-worker logica die `update_job_status` aanroept."
|
||
---
|
||
|
||
# Worker idempotency & job-status protocol
|
||
|
||
Beschrijft hoe de Scrum4Me-worker `ClaudeJob.status` moet zetten op basis
|
||
van `VerifyResult` × git-diff-staat × branch-staat. Doel: voorkom
|
||
status-divergentie zoals geconstateerd in de **PBI-33 batch (5-5-2026
|
||
22:22)** waarin werk dat al gemerged was via PR #102/#103/#104 leidde
|
||
tot inconsistente combinaties van `verify=EMPTY → FAILED` en
|
||
`verify=DIVERGENT → DONE`.
|
||
|
||
---
|
||
|
||
## Beslissingsboom
|
||
|
||
Aan het einde van een story-job, ná `verify`-pass:
|
||
|
||
| `verify_result` | netto diff t.o.v. `origin/main` | branch al gemerged | → `ClaudeJob.status` | `Task.status` |
|
||
|---|---|---|---|---|
|
||
| `ALIGNED` of `PARTIAL` | nieuwe commit aanwezig | n.v.t. | **`DONE`** | `DONE` |
|
||
| `EMPTY` | leeg (niets gewijzigd) | werk zit al op `origin/main` | **`SKIPPED`** | `DONE` |
|
||
| `EMPTY` | leeg, maar werk staat **niet** op origin | n.v.t. | **`FAILED`** (`error: "verify produced no output"`) | `IN_PROGRESS` (handmatig onderzoeken) |
|
||
| `DIVERGENT` | aanwezig, maar identiek aan al-gemergde branch | ja (PR closed/merged) | **`SKIPPED`** | `DONE` |
|
||
| `DIVERGENT` | aanwezig, niet matchend met main | nee | **`FAILED`** (`error: "verify divergent — handmatige review"`) | `IN_PROGRESS` |
|
||
| (compile-fail, test-fail, push-fail, exception) | n.v.t. | n.v.t. | **`FAILED`** met concrete `error` | `IN_PROGRESS` |
|
||
| (gebruiker drukt cancel) | n.v.t. | n.v.t. | **`CANCELLED`** | `TO_DO` |
|
||
|
||
### Vuistregels
|
||
|
||
- **`SKIPPED`** = "geen netto-output, maar geen fout" — werk was al
|
||
gedaan vóór deze job draaide. Task mag op `DONE` omdat het beoogde
|
||
resultaat in main aanwezig is.
|
||
- **`FAILED`** is gereserveerd voor échte fouten: code-fouten,
|
||
test-failures, push-fouten, onverklaarde diff. Niet voor
|
||
"implementatie was al gedaan".
|
||
- **`DONE`** alleen bij `ALIGNED`/`PARTIAL` mét nieuwe commit op de
|
||
feature-branch. Een lege `DIVERGENT` op een al-gemergde branch is
|
||
géén `DONE`.
|
||
|
||
---
|
||
|
||
## StoryLog-verplichting
|
||
|
||
Tijdens elke job moet de worker `story_logs`-entries schrijven via de
|
||
MCP-tools, anders is de Sync-tab leeg:
|
||
|
||
| Wanneer | MCP-tool | Inhoud |
|
||
|---|---|---|
|
||
| Bij claim | `log_implementation` | "Start implementatie van T-XXX. Branch X. Plan: …" |
|
||
| Per commit | `log_commit` | hash + message + samenvatting van wijzigingen |
|
||
| Na verify | `log_test_result` | status `PASSED` of `FAILED` + samenvatting van checks |
|
||
|
||
In **PBI-33 batch** zijn deze tools **niet** aangeroepen — `story_logs`
|
||
voor ST-1208/1209/1210 is leeg. Worker MAG geen job afronden zonder
|
||
minimaal één `log_implementation` (start) en één `log_test_result`
|
||
(eind).
|
||
|
||
---
|
||
|
||
## Idempotency-protocol (vóór schrijven)
|
||
|
||
Bij claim van een job:
|
||
|
||
1. Lees `Task.implementation_plan` — beschrijft expliciet welke files
|
||
gewijzigd moeten worden.
|
||
2. Vergelijk de huidige `origin/main`-staat met die plan-instructies:
|
||
- Bestaat het bestand al met de beoogde inhoud?
|
||
- Bestaat de migratie al?
|
||
- Bevat de relevante codepad de nieuwe symbolen/types?
|
||
3. Bij **volledige hit**: roep `log_implementation` met inhoud "Werk
|
||
reeds aanwezig op origin/main vanaf commit X (Y)." Sla
|
||
verify-stap over en zet `JobStatus.SKIPPED`. Task naar `DONE`.
|
||
4. Bij **gedeeltelijke hit**: log de bevindingen via
|
||
`log_implementation` en doe alleen het resterende werk. Eindig met
|
||
`DONE` (`ALIGNED` of `PARTIAL`) als je netto-output hebt.
|
||
|
||
Dit voorkomt dubbele commits op al-gemergde branches en houdt
|
||
`pushed_at` semantisch correct (alleen gevuld als er werkelijk
|
||
gepusht is).
|
||
|
||
---
|
||
|
||
## Case-study: PBI-33 (5-5-2026 22:22)
|
||
|
||
PBI-33 ("PLAN_CHAT — gebruikersvragen over plan") werd opnieuw aangemaakt
|
||
nadat de feature al via een eerdere batch was gemerged onder cuid-style
|
||
story-codes (`ST-bsjoqjnr`, `ST-p6d1odh0`, …). De worker draaide om
|
||
22:22 en zag:
|
||
|
||
- **T-533** (`ST-1208` schema-werk): diff = leeg → `verify=EMPTY` →
|
||
`Job.FAILED` met error "Implementatie reeds voltooid en gemerged".
|
||
Volgens het nieuwe protocol had dit **`SKIPPED`** moeten zijn.
|
||
- **T-534…538**: diff niet leeg op feature-branches `feat/story-7pl4dsb6`
|
||
en `feat/story-0vtnydpi` (al-gemergde branches uit eerdere PR's) →
|
||
`verify=DIVERGENT` → `Job.DONE` met `pushed_at=now()`. Volgens het
|
||
nieuwe protocol had dit ook **`SKIPPED`** moeten zijn — branch was
|
||
al closed/merged, geen nieuwe commit.
|
||
- **`story_logs` voor ST-1208/1209/1210 is leeg** — geen
|
||
`log_implementation`, geen `log_commit`, geen `log_test_result`.
|
||
|
||
Drie protocol-overtredingen die we met deze runbook + de nieuwe
|
||
`SKIPPED`-status aanpakken.
|
||
|
||
---
|
||
|
||
## Config doorgeven aan Claude Code (PBI-67)
|
||
|
||
`wait_for_job` levert sinds PBI-67 een `config`-object mee in de
|
||
response. **De runner** (`scrum4me-docker/bin/run-one-job.ts`) leest deze
|
||
config en bouwt per geclaimde job de juiste Claude CLI-flags. Eén
|
||
Claude-invocation per job — niet één lange sessie die zelf claimt.
|
||
|
||
```bash
|
||
claude \
|
||
-p "$PROMPT" \
|
||
--model "$MODEL" \
|
||
--permission-mode "$PERMISSION_MODE" \
|
||
${EFFORT:+--effort $EFFORT} \
|
||
--allowedTools "$ALLOWED_TOOLS" \
|
||
--mcp-config /opt/agent/mcp-config.json \
|
||
--add-dir /opt/agent \
|
||
--output-format text
|
||
```
|
||
|
||
Waar:
|
||
|
||
| Variabele | Bron in response | Voorbeeld |
|
||
|---|---|---|
|
||
| `PROMPT` | `getKindPromptText(kind)` met `$PAYLOAD_PATH` vervangen | (kind-prompt-md) |
|
||
| `MODEL` | `config.model` | `claude-sonnet-4-6` |
|
||
| `PERMISSION_MODE` | `config.permission_mode` | `bypassPermissions` |
|
||
| `EFFORT` | `mapBudgetToEffort(config.thinking_budget)` (null = vlag weg) | `medium` |
|
||
| `ALLOWED_TOOLS` | `config.allowed_tools.join(',')` | `Read,Edit,…,mcp__scrum4me__update_task_status,…` |
|
||
|
||
### Claude CLI 2.1.x flag-correctie
|
||
|
||
De Claude CLI 2.1.x heeft géén numerieke `--thinking-budget` en géén
|
||
`--max-turns`. Mapping:
|
||
|
||
| `config.thinking_budget` | CLI-flag |
|
||
|---|---|
|
||
| 0 | (geen `--effort` flag) |
|
||
| 1-6000 | `--effort medium` |
|
||
| 6001-12000 | `--effort high` |
|
||
| 12001-24000 | `--effort xhigh` |
|
||
| >24000 | `--effort max` |
|
||
|
||
`config.max_turns` blijft **audit-only** — wordt gesnapshot in
|
||
`ClaudeJob.requested_*` voor cost-attribution maar niet doorgegeven aan
|
||
Claude. De resolver in `lib/job-config.ts` exporteert
|
||
`mapBudgetToEffort(budget)` voor deze mapping.
|
||
|
||
### Verwachte CLI-aanroep per kind (defaults zonder overrides)
|
||
|
||
| Kind | Model | thinking_budget | --effort | permission_mode |
|
||
|---|---|---|---|---|
|
||
| `IDEA_GRILL` | sonnet-4-6 | 12000 | high | acceptEdits |
|
||
| `IDEA_MAKE_PLAN` | opus-4-7 | 24000 | xhigh | acceptEdits |
|
||
| `PLAN_CHAT` | sonnet-4-6 | 6000 | medium | acceptEdits |
|
||
| `TASK_IMPLEMENTATION` | sonnet-4-6 | 6000 | medium | bypassPermissions |
|
||
| `SPRINT_IMPLEMENTATION` | sonnet-4-6 | 6000 | medium | bypassPermissions |
|
||
|
||
### Wie doet wat in de runner-architectuur
|
||
|
||
| Component | Verantwoordelijkheid |
|
||
|---|---|
|
||
| `bin/run-agent.sh` | Daemon-loop, exponential backoff, UNHEALTHY-marker, log-rotation, TOKEN_EXPIRED-detectie via exit-code 3 of stdout-regex |
|
||
| `bin/run-one-job.ts` | **Claim** (tryClaimJob + LISTEN-fallback 270s), config-resolve (getFullJobContext), payload schrijven, **CLI-flags bouwen**, spawn `claude`, lease-renewal voor SPRINT (setInterval 60s), rollbackClaim bij Claude exit≠0 zonder update_job_status, cleanup |
|
||
| `claude` (per invocation) | Voert exclusief de geclaimde job uit. **Mag geen** `wait_for_job`, `check_queue_empty`, of `job_heartbeat` aanroepen — zit niet in `allowed_tools` |
|
||
|
||
Volledige resolver-uitleg + override-cascade staat in
|
||
[job-model-selection.md](./job-model-selection.md). Refactor-plan:
|
||
[queue-loop-extraction.md](../plans/queue-loop-extraction.md).
|
||
|
||
---
|
||
|
||
## Referenties
|
||
|
||
- Enum: `prisma/schema.prisma` → `enum ClaudeJobStatus`
|
||
- Mapping: `lib/job-status.ts` (DB↔API) en
|
||
`components/shared/job-status.ts` (label + kleur)
|
||
- Status-data-cleanup: `app/api/cron/cleanup-agent-artifacts/route.ts`
|
||
- KPI-aggregatie: `lib/insights/agent-throughput.ts` (terminal_7d
|
||
inclusief SKIPPED)
|
||
- Gerelateerd plan: `docs/old/plans/auto-pr-deploy-sync.md` Deel D
|
||
- PBI-67 resolver: `scrum4me-mcp/src/lib/job-config.ts` + `lib/job-config.ts`
|
||
(Sync-tab toont per-Story job-status incl. SKIPPED)
|