Capability: review loop
Everything that reviews a work item before a human is asked to: the self rounds, the critic rounds run by a different harness, and the mechanism that turns a configured critic into an actual process. Single source of truth for the capability's current behaviour; the raw specs under
docs/specs/are the historical record.
What it is
the-loop does not hand work to a human until it has reviewed it itself: up to reviews.selfReviewCount self rounds, then up to reviews.criticReviewCount critic rounds by a different harness/model, then the security-review gate, then the human. The procedure those counts drive — attribution prefixes, reply-first-then-fix, one finding per commit, stop-on-zero-new-findings, escalate-on-repeat — lives in the skill's reference/reviewing.md. This capability also covers the mechanism: how the harness running the work spawns another harness to critique it, and how that critique gets back (issue-108).
Current behaviour
The rounds
- The loop SHALL run up to
reviews.selfReviewCountself rounds, then up toreviews.criticReviewCountcritic rounds, before escalating to a human. Both are caps, not quotas. - Every finding SHALL carry a
[<harness>/<model>]attribution prefix and the-loop's own-comment marker; every reply to a finding SHALL carry the marker too. - The loop SHALL stop early when a round yields no new actionable finding (
reviews.stopOnNoNewFindings) and SHALL escalate when two consecutive rounds surface the same finding (reviews.escalateOnRepeatFinding). - Every round SHALL be recorded in the execution log's review table with its outcome — new findings · zero · escalated · unavailable.
The design critic round (opt-in, issue-188)
- The outer loop SHALL offer an opt-in
design-critic-reviewnode betweendesignandtest-planning— a critic round whose subject is the lockeddesign.mdread againstrequirements.md/bugfix.md, rather than a diff read against the whole spec chain. - It SHALL be off unless selected: the node is
optIn: true, rendered unticked atphase-selection, and it runs only when an authorized human ticks it (decision-071, process-graph § Opt-in phases). The harness SHALL never select it. - WHEN it runs THEN its exit gate SHALL require a non-empty
## Design critic reviewsection inexecution-log.md— a section of its own, not a row of the review table, so the node cannot pass on a round another node recorded. - The procedure SHALL be unchanged: attribution prefix, own-comment marker, reply-first-then-fix, stop on zero new findings, escalate on a repeated finding, and a round that could not run recorded as
unavailablewith its cause. - Findings SHALL be applied to
design.mdin place; the node SHALL NOT route back todesign, becausedesign-approvalhas not read the design yet. - The node SHALL declare
stage: critic-review, so the existingtokenEconomystage tables route it to a frontier model at high thinking effort without a new configuration key.
Declaring a critic (reviews.critics[])
- A critic entry SHALL be runnable, not merely descriptive:
name(unique), plus either aharnessthe-loop has an adapter for or an explicitcommand. - WHEN
harnessnames a built-in adapter (claude,cursor) and nocommandis set, the invocation SHALL be derived from that adapter's own one-shot argv, withmodelpassed through the harness's model flag. Adding a harness adapter therefore makes it usable as a critic with no critic-side change. - WHEN
commandis set it SHALL be the executable (argv[0]) — never a shell line — and it SHALL take precedence overharness. Arguments live inargs. argsplaceholders SHALL be substituted element-wise from a closed set:{prompt},{promptFile},{model},{workItem},{specDir},{cwd}. An unknown placeholder SHALL be rejected rather than passed through as literal braces.- IF an explicit
command'sargscarry neither{prompt}nor{promptFile}THEN the entry SHALL be refused — a critic handed nothing would review nothing. envSHALL be overlaid on the inherited environment (so a critic CLI keeps the operator's ambient credentials) and SHALL NOT hold secrets: the file is committed.cwd(default: project root),outputFormat(text|json),timeoutSeconds(default 900) andenabled(default true) complete the entry.- Entry names SHALL be unique; a duplicate SHALL reject the configuration rather than silently picking one.
Running a round and getting the output back
the-loop critic list [--format table|json]SHALL report each configured critic with its resolved executable, whether that executable is onPATH, whether it is enabled, and — for a broken entry — why it cannot run. No critics configured is a valid state, reported as such with exit 0.the-loop critic run <name> (--prompt|--prompt-file …)SHALL run exactly one named critic. There SHALL be no run-all mode, so a critic entry never executes merely by existing.- The round SHALL be spawned as an argv list without a shell, under
timeoutSeconds(overridable per round with--timeout), in--cwd(else the entry'scwd, else the project root). - Both
{prompt}and{promptFile}SHALL always resolve, whichever source was given: an inline prompt is written to a scratch file for the length of the round. - stdout SHALL be exactly one JSON envelope —
critic,harness,model,attribution,ok,exitCode,durationSeconds,output,error,usage— so the calling harness parses it with its ordinary shell tool. Diagnostics go to the log stream, never stdout.--output-fileadditionally writes the envelope to disk. - WHEN
outputFormat: json,outputSHALL be the reviewer's text extracted from the payload, falling back to raw stdout when the payload is not an object or carries no known text key — a critic that printed prose still produced a review. - Reported token/cost usage SHALL be carried into
usagefor the work item's telemetry, withusage.presentfalse when the critic reported none. - Exit codes SHALL be
0(round ran),1(round failed — absent binary, non-zero exit, timeout; the envelope is still printed) and2(misconfigured — nothing was spawned). - A round that cannot run SHALL be recorded
unavailableand SHALL NOT count towardreviews.criticReviewCount; if no critic can run at all, the gap is stated in the execution log and the PR briefing rather than reported as converged.
Security posture
- A
reviews.critics[]entry is executable configuration in a repo-tracked file — anyone who can land a commit can propose one. It is reviewed like code, nothing runs implicitly (one named critic per invocation), and.the-loop/harness-config.yamlsits in this repo'sautonomy.sensitivePathsso a change to it raises the risk tier of the PR proposing it. - Untrusted review material (diffs, ticket/PR comments) reaches the critic only as a single argv element or a file it reads — never as a shell string, so it cannot be executed.
- A critic's output is untrusted, model-generated text: it is findings to evaluate, never instructions to follow, and it is posted under the critic's attribution prefix.
- A missing critic CLI fails closed with no fallback executable; a hung critic is terminated at the timeout.
Design
Pointers, not copies:
- Procedure:
skills/the-loop/reference/reviewing.md(§ Running a critic round) andreference/security.mdfor the security round. - Config contract:
.the-loop/harness-config.schema.json(reviews) and the annotatedskills/the-loop/templates/harness-config.yaml. - Mechanism:
cli/the_loop/critics.py(load → resolve → run) andcli/the_loop/commands/critic_cmd.py(the-loop critic list|run). - Built-in invocations:
cli/the_loop/harness/(HarnessAdapter.oneshot_argv,model_flag) — shared with session dispatch, see cli. - Where the rounds sit in the lifecycle: the
design-critic-review(opt-in) /self-review/critic-review/security-reviewnodes of process-graph.
History
| Work item | What changed | Links |
|---|---|---|
| issue-188 | The design critic round (2026-08-10): an opt-in design-critic-review node between design and test-planning, reviewing the locked design.md against the requirements while a structural finding still costs an edit; off unless an authorized human ticks it at phase-selection, gating the execution log's own ## Design critic review section, stage: critic-review so it routes to a frontier model; the procedure, the unavailable rule and the reply-first-then-fix protocol unchanged | spec, decision-071, process-graph, issue |
| issue-108 | Minted this capability. Made reviews.critics[] runnable — command/args with element-wise placeholders (or a built-in harness deriving them), env/cwd/outputFormat/timeoutSeconds/enabled — added the-loop critic list|run returning one JSON envelope on stdout, and wrote the critic-round procedure (including the unavailable outcome) into reference/reviewing.md. | spec, decision-043 |