Iterate Until Verified
When the job is to produce an artifact (rendered image, SVG, chart, config file, regex, generated code, rubric-graded
prose) and a concrete pass/fail check exists, use the check tool to run the loop instead of eyeballing the result. The
extension tracks iterations on disk + in the session branch, enforces budgets, and catches "looks right to me" claims
you didn't actually verify.
You are the actor: you edit the artifact. The check (a bash command or a critic subagent) is the environment.
Every check run returns a verdict; you use it to decide the next edit - the same shape as a tight
test-driven-development loop, just formalized so weak models can follow it too.
When to declare a check
Reach for check when all of these hold:
- You are producing a concrete artifact: a file, a diagram, a chart, a config, a generated snippet, a rendered image.
- A pass/fail contract exists or can be stated: "the file exists and contains X", "the SVG has three pie slices", "the
regex matches these examples and rejects those", "the output validates against this schema", "the rubric says…".
- You expect to iterate at least once. If the first draft is obviously right, you still benefit - one
check run
catches the off-by-one case cheaper than a user round-trip.
Skip the check when:
- The deliverable IS the answer to a question ("what does this function do?"). No artifact, nothing to re-render.
- A single tool call completes the task with deterministic success (
git status, "read line 40"). The round trip costs
more than it saves.
- The artifact is genuinely throwaway (one-off
/tmp scratch). If the user will look at it once and discard it, don't
spin up the loop.
- You already have a user-facing test command you'd run anyway (
npm test). Run it inline - the iteration-loop's bash
kind is for cases where the check is the whole contract, not a superset of an existing suite.
How to pick a check kind
| Check kind |
Use when… |
Example |
bash |
Pass/fail is fully deterministic - an exit code, a pattern match, or a JSON predicate. |
Verify /tmp/hello.txt equals "hello": cmd=test "$(cat /tmp/hello.txt)" = "hello", default passOn: exit-zero. |
bash |
A validator / linter / schema-checker exists and is the ground truth. |
cmd=xmllint --noout out.svg, or cmd=jq -e . config.json with passOn: exit-zero. |
bash (regex: predicate) |
Want to match stdout against a pattern regardless of exit code. |
cmd=rsvg-convert --version, passOn: regex:^rsvg-convert 2\.. |
bash (jq: predicate) |
Command outputs JSON and pass/fail is a structural check on it. |
cmd=curl -s http://localhost:8080/health, passOn: jq:.ok == true. |
critic |
Pass/fail is subjective or visual - "does the chart have three clearly-labeled slices", "does the tone match the brand". |
artifact: out.svg, rubric: "SVG renders a pie chart with 3 slices, each labeled with its percentage, using the brand palette." |
critic |
The artifact is visual (PNG/SVG/JPG) and you want the critic to literally look at it. |
read out.png inside the critic attaches the image; qwen3 / haiku / opus-vision all critique directly. |
Rules of thumb:
- Start with
bash if any deterministic validator exists. Exit codes are cheap, zero-cost, and leave no ambiguity.
- Use
critic when the rubric fits better as English than as code. "Three slices, correct labels, brand palette" is
harder to express as a validator than as a rubric.
- For visual critics on cheap local models, use
modelOverride: "llama-cpp/qwen3-6-35b-a3b" on the critic spec so the
judge runs on a vision-capable local model instead of a strong paid one.
- SVG+vision path on this host:
magick in.svg out.png (ImageMagick is available; rsvg-convert is not). Have the
critic read the rendered PNG - vision models can't parse raw SVG reliably.
Authorship flow
Hybrid authorship is deliberate - you draft, the user reviews, only then does the loop start.
1. Declare. Call check with action: "declare":
{
"action": "declare",
"kind": "bash",
"artifact": "out.svg",
"cmd": "xmllint --noout out.svg",
"maxIter": 5,
"maxCostUsd": 0.1
}
Or for a critic:
{
"action": "declare",
"kind": "critic",
"artifact": "out.svg",
"rubric": "SVG is a pie chart with exactly 3 slices. Each slice has a visible label with its percentage. Uses the brand palette (red #e53, blue #36c, yellow #fd3).",
"maxIter": 5
}
The tool writes .pi/checks/<task>.draft.json. task defaults to default; v1 supports one active task at a time.
2. Surface the draft. Print the draft inline so the user can review, and tell them how to start:
Proposed check (.pi/checks/default.draft.json): bash xmllint --noout out.svg, max 5 iterations, $0.10 budget.
Review the draft, then run check with action: "accept" (or /check accept default) to begin iterating.
Do NOT call check run before the user accepts - the tool will refuse while a draft is pending.
3. Accept. On user confirmation, call check with action: "accept". The extension renames draft → active and
seeds the iteration state. The system prompt starts showing a ## Iteration Loop block every turn from now on.
4. Iterate. See next section.
5. Close. On stopReason: "passed" or when the user accepts best-so-far, call check close with an appropriate
reason (passed, budget-iter, budget-cost, wall-clock, fixpoint, user-closed). The task directory archives
under .pi/checks/archive/<timestamp>-<task>/.
Iteration discipline
This is where small models lose - they edit, say "looks right", and skip the verification step. The rules below
mechanize the contract.
- After every substantive edit of the artifact, run the check. The extension counts edits since the last
check run; hitting the threshold (default 2) triggers a strict nudge at turn end.
- Read the verdict before editing again. Open the returned
observation_path if you need the raw output, check the
issues array, and address the highest-severity issue first (blocker → major → minor).
- One focused edit per iteration. Shotgun edits make verdicts hard to attribute. If multiple issues exist, fix the
blocker, re-run, then move to the next tier.
- Never claim "the artifact is done" without a passing
check run this turn. The claim-nudge extension catches
phrases like "looks right", "matches the spec", "the chart is ready" when no successful verdict was recorded - either
run the check or retract.
- Honor the injected status block. Every turn while a task is active, the system prompt ends with
## Iteration Loop (task: default) carrying iteration count, last verdict, best-so-far, remaining budget, and a
Next step: line. That line is your directive - follow it.
- Budget exhaustion is not failure. If
stopReason comes back as budget-iter, budget-cost, or wall-clock, the
extension returns best-so-far. Report it to the user and ask whether to accept, extend the budget via a new declare,
or reshape the rubric.
- Fixpoint = stop editing blindly.
stopReason: "fixpoint" means two consecutive iterations produced identical
artifact bytes. Edit differently or close the loop - spinning won't help.
Composition with todo
One iteration loop = one todo. Keeps the plan and the loop in sync so the user can see both in the injected blocks.
- When you
check accept, park the matching todo in review with a note like "iterating - parked on check/default".
- While iterating, the todo stays in
review; individual iterations are NOT separate todos (they'd churn the plan).
- Only
complete the todo after check run returns stopReason: "passed" (or the user accepted best-so-far on budget
exhaustion). Completion note: "check default passed on iter 3, score 1.00".
- If the loop terminates without a pass (budget-iter, fixpoint) and the user wants to retry differently,
reopen the
todo and declare a new check with adjusted rubric/budget.
Use scratchpad for cross-iteration context you want surviving compaction: the rendering pipeline command
(magick in.svg out.png), non-obvious rubric interpretations, the draft's model override. One short note per item, not
an iteration log - the extension already maintains history.
Anti-patterns
- Don't skip authorship because "the check is obvious." Weak models often draft checks that look sensible and subtly
mis-spec the rubric. The user's review catches that cheaply.
- Don't
check run without editing between iterations. Wasted spin; every call snapshots the artifact and (for
critic) burns cost. The fixpoint detector will stop you after two identical bytes, but don't make it do your job.
- Don't rely on the claim nudge to catch edits. The strict edit-without-check nudge exists because the claim nudge
only fires when the model vocalizes a completion claim - and small models often skip the claim entirely. Run the check
proactively.
- Don't iterate without reading the prior verdict. The
issues array is your signal. Making a guess without opening
it is how loops wander.
- Don't widen the rubric mid-loop to make the check pass. If the rubric was wrong,
check close with
reason: "user-closed", then declare a corrected check. Never hand-edit .pi/checks/default.json mid-run to cheat
the verdict.
- Don't forget to
close. Active tasks keep injecting the status block every turn; a stale active check clutters
every unrelated conversation until the user notices. close on every terminal stop-reason.
- Don't declare a second task while one is active. v1 enforces single-task - the second
declare will error. Finish
or close the first.
- Don't use
modelOverride on the critic without a reason. The critic inherits by default. Override only to push
vision grading to a cheap local model (llama-cpp/qwen3-6-35b-a3b) or to deliberately downshift when the judge is the
bottleneck.
Quick reference
| Situation |
Move |
| "Produce an SVG that …" / "Generate a chart that …" / "Make a Y that satisfies Z" |
check declare with an appropriate kind; surface draft; iterate after accept. |
| Deterministic validator exists |
kind: "bash", cmd: "<validator>", default passOn: "exit-zero". |
| Rubric is easier in English than code |
kind: "critic", stating the rubric as listable numbered requirements. |
| Visual artifact (PNG / rendered SVG) |
kind: "critic" - the critic reads the file and pi auto-attaches the image. |
| SVG needs rendering first |
cmd: "magick in.svg out.png && <rest>" (on this host; rsvg-convert is absent). |
| Command emits JSON, pass depends on structure |
passOn: "jq:<expr>" (needs jq on PATH). |
| You just got a verdict with issues |
Read the highest-severity issue first, make one focused edit, check run again. |
stopReason: "passed" |
check close with reason: "passed". Mark the matching todo complete. |
stopReason: "budget-iter" (or budget-cost / wall-clock) |
Report best-so-far. User chooses: accept, extend budget, or reshape rubric. |
stopReason: "fixpoint" |
Stop - bytes aren't changing. Edit differently or close + re-declare. |
Model claimed "looks right" but never ran check run this turn |
Run check run now or retract the claim before the nudge forces the round trip. |
Already have a bash check of the same shape from a previous task |
Re-declare it. v1.5 will add recipe reuse via memory; for now copy the fields. |
1---2name: iterate-until-verified3description: Use the `check` tool to run a disciplined feedback loop whenever the task is "produce an artifact and confirm it satisfies a verifiable contract" - render an SVG / chart / diagram, generate a config / regex / test / fixture / snippet that has to match a spec, write code that has to pass a test suite, produce prose a critic can rubric-judge, or "make a Y that does Z". Never claim the artifact is done without a passing verdict from `check run` this turn.4---56# Iterate Until Verified78When the job is to produce an artifact (rendered image, SVG, chart, config file, regex, generated code, rubric-graded9prose) and a concrete pass/fail check exists, use the `check` tool to run the loop instead of eyeballing the result. The10extension tracks iterations on disk + in the session branch, enforces budgets, and catches "looks right to me" claims11you didn't actually verify.1213You are the **actor**: you edit the artifact. The **check** (a bash command or a critic subagent) is the environment.14Every `check run` returns a verdict; you use it to decide the next edit - the same shape as a tight15test-driven-development loop, just formalized so weak models can follow it too.1617## When to declare a check1819Reach for `check` when **all** of these hold:2021- You are producing a concrete artifact: a file, a diagram, a chart, a config, a generated snippet, a rendered image.22- A pass/fail contract exists or can be stated: "the file exists and contains X", "the SVG has three pie slices", "the23 regex matches these examples and rejects those", "the output validates against this schema", "the rubric says…".24- You expect to iterate at least once. If the first draft is obviously right, you still benefit - one `check run`25 catches the off-by-one case cheaper than a user round-trip.2627Skip the check when:2829- The deliverable IS the answer to a question ("what does this function do?"). No artifact, nothing to re-render.30- A single tool call completes the task with deterministic success (`git status`, "read line 40"). The round trip costs31 more than it saves.32- The artifact is genuinely throwaway (one-off `/tmp` scratch). If the user will look at it once and discard it, don't33 spin up the loop.34- You already have a user-facing test command you'd run anyway (`npm test`). Run it inline - the iteration-loop's bash35 kind is for cases where the check is the whole contract, not a superset of an existing suite.3637## How to pick a check kind3839| Check kind | Use when… | Example |40| --------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- |41| `bash` | Pass/fail is fully deterministic - an exit code, a pattern match, or a JSON predicate. | Verify `/tmp/hello.txt` equals `"hello"`: `cmd=test "$(cat /tmp/hello.txt)" = "hello"`, default `passOn: exit-zero`. |42| `bash` | A validator / linter / schema-checker exists and is the ground truth. | `cmd=xmllint --noout out.svg`, or `cmd=jq -e . config.json` with `passOn: exit-zero`. |43| `bash` (`regex:` predicate) | Want to match stdout against a pattern regardless of exit code. | `cmd=rsvg-convert --version`, `passOn: regex:^rsvg-convert 2\.`. |44| `bash` (`jq:` predicate) | Command outputs JSON and pass/fail is a structural check on it. | `cmd=curl -s http://localhost:8080/health`, `passOn: jq:.ok == true`. |45| `critic` | Pass/fail is subjective or visual - "does the chart have three clearly-labeled slices", "does the tone match the brand". | `artifact: out.svg`, `rubric: "SVG renders a pie chart with 3 slices, each labeled with its percentage, using the brand palette."` |46| `critic` | The artifact is visual (PNG/SVG/JPG) and you want the critic to literally look at it. | `read out.png` inside the critic attaches the image; qwen3 / haiku / opus-vision all critique directly. |4748Rules of thumb:4950- Start with `bash` if any deterministic validator exists. Exit codes are cheap, zero-cost, and leave no ambiguity.51- Use `critic` when the rubric fits better as English than as code. "Three slices, correct labels, brand palette" is52 harder to express as a validator than as a rubric.53- For visual critics on cheap local models, use `modelOverride: "llama-cpp/qwen3-6-35b-a3b"` on the critic spec so the54 judge runs on a vision-capable local model instead of a strong paid one.55- SVG+vision path on this host: `magick in.svg out.png` (ImageMagick is available; `rsvg-convert` is not). Have the56 critic read the rendered PNG - vision models can't parse raw SVG reliably.5758## Authorship flow5960Hybrid authorship is deliberate - you draft, the user reviews, only then does the loop start.6162**1. Declare.** Call `check` with `action: "declare"`:6364```json65{66 "action": "declare",67 "kind": "bash",68 "artifact": "out.svg",69 "cmd": "xmllint --noout out.svg",70 "maxIter": 5,71 "maxCostUsd": 0.172}73```7475Or for a critic:7677```json78{79 "action": "declare",80 "kind": "critic",81 "artifact": "out.svg",82 "rubric": "SVG is a pie chart with exactly 3 slices. Each slice has a visible label with its percentage. Uses the brand palette (red #e53, blue #36c, yellow #fd3).",83 "maxIter": 584}85```8687The tool writes `.pi/checks/<task>.draft.json`. `task` defaults to `default`; v1 supports one active task at a time.8889**2. Surface the draft.** Print the draft inline so the user can review, and tell them how to start:9091> Proposed check (`.pi/checks/default.draft.json`): bash `xmllint --noout out.svg`, max 5 iterations, $0.10 budget.92> Review the draft, then run `check` with `action: "accept"` (or `/check accept default`) to begin iterating.9394Do NOT call `check run` before the user accepts - the tool will refuse while a draft is pending.9596**3. Accept.** On user confirmation, call `check` with `action: "accept"`. The extension renames draft → active and97seeds the iteration state. The system prompt starts showing a `## Iteration Loop` block every turn from now on.9899**4. Iterate.** See next section.100101**5. Close.** On `stopReason: "passed"` or when the user accepts best-so-far, call `check close` with an appropriate102`reason` (`passed`, `budget-iter`, `budget-cost`, `wall-clock`, `fixpoint`, `user-closed`). The task directory archives103under `.pi/checks/archive/<timestamp>-<task>/`.104105## Iteration discipline106107This is where small models lose - they edit, say "looks right", and skip the verification step. The rules below108mechanize the contract.109110- **After every substantive edit of the artifact, run the check.** The extension counts edits since the last111 `check run`; hitting the threshold (default 2) triggers a strict nudge at turn end.112- **Read the verdict before editing again.** Open the returned `observation_path` if you need the raw output, check the113 `issues` array, and address the highest-severity issue first (blocker → major → minor).114- **One focused edit per iteration.** Shotgun edits make verdicts hard to attribute. If multiple issues exist, fix the115 blocker, re-run, then move to the next tier.116- **Never claim "the artifact is done" without a passing `check run` this turn.** The claim-nudge extension catches117 phrases like "looks right", "matches the spec", "the chart is ready" when no successful verdict was recorded - either118 run the check or retract.119- **Honor the injected status block.** Every turn while a task is active, the system prompt ends with120 `## Iteration Loop (task: default)` carrying iteration count, last verdict, best-so-far, remaining budget, and a121 `Next step:` line. That line is your directive - follow it.122- **Budget exhaustion is not failure.** If `stopReason` comes back as `budget-iter`, `budget-cost`, or `wall-clock`, the123 extension returns best-so-far. Report it to the user and ask whether to accept, extend the budget via a new `declare`,124 or reshape the rubric.125- **Fixpoint = stop editing blindly.** `stopReason: "fixpoint"` means two consecutive iterations produced identical126 artifact bytes. Edit differently or close the loop - spinning won't help.127128## Composition with `todo`129130One iteration loop = one todo. Keeps the plan and the loop in sync so the user can see both in the injected blocks.131132- When you `check accept`, park the matching todo in `review` with a note like `"iterating - parked on check/default"`.133- While iterating, the todo stays in `review`; individual iterations are NOT separate todos (they'd churn the plan).134- Only `complete` the todo after `check run` returns `stopReason: "passed"` (or the user accepted best-so-far on budget135 exhaustion). Completion note: `"check default passed on iter 3, score 1.00"`.136- If the loop terminates without a pass (budget-iter, fixpoint) and the user wants to retry differently, `reopen` the137 todo and `declare` a new check with adjusted rubric/budget.138139Use `scratchpad` for cross-iteration context you want surviving compaction: the rendering pipeline command140(`magick in.svg out.png`), non-obvious rubric interpretations, the draft's model override. One short note per item, not141an iteration log - the extension already maintains history.142143## Anti-patterns144145- **Don't skip authorship because "the check is obvious."** Weak models often draft checks that look sensible and subtly146 mis-spec the rubric. The user's review catches that cheaply.147- **Don't `check run` without editing between iterations.** Wasted spin; every call snapshots the artifact and (for148 critic) burns cost. The fixpoint detector will stop you after two identical bytes, but don't make it do your job.149- **Don't rely on the claim nudge to catch edits.** The strict edit-without-check nudge exists because the claim nudge150 only fires when the model vocalizes a completion claim - and small models often skip the claim entirely. Run the check151 proactively.152- **Don't iterate without reading the prior verdict.** The `issues` array is your signal. Making a guess without opening153 it is how loops wander.154- **Don't widen the rubric mid-loop to make the check pass.** If the rubric was wrong, `check close` with155 `reason: "user-closed"`, then `declare` a corrected check. Never hand-edit `.pi/checks/default.json` mid-run to cheat156 the verdict.157- **Don't forget to `close`.** Active tasks keep injecting the status block every turn; a stale active check clutters158 every unrelated conversation until the user notices. `close` on every terminal stop-reason.159- **Don't declare a second task while one is active.** v1 enforces single-task - the second `declare` will error. Finish160 or close the first.161- **Don't use `modelOverride` on the critic without a reason.** The critic inherits by default. Override only to push162 vision grading to a cheap local model (`llama-cpp/qwen3-6-35b-a3b`) or to deliberately downshift when the judge is the163 bottleneck.164165## Quick reference166167| Situation | Move |168| --------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |169| "Produce an SVG that …" / "Generate a chart that …" / "Make a Y that satisfies Z" | `check declare` with an appropriate kind; surface draft; iterate after `accept`. |170| Deterministic validator exists | `kind: "bash"`, `cmd: "<validator>"`, default `passOn: "exit-zero"`. |171| Rubric is easier in English than code | `kind: "critic"`, stating the rubric as listable numbered requirements. |172| Visual artifact (PNG / rendered SVG) | `kind: "critic"` - the critic `read`s the file and pi auto-attaches the image. |173| SVG needs rendering first | `cmd: "magick in.svg out.png && <rest>"` (on this host; `rsvg-convert` is absent). |174| Command emits JSON, pass depends on structure | `passOn: "jq:<expr>"` (needs `jq` on PATH). |175| You just got a verdict with issues | Read the highest-severity issue first, make one focused edit, `check run` again. |176| `stopReason: "passed"` | `check close` with `reason: "passed"`. Mark the matching todo `complete`. |177| `stopReason: "budget-iter"` (or `budget-cost` / `wall-clock`) | Report best-so-far. User chooses: accept, extend budget, or reshape rubric. |178| `stopReason: "fixpoint"` | Stop - bytes aren't changing. Edit differently or close + re-declare. |179| Model claimed "looks right" but never ran `check run` this turn | Run `check run` now or retract the claim before the nudge forces the round trip. |180| Already have a `bash` check of the same shape from a previous task | Re-declare it. v1.5 will add recipe reuse via `memory`; for now copy the fields. |