How Boof Runner works
Task lifecycle, the planner → developer → reviewer pipeline, observer, verdict protocol and the review fix loop.
How it works
A task moves physically between status directories under <data>/tasks/, kept in sync
with its status frontmatter field:
drafts/ → pending/ → ongoing/ → done/
↘ actions/ (awaiting your answers) ↘ failed/The runner polls pending/, picks the highest-priority task, and runs
planner → planner-refinement → developer → reviewer, writing a report at every step.
If the planner raises clarifying questions, the task parks in actions/ and the runner
moves on; you answer in the Actions tab and it resumes. Developer/reviewer questions
are routed back to the planner via file — never surfaced to you.
Turn the runner on/off any time from the UI; it finishes the in-flight step gracefully.
Observer, verdicts and the fix loop
One model develops, a second observes, a third gives the final review. Only a
reviewer APPROVE lets a task finish as done.
- Observer (optional). Assign
settings.roles.observer({ "tool", "model" }, also settable in the Settings page;(none)disables it) or override it per task withtools.observerin the task frontmatter. It runs after the developer and sees the task prompt, the final plan and the diff against the worktree base. - Verdict protocol. Observer and reviewer reports must end with exactly one line:
VERDICT: APPROVE,VERDICT: CHANGES_REQUESTED(findings listed above it) orVERDICT: REJECT(not fixable in place). The last non-empty line must be the verdict (case-sensitive, trailing whitespace and markdown emphasis ignored); any other verdict line with a different verdict anywhere in the report makes it invalid, identical repeats are fine. A missing, malformed or conflicting verdict counts as not approved. - Gate and fix loop (
settings.review.gate, defaulttrue). OnCHANGES_REQUESTEDthe developer re-runs in the same worktree to fix each real finding with a test or rebut it with evidence, then the same role re-checks. A reviewer fix is re-checked by the observer first. Each of observer and reviewer getssettings.review.maxFixRoundsfix rounds (default2).REJECT, an invalid verdict or a spent budget fails the task withreview not approved: <role> <verdict>(INVALIDfor a bad verdict); the worktree and branch are kept. Withgate: falsethe observer still runs but verdicts are only recorded. - Diff cap. Prompts inline the diff only up to
settings.review.maxInlineDiffBytes(default120000); above that they get the--statsummary, the untracked-file list and a note to read it withgit diff <base-sha>.
All keys are optional; existing settings.json and task files keep working.
{
"roles": { "observer": { "tool": "claude-code", "model": "sonnet" } },
"review": { "gate": true, "maxFixRounds": 2, "maxInlineDiffBytes": 120000 }
}For offline tests the mock tool takes a marker in the prompt:
[[mock-verdicts observer=CHANGES_REQUESTED,APPROVE reviewer=APPROVE]] (per role, one
verdict per round, last repeats; also NONE, GARBLED, CONFLICT). The marker must
sit on one line, the last complete marker wins, and unknown tokens throw.