You're reading the 0.10.0 archive.

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.

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 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:

Scroll to explore the table →
fieldtypedefaultrequiredconstraintsdescription
typestringyes= loopType discriminator - required for loops
idstringyesminLength 1Unique identifier for this loop
namestringyesminLength 1Human-readable name displayed in UI and logs
descriptionstringnoOptional description shown to users
terminateOniterationLimit | contextExceededyesTermination condition for the loop
codonsarray<object>yesminItems 1Array of codons to execute in each iteration
budgetobjectnoBudget scope for this loop.
archiveOnSuccessarray<string>noPaths to archive when the loop terminates. Paths are relative to the agent workspace.
Scroll to explore the table →
fieldtypedefaultrequiredconstraintsdescription
iterationLimitobjecttype = iterationLimit
limitintegeryesmin 1
contextExceededobjecttype = 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/<loopId>-<iteration>/<codonId>/<path>; a runtime id such as edit#0 becomes edit-0. A non-loop codon uses rigArchive/<codonId>/<path>, while a loop-level termination archive uses rigArchive/<loopId>-loop/<path>. 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. Model names here are the registry-checked haiku and opus shortcuts.

Keep the handoff file-based even when you change the model pairing.

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, or use an individual artifact at /fixtures/0.10.0/files/<relative-file>. 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).

⌁ Terminal
# 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:

⌁ Terminal
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:

⌁ Terminal
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/<id>/agentRoot/, the Exec → path printed at startup. Managed execution directories live under ~/.hankweave-executions/<id>/, with run logs under .hankweave/runs/<runId>/; see execution-directory for the complete layout. Flag behavior belongs to CLI reference and 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:

Output
Created new execution directory: ~/.hankweave-executions/<exec-id>
New execution: <exec-id>
  Source → data
  Exec   → ~/.hankweave-executions/<exec-id>
  SDKs   → Claude node_modules ✓

╭──────────────────────────────────────────────────────────────────────────────╮
  Hankweave Server Started
  WebSocket: ws://localhost:<port>

Running in headless mode on port <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.

Output
╭─ 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.

Output
{"id":"<id>","timestamp":"<ts>","type":"codon.completed","data":{"codonId":"plan","success":true,"cost":"<n>","duration":"<n>","exitStatus":{"type":"success"}}}
…
{"id":"<id>","timestamp":"<ts>","type":"codon.completed","data":{"codonId":"review#0","success":true,"cost":"<n>","duration":"<n>","exitStatus":{"type":"success"}}}
…
{"id":"<id>","timestamp":"<ts>","type":"codon.completed","data":{"codonId":"update#0","success":true,"cost":"<n>","duration":"<n>","exitStatus":{"type":"success"}}}
…
{"id":"<id>","timestamp":"<ts>","type":"codon.completed","data":{"codonId":"review#1","success":true,"cost":"<n>","duration":"<n>","exitStatus":{"type":"success"}}}
…
{"id":"<id>","timestamp":"<ts>","type":"codon.completed","data":{"codonId":"update#1","success":true,"cost":"<n>","duration":"<n>","exitStatus":{"type":"success"}}}
…
{"id":"<id>","timestamp":"<ts>","type":"codon.completed","data":{"codonId":"review#2","success":true,"cost":"<n>","duration":"<n>","exitStatus":{"type":"success"}}}
…
{"id":"<id>","timestamp":"<ts>","type":"codon.completed","data":{"codonId":"update#2","success":true,"cost":"<n>","duration":"<n>","exitStatus":{"type":"success"}}}
{"id":"<id>","timestamp":"<ts>","type":"loop.iteration.completed","data":{"loopId":"blind-reviews","iteration":2,"durationMs":81500,"costUsd":0.0972383,"tokensUsed":6642,"isFinal":true,"terminationReason":"iteration_limit"}}
{"id":"<id>","timestamp":"<ts>","type":"state.transition","data":{"transitionType":"RunCompleted","runId":"<id>","transition":{"type":"RunCompleted","data":{"runId":"<id>"}},"resultingState":{"currentRunId":null,"runCount":1,"totalCost":"<n>","currentRunCost":"<n>"}}}

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.

Output
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:

Scroll to explore the table →
codonmeasured 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; use the 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:

⌁ Terminal
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:

Output

╭────────────────────────────────────────────────────────────────────╮
│  Hankweave v0.10.0                                                 │
│  darwin arm64 • node v23.8.0                                       │
╰────────────────────────────────────────────────────────────────────╯

Calculating data signature for validation...

> Validating configuration: ~/.hankweave-executions/<exec-id>/agentRoot/fixtures/plan-review-update/hank.break-continue-previous.json

  Data source:    ~/.hankweave-executions/<exec-id>/agentRoot/fixtures/plan-review-update/data
  Execution path: ~/.hankweave-executions/validation-<id>

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:

⌁ Terminal
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.