# Build a plan-review-update loop A single review pass over a plan tends to catch the same blind spots each time, because the reviewer shares the builder's context. This page builds a small loop that avoids that: one planning codon runs once and writes `plan.md`, then a loop alternates a fresh reviewer and a fresh updater, passing the work through ordinary files in the workspace. The loop contains only `review` and `update`, so those two codons run on each iteration. We will work from a checked fixture, so every command below has a captured result to compare against. By the end you will have a validated hank file, a completed headless run with seven codon completions, a set of preserved review notes, and a measured cost table you can use before adjusting budgets. The final section deliberately breaks the configuration at two known boundaries so you can see what the failure output looks like before you meet it in your own project. ## Choose the plan-review-update loop The design separates the builder from the evaluator. That separation lets us tune the reviewer to look for omissions while the updater applies the findings, and it is what makes the review "blind": the fresh reviewer cannot fill gaps from the builder's conversation, so the work has to travel through a file in the workspace. In this fixture, the `plan` codon runs once, then the loop repeats `review` and `update`, with setup work between codons when a project needs it. A related `implement → review → update` variant uses rigs to run tests between codons so each reviewer starts with a fresh batch of results; see [reliability patterns](/0.10.0/files/author/patterns/reliability). The checked fixture uses `haiku` for its `plan`, `review`, and `update` codons, so the commands below run the all-`haiku` capture. The production-derived pairing documented in [reliability patterns](/0.10.0/files/author/patterns/reliability) instead uses a `haiku` blind reviewer and an `opus` updater for three iterations. A broader planning lineup–Gemini, Opus, and GPT planning views, an Opus merge, then a Sonnet review loop–is lineage context rather than this fixture's recipe. The reason to change the pairing is different failure modes, not a promise that one model is always better. ## Make the loop's handoffs explicit With the pattern chosen, the next step is the hank file itself. A loop entry is a `loop` object: it names its termination rule and codons, while optional budget and archive settings scope the loop. The full field set: | field | type | default | required | constraints | description | | ------------------ | ----------------------------------- | ------- | -------- | ----------- | ------------------------------------------------------------------------------------- | | `type` | `string` | | yes | = `loop` | Type discriminator - required for loops | | `id` | `string` | | yes | minLength 1 | Unique identifier for this loop | | `name` | `string` | | yes | minLength 1 | Human-readable name displayed in UI and logs | | `description` | `string` | | no | | Optional description shown to users | | `terminateOn` | `iterationLimit \| contextExceeded` | | yes | | Termination condition for the loop | | `codons` | `array` | | yes | minItems 1 | Array of codons to execute in each iteration | | `budget` | `object` | | no | | Budget scope for this loop. | | `archiveOnSuccess` | `array` | | no | | Paths to archive when the loop terminates. Paths are relative to the agent workspace. | | field | type | default | required | constraints | description | | ----------------- | --------- | ------- | -------- | -------------------------- | ----------- | | `iterationLimit` | `object` | | | `type` = `iterationLimit` | | | ↳ `limit` | `integer` | | yes | min 1 | | | `contextExceeded` | `object` | | | `type` = `contextExceeded` | | In this fixture, `terminateOn` is `{ "type": "iterationLimit", "limit": 3 }`. The runtime therefore visits iterations `0`, `1`, and `2`, checking the limit after the last codon in each iteration. A loop budget can also cap dollars or time; budget exhaustion takes precedence over the configured termination rule. `fresh` is legal in this iteration-limited loop but is a hard load error inside a `contextExceeded` loop. The handoff between codons is where the blind-review property is enforced. We give both loop codons `continuationMode: "fresh"`. The reviewer starts without the updater's conversation history, reads the files in `agentRoot/`, and writes `review-notes.md`; the updater reads that file and rewrites `plan.md`. Loop runtime IDs carry the iteration suffix: `review#0`, `update#0`, through `review#2`, `update#2`. `rigSetup` is setup work that runs before its codon. The updater's rig copies the current review into `notes/` before the updater starts, which is how the per-iteration notes survive. Each rig operation must succeed unless `allowFailure: true` is set; the fixture chooses that one guard rather than adding `|| true` to the shell command. If you replace the shell copy with a schema `copy` operation, keep `copy.from` as a relative POSIX path inside the hank directory; absolute paths, `..` escapes, and symlinks are rejected in 0.10.0. `archiveOnSuccess` entries are file globs resolved against the agent workspace. This fixture's `archiveOnSuccess: ["notes/"]` is a bare directory entry and resolves zero files, so the note series is preserved by the updater's rig copy instead. When a loop codon's archive glob matches files, the runtime moves them under `rigArchive/-//`; a runtime id such as `edit#0` becomes `edit-0`. A non-loop codon uses `rigArchive//`, while a loop-level termination archive uses `rigArchive/-loop/`. These destinations are under the execution directory, alongside `agentRoot`; a rig running in `agentRoot/` copies a prior iteration back from `../rigArchive/revise-0/edit-0/current-project/`. Use a file glob such as `current-project/**` for a tree: the loop-archive fixture records `rigArchive/revise-0/edit-0/current-project/history.txt` and `rigArchive/revise-1/edit-1/current-project/history.txt`. Archive destination shapes belong in the [execution-directory reference](/0.10.0/files/reference/execution-directory). Model names here are the registry-checked `haiku` and `opus` shortcuts. > **Pitfall:** Do not use `continue-previous` for a loop codon after a different-model codon. The load check applies to the later codon–here, `update` after `review`–not the reviewer at loop index 0; use `fresh` and pass the handoff through files. Keep the handoff file-based even when you change the model pairing. > **VersionNote:** In 0.10.0, hank reference paths are strict: a rig `copy.from` cannot be absolute, escape the hank directory, or use a symlink. The excerpt below isolates those handoff settings–`continuationMode`, the rig, and the archive entry–so you can see them without the surrounding prompt and retry configuration: ```json { "type": "loop", "id": "blind-reviews", "name": "Repeatedly improve the plan through blind iteration", "terminateOn": { "type": "iterationLimit", "limit": 3 }, "archiveOnSuccess": ["notes/"], "codons": [ { "id": "review", "model": "haiku", "continuationMode": "fresh", "checkpointedFiles": ["review-notes.md"] }, { "id": "update", "model": "haiku", "continuationMode": "fresh", "rigSetup": [ { "type": "command", "allowFailure": true } ] } ] } ``` For prompts, retries, budgets, and checkpoint details (the sealed file state recorded after a codon completes), use the fixture's complete `hank.json`, reproduced here: ```json { "type": "loop", "id": "blind-reviews", "name": "Repeatedly improve the plan through blind iteration", "terminateOn": { "type": "iterationLimit", "limit": 3 }, "budget": { "maxDollars": 0.9, "maxTimeSeconds": 900, "onExceeded": "complete" }, "archiveOnSuccess": ["notes/"], "codons": [ { "id": "review", "name": "Blind review of the plan", "model": "haiku", "continuationMode": "fresh", "promptFile": "prompts/review.md", "checkpointedFiles": ["review-notes.md"], "onFailure": "retry", … }, { "id": "update", "name": "Apply the review to the plan", "model": "haiku", "continuationMode": "fresh", "promptFile": "prompts/update.md", "rigSetup": [ { "type": "command", "command": { "run": "mkdir -p notes && cp review-notes.md \"notes/review-notes-$(ls notes/ | wc -l | tr -d ' ').md\"" }, "allowFailure": true } ], "checkpointedFiles": ["plan.md", "plan-changelog.md"], … } ] } ``` `checkpointedFiles` names the files tracked in that checkpoint; this fixture tracks the review notes for `review` and the plan plus changelog for `update`. ## Run from the fixture directory Download [`hankweave-fixtures-0.10.0.tar.gz`](/content-assets/cf45dff5691c48c0/fixtures/0.10.0/hankweave-fixtures-0.10.0.tar.gz), or use an individual artifact at `/fixtures/0.10.0/files/`. The archive already contains one top-level `hankweave-fixtures-0.10.0/` directory. Extract it from the archive's parent, then enter the fixture directory; do not pre-create the same root and extract into it. The shown `bunx`/`npx` recipe runs on Node ≥22.19.0: the package executable has a Node shebang and its `engines` field requires that version; the capture ran under Node v23.8.0. `bunx hankweave@0.10.0 …` is the chosen Bun launcher spelling; the equivalent Node/npm spelling for validation is `npx hankweave@0.10.0 hank.json data/ --validate`, with the same substitution for the run command. Before starting a run, set `ANTHROPIC_API_KEY` in the environment. This fixture's `plan`, `review`, and `update` codons all use the `haiku` shortcut for `claude-haiku`; without the key, startup fails its provider self-test with `No authentication found (set ANTHROPIC_API_KEY)`. ```sh # Run these commands from the directory containing the downloaded archive. tar -xzf hankweave-fixtures-0.10.0.tar.gz cd hankweave-fixtures-0.10.0/plan-review-update ``` The entry point is `plan-review-update/hank.json` with its `data/` directory. Validate before spending on codons: ```sh bunx hankweave@0.10.0 hank.json data/ --validate ``` Validation hashes the data source, resolves and validates the hank, and performs harness setup, credential, and catalog checks. It does not execute codons or make a model-generation self-test. At runtime startup, before codons execute, provider health checks can call `generateText("Hi", maxOutputTokens: 16)` on available registry providers, not only the models selected by this hank. Those calls may be billable and are outside the tracked-codon capture total. A successful validation prints the `GOOD TO RUN!` box. The captured full run is headless (without an interactive UI) and starts a new execution: ```sh bunx hankweave@0.10.0 hank.json data/ --headless --start-new -o out ``` `--start-new` gives the attempt an independent execution. This fixture declares no `outputFiles`, so `-o out` copies nothing: the captured `out/` tree is empty, and `plan.md`, `plan-changelog.md`, `review-notes.md`, and `notes/review-notes-{0,1,2}.md` remain under `~/.hankweave-executions//agentRoot/`, the `Exec →` path printed at startup. Managed execution directories live under `~/.hankweave-executions//`, with run logs under `.hankweave/runs//`; see [execution-directory](/0.10.0/files/reference/execution-directory) for the complete layout. Flag behavior belongs to [CLI reference](/0.10.0/files/reference/cli) and [resume, rollback, and retry](/0.10.0/files/operate/resume-rollback-and-retry). Since 0.7.3, validation warns about `abort` failure policies inside loops. The startup stream confirms the new execution directory, the source-to-exec mapping, and headless mode: ```text Created new execution directory: ~/.hankweave-executions/ New execution: Source → data Exec → ~/.hankweave-executions/ SDKs → Claude node_modules ✓ ╭──────────────────────────────────────────────────────────────────────────────╮ Hankweave Server Started WebSocket: ws://localhost: Running in headless mode on port ``` ## Check the run's completion evidence Three checks confirm the run did what the hank file describes: the validation summary, the event stream, and the files left in the workspace. **Check-it 1 – validation:** confirm the `GOOD TO RUN!` box reports three codons, three prompts, three system prompts, one rig, and three checkpoints. ```text ╭─ GOOD TO RUN! ─────────────────────────────────────────────────────╮ │ 3 codons • 3 prompts • 3 system prompts • 1 rigs • 3 checkpoints │ ╰────────────────────────────────────────────────────────────────────╯ ``` **Check-it 2 – completion:** count seven `codon.completed` events–`plan`, three reviewer iterations, and three updater iterations–then confirm the final `RunCompleted` event. The loop emits three `loop.iteration.completed` events; iteration 2 is final because the termination reason is `iteration_limit`. ```text {"id":"","timestamp":"","type":"codon.completed","data":{"codonId":"plan","success":true,"cost":"","duration":"","exitStatus":{"type":"success"}}} … {"id":"","timestamp":"","type":"codon.completed","data":{"codonId":"review#0","success":true,"cost":"","duration":"","exitStatus":{"type":"success"}}} … {"id":"","timestamp":"","type":"codon.completed","data":{"codonId":"update#0","success":true,"cost":"","duration":"","exitStatus":{"type":"success"}}} … {"id":"","timestamp":"","type":"codon.completed","data":{"codonId":"review#1","success":true,"cost":"","duration":"","exitStatus":{"type":"success"}}} … {"id":"","timestamp":"","type":"codon.completed","data":{"codonId":"update#1","success":true,"cost":"","duration":"","exitStatus":{"type":"success"}}} … {"id":"","timestamp":"","type":"codon.completed","data":{"codonId":"review#2","success":true,"cost":"","duration":"","exitStatus":{"type":"success"}}} … {"id":"","timestamp":"","type":"codon.completed","data":{"codonId":"update#2","success":true,"cost":"","duration":"","exitStatus":{"type":"success"}}} {"id":"","timestamp":"","type":"loop.iteration.completed","data":{"loopId":"blind-reviews","iteration":2,"durationMs":81500,"costUsd":0.0972383,"tokensUsed":6642,"isFinal":true,"terminationReason":"iteration_limit"}} {"id":"","timestamp":"","type":"state.transition","data":{"transitionType":"RunCompleted","runId":"","transition":{"type":"RunCompleted","data":{"runId":""}},"resultingState":{"currentRunId":null,"runCount":1,"totalCost":"","currentRunCost":""}}} ``` **Check-it 3 – files:** inspect `agentRoot/` for the final plan and changelog, the latest `review-notes.md`, and the three preserved review notes. The note filenames are zero-indexed, matching the loop iteration IDs. ```text notes/review-notes-0.md notes/review-notes-1.md notes/review-notes-2.md plan-changelog.md plan.md review-notes.md ``` ## Measure costs before changing caps Use the captured run's measured costs rather than a forecast. The capture was measured on 2026-09-03 with `haiku` for all three codons: | codon | measured cost (USD) | | ---------- | ------------------: | | `plan` | $0.02284565 | | `review#0` | $0.02012185 | | `update#0` | $0.04359475 | | `review#1` | $0.03351075 | | `update#1` | $0.05477635 | | `review#2` | $0.04528230 | | `update#2` | $0.05195600 | | **total** | **$0.27208765** | The verified model manifest lists `haiku` at $1/$5 and `opus` at $5/$25 per million input/output tokens. Those are manifest rates for comparing the cheap-reviewer/expensive-updater split, not a replacement for the dated capture above. ## Replace agent review with a rig When a fixed evaluation suite can answer the review question, we can run it from a rig and reserve the agentic reviewer for work that needs judgment. Read the documented comparison as five rounds of `(Agentic Build × Automated Review)` plus one `Agentic Review`, compared with four rounds of `(Agentic Build + Agentic Review)`; it is a comparison of review designs, not a measured cost for this fixture. For the method, use [reliability patterns](/0.10.0/files/author/patterns/reliability); use the [patterns gallery](/0.10.0/files/author/patterns/gallery) to choose another runnable shape. ## Break model handoffs on purpose The fixture ships two named break hanks, each with its own capture, so you can see both failure boundaries before hitting them in your own configuration. The first targets the model-handoff rule from the pitfall above: ```sh bunx hankweave@0.10.0 hank.break-continue-previous.json data/ --validate ``` This changes `update` to `continue-previous` and `sonnet` after a `haiku` review. Validation fails at load because different models cannot share the same session ID: ```text ╭────────────────────────────────────────────────────────────────────╮ │ Hankweave v0.10.0 │ │ darwin arm64 • node v23.8.0 │ ╰────────────────────────────────────────────────────────────────────╯ Calculating data signature for validation... > Validating configuration: ~/.hankweave-executions//agentRoot/fixtures/plan-review-update/hank.break-continue-previous.json Data source: ~/.hankweave-executions//agentRoot/fixtures/plan-review-update/data Execution path: ~/.hankweave-executions/validation- Validation failed: Loop 2 (blind-reviews) > Codon 2 (update): Cannot use continuationMode "continue-previous" when model differs from previous codon in loop. Different models cannot share the same session ID. Change to "fresh" to start a new conversation with a different model. ``` To exercise the rig boundary, run the second named variant with the captured headless invocation: ```sh bunx hankweave@0.10.0 hank.break-allowfailure.json data/ --headless --start-new -y ``` Run this variant with `--start-new` after the main run: without it, execution lookup uses the newest managed execution for the `data/` hash, not the hank filename, and would resume the completed main execution. The capture omitted the flag only because no prior execution existed. Its update rig exits before the update codon starts. Without `allowFailure`, that rig failure puts the codon and run into failure; the variant's capture records the run as failed after `plan` and `review#0` complete.