feat(implement-loop): retire the finalize dispatch; the end-report carries the artifacts

The finalize agent only templated the script's already-computed
aggregate into stats.json / BLOCKED.md — ~41k real tokens per run
(197 dispatches / 8.0M in the 206-run corpus) for pure formatting.
The templates now live in the script (single source): the end-report
carries artifacts.stats_json and artifacts.blocked_md (null on DONE
and clean no-ops), and the orchestrator writes them byte-identical,
filling exactly two runtime slots it owns anyway — {{DATE}} (date +%F)
and {{FILES_TOUCHED}} (git status --porcelain, its Step-3/4 inspection
duty). File paths, consumers (tdd decompose reads BLOCKED.md,
postmortem parses it), and content shape are unchanged; the
agent-authored 'suggested next step' line is dropped — blocked_detail
carries the diagnosis.

refs #29
This commit is contained in:
2026-07-17 15:29:12 +02:00
parent 8272077e9b
commit 615a76eb01
3 changed files with 119 additions and 73 deletions
+34 -20
View File
@@ -1,6 +1,6 @@
---
name: implement
description: Use when an implementation plan exists under docs/plans and is ready to execute, OR when a debug/tdd RED-test is handed off for the GREEN side. Runs the `implement-loop` Workflow — a deterministic script that executes the per-task loop (implementer → spec-compliance → quality, each a separate agent call) and aggregates in code, writing edits, tests, and a stats file to the working tree without committing (and `BLOCKED.md` on PARTIAL/BLOCKED). The orchestrator reads the end-report, inspects the working tree, decides commit shape, and performs all commits. Also documents the `compiler-driven` light-edit arm.
description: Use when an implementation plan exists under docs/plans and is ready to execute, OR when a debug/tdd RED-test is handed off for the GREEN side. Runs the `implement-loop` Workflow — a deterministic script that executes the per-task loop (implementer → spec-compliance → quality, each a separate agent call) and aggregates in code, writing edits and tests to the working tree without committing. The orchestrator reads the end-report, writes the stats file (and `BLOCKED.md` on PARTIAL/BLOCKED) from the contents the end-report carries, inspects the working tree, decides commit shape, and performs all commits. Also documents the `compiler-driven` light-edit arm.
---
# implement — plan execution on the Workflow substrate
@@ -30,11 +30,17 @@ real build/test outcome). The four phase agents survive unchanged as the
agent-types the script dispatches; only the dispatch/aggregation prose
the orchestrator-agent carried is gone.
All work lives in the working tree: code edits, the stats file, and —
on PARTIAL/BLOCKED — `BLOCKED.md` at the repo root. The script **never
All code edits live in the working tree. The stats file and — on
PARTIAL/BLOCKED — `BLOCKED.md` are **built by the script and carried in
the end-report** (`artifacts.stats_json` / `artifacts.blocked_md`): the
script has no filesystem access of its own, and dispatching an agent
just to template the script's own aggregate cost ~41k real tokens per
run (issue #29). The orchestrator writes them byte-identical, filling
the two runtime slots it owns anyway — `{{DATE}}` (`date +%F`) and
`{{FILES_TOUCHED}}` (`git status --porcelain`). The script **never
commits** and never moves main HEAD. The orchestrator reads one
end-report (the workflow's return value), inspects the unstaged tree,
and decides commit shape.
end-report (the workflow's return value), writes the artifacts,
inspects the unstaged tree, and decides commit shape.
## When to Use / Skipping
@@ -99,13 +105,13 @@ regression around the RED-first gate.
## The Iron Law
```
THE LOOP NEVER COMMITS. CODE EDITS, TESTS, AND THE STATS FILE LIVE IN THE WORKING TREE AS UNSTAGED CHANGES UNTIL THE ORCHESTRATOR COMMITS THEM.
THE LOOP NEVER COMMITS. CODE EDITS AND TESTS LIVE IN THE WORKING TREE AS UNSTAGED CHANGES UNTIL THE ORCHESTRATOR COMMITS THEM; THE STATS FILE AND BLOCKED.md ARE WRITTEN BY THE ORCHESTRATOR FROM THE CONTENTS THE END-REPORT CARRIES.
WITHIN THIS LOOP, MAIN HEAD IS SACROSANCT — NO RESET, NO REVERT; THE LOOP ONLY EVER WRITES UNSTAGED CHANGES. MAIN MOVES FORWARD ONLY VIA ORCHESTRATOR COMMITS. (THE `/boss` ROLLBACK SANDBOX WINDS BACK ALREADY-COMMITTED AUTONOMOUS WORK — A BOSS-LEVEL RECOVERY, NOT A STEP IN THIS LOOP.)
NO IN-LOOP AGENT EVER DISCARDS WORKING-TREE STATE VIA `git checkout` / `git restore` — WHOLE-FILE OR `--patch`: NOTHING COMMITS BETWEEN TASKS, SO A SHARED FILE CARRIES EARLIER TASKS' UNCOMMITTED DONE WORK, AND A CHECKOUT SILENTLY DESTROYS IT (issue #23). RECOVERY IS FORWARD — EDIT BACK, OR REPORT `BLOCKED`. (THE ORCHESTRATOR'S OWN END-OF-ITERATION `git checkout -- .` DISPOSITION HAPPENS OUTSIDE THE LOOP.)
PER-TASK PHASES ARE SEPARATE AGENT CALLS IN THE WORKFLOW SCRIPT — implementer, then spec-compliance, then quality. SPEC COMPLIANCE IS GATED BEFORE QUALITY.
TASKS RUN SEQUENTIALLY; A BLOCKED TASK STOPS THE LOOP — NO SKIP-AHEAD (TASK ORDERING DEPENDENCIES ARE UNKNOWN).
NEVER PUSH PAST `BLOCKED` BY HAND.
ON `PARTIAL` OR `BLOCKED`, THE SCRIPT WRITES `BLOCKED.md` AT THE REPO ROOT — UNCOMMITTED BY CONVENTION. ON `DONE`, NO SEPARATE FILE. (EXCEPTIONS THAT WRITE NO `BLOCKED.md`: A NO-OP ITERATION — ITS TREE IS ALREADY CLEAN — AND ANY PRE-LOOP EARLY EXIT (MALFORMED CARRIER, DIRTY-TREE PREFLIGHT, PLAN-EXTRACT GUARD): NOTHING RAN AND NOTHING WAS WRITTEN, SO THE STATUS + `infra:`-PREFIXED REASON RIDE THE END-REPORT AND THERE IS NOTHING TO CLEAN UP OR EXPLAIN.)
ON `PARTIAL` OR `BLOCKED`, THE END-REPORT CARRIES THE `BLOCKED.md` CONTENT (`artifacts.blocked_md`); THE ORCHESTRATOR'S FIRST ACTION ON READING SUCH A REPORT IS WRITING IT VERBATIM TO `BLOCKED.md` AT THE REPO ROOT — UNCOMMITTED BY CONVENTION. ON `DONE`, `artifacts.blocked_md` IS NULL. (EXCEPTIONS WHERE IT IS ALSO NULL: A NO-OP ITERATION — ITS TREE IS ALREADY CLEAN — AND ANY PRE-LOOP EARLY EXIT (MALFORMED CARRIER, DIRTY-TREE PREFLIGHT, PLAN-EXTRACT GUARD): NOTHING RAN AND NOTHING WAS WRITTEN, SO THE STATUS + `infra:`-PREFIXED REASON RIDE THE END-REPORT AND THERE IS NOTHING TO CLEAN UP OR EXPLAIN.)
`DONE` REQUIRES POSITIVE EVIDENCE THAT AN EDIT LANDED — A NO-OP ITERATION (`git status --porcelain` EMPTY, untracked files INCLUDED) IS NEVER `DONE`, IT IS `BLOCKED` (the issue #11 vacuous-green shape, guarded on the main path). THE VERDICT IS THE GIT TREE COUNT, NEVER A SELF-REPORT.
IN MINI MODE THE RED->GREEN IS OBSERVED, NOT ASSERTED: AN INDEPENDENT VERIFY RE-RUNS THE HANDED-OFF RED TEST AND THE SUITE; A STILL-RED TEST OR A REGRESSION IS `BLOCKED` (route back to debug, RED-first).
THE COMPILER-DRIVEN ARM COMMITS ONLY ON CLEAN BUILD + SUITE GREEN UNCHANGED; ELSE IT BOUNCES (specify for a design hole, tdd for discovered new behaviour, debug for a regression).
@@ -227,20 +233,28 @@ worktree branch).
The workflow returns a small structured end-report (its return value):
`status`, `iter_id`, `started_from`, `tasks_total` / `tasks_completed`,
per-task summaries, concerns, E2E fixtures, the stats path, and — on
per-task summaries, concerns, E2E fixtures, the `artifacts` block
(`stats_json`, `stats_path_hint`, `blocked_md`), and — on
PARTIAL/BLOCKED — the blocked detail. Per-task chatter stayed inside
the workflow's agent contexts and never reached the orchestrator. Read
the end-report; it is the per-task summary you build the commit body
from. An **infra early-exit** (malformed or missing carrier, dirty-tree
from. **First action, before anything else:** materialise the
artifacts — write `artifacts.stats_json` to the stats path (fill
`{{DATE}}` with `date +%F`), and when `artifacts.blocked_md` is
non-null write it verbatim to `BLOCKED.md` at the repo root (fill
`{{DATE}}` and `{{FILES_TOUCHED}}` from `git status --porcelain`). The
contents are the script's single-sourced templates — do not rephrase,
restructure, or embellish them. An **infra early-exit** (malformed or missing carrier, dirty-tree
preflight, a plan-extract guard) returns a minimal report instead:
`status: BLOCKED` plus an `infra:`-prefixed `reason`, no `blocked_detail`
and none of the per-task fields — nothing ran.
### Step 3 — Orchestrator inspect + commit step (on DONE)
The workflow returns with code edits and the stats file sitting in the
working tree as unstaged changes. Nothing is committed yet, and there
is no `BLOCKED.md` (DONE never writes one).
The workflow returns with code edits sitting in the working tree as
unstaged changes; the stats file was just written by you from
`artifacts.stats_json` (Step 2). Nothing is committed yet, and there
is no `BLOCKED.md` (`artifacts.blocked_md` is null on DONE).
1. Inspect: `git status` and `git diff` — confirm the changes match
what the end-report claims. The end-report is the per-task summary;
@@ -268,16 +282,16 @@ is no `BLOCKED.md` (DONE never writes one).
### Step 4 — Orchestrator handling (on PARTIAL or BLOCKED)
The workflow returns with whatever work-in-progress it managed plus
`BLOCKED.md` at the repo root carrying the diagnostic. Nothing is
committed. The orchestrator decides what to do with the dirty working
tree:
The workflow returns with whatever work-in-progress it managed;
`BLOCKED.md` at the repo root carries the diagnostic — written by you
in Step 2 from `artifacts.blocked_md`. Nothing is committed. The
orchestrator decides what to do with the dirty working tree:
0. **No-op `BLOCKED` special case** — if the end-report's status is
`BLOCKED` with `blocked_md` null and a no-op `blocked_detail` (the reason
names a no-op iteration or a clean-tree no-op fix; `files_touched` is 0 in
standard mode, null in mini), there is **no `BLOCKED.md`** and the tree is
already clean. The diagnostic is in the end-report's `blocked_detail`, not
`BLOCKED` with `artifacts.blocked_md` null and a no-op `blocked_detail`
(the reason names a no-op iteration or a clean-tree no-op fix;
`files_touched` is 0 in standard mode, null in mini), there is
**no `BLOCKED.md`** and the tree is already clean. The diagnostic is in the end-report's `blocked_detail`, not
a file. Nothing to clean up or stash; re-plan or re-run from the same
clean tree.
The same no-file shape occurs for an **infra early-exit** (`reason`