| name | review-loop |
| description | Tear apart a written artifact and iterate it to a pass. **Use it when something is already written** โ an OpenSpec change, a proposal, a spec, a diff; a pure-prose proposal belongs here too. **Nothing written yet, and the question is "which way should we go" (technology selection / architecture / whether to build)? โ that is `council`, not this.** Each round three reviewers run in parallel (Code Reviewer + Reality Checker + Codex โ a cross-family reviewer, which is the one distribution the other two cannot supply), their returns are pasted verbatim, findings merge into one triage list, a fixer applies the accepted ones, and the loop re-reviews until a terminal token. Every check reads an artifact the checked party did not author โ that is the whole design. Standing counter-pressures: a scope fence (a fix that would add features nobody asked for stops and asks), a simplicity lens (the loop only inserts, so something must count the bloat), a legibility lens (a cold reader, so an iterated review cannot turn a readable document into a patch pile), and a convergence check (if two rounds running find blockers that the loop's own last fix produced, it dispatches a root-cause analyst before giving up (Termination) instead of iterating forever). Triggers: review this change until it passes / find the ship-blockers / keep fixes minimal / ๅฏนๆฌๆฌกๆๆก/ๅๆดๅๅฏนๆๆง review ๅพช็ฏ / review ๅฐ้่ฟไธบๆญข / ้ฟๅ
review ๆๅ
ฅ่ฟๅคๆ ๆไปฃ็ . Pairs with a /goal-style completion harness for long runs; runs standalone without one. |
review-loop
Tear apart a written artifact and iterate it to a pass. Each round: dispatch reviewers โ triage โ dispatch fixes โ re-review, until a terminal token. For long runs wrap it in a completion harness (/goal on Claude Code โ last section); optional, and without one the main agent applies the evaluator's predicates to itself.
Scope. Something is already written โ an OpenSpec change (a spec-driven proposal workflow whose artifacts are markdown change proposals), a proposal, a spec, a diff. A pure-prose proposal is in scope: ยง1b's table comes back naturally empty, ยง1e applies only its three prose tags, ยง1f is the lane the round is actually for. A decision with nothing written yet is council, not this. For a trivial diff โ a typo, a one-line fix โ skip the loop: one direct review is cheaper and enough.
The evidence rule (read first โ it is the shape of every rule below)
The main agent dispatches the reviewers, grades their findings, writes the fixes, and writes the status block the evaluator then reads. It has exactly one incentive: to be done. So a check whose input the main agent typed is not a check โ it is a disclosure wearing a check's clothes, and it will be written clean on the round it matters.
A check is only as strong as the artifact it reads. Three tiers, and every gate names its own.
| the artifact | who makes it | what it proves |
|---|
| the slot returns โ each reviewer's verdict line + findings list, pasted verbatim | the reviewers | the round was dispatched, and these are the findings |
| the triage list โ every finding after re-grading and dedup, with severity, location, disposition, pasted | the main agent, but derived from the returns above and diffable against them | what the round decided, and whether the grading holds |
Landed: โ the diff the round's fixes actually produced, not the ranges the spec aimed at. On code: git diff --stat (its ยฑ counts are โญ's operand) or the full diff โ bare ranges carry no counts and cannot feed the recompute. On prose: the changed text itself โ git diff -U0's full hunks, additions and removals both, pasted (the removals are what keep net: recomputable on prose) | the fixer's edit | what the loop actually changed, in the same coordinates as next round's findings |
On prose Landed: carries text, not coordinates โ โซ's operand there is text, and handing it --stat degrades the loop's one unfabricatable check to a typed number.
Tier 1 โ mechanical output: a proof. A command's output, a diff, a re-run. The main agent cannot produce it without running the command, and anyone can re-run it. Landed: and the anchors' command + output are here.
Tier 2 โ a pasted subagent return: a cost barrier, not a proof. The main agent could fabricate one โ but fabricating a five-thousand-word adversarial review is doing the review. That is the whole value: paraphrase is cheap and is laundering; verbatim is not. Verbatim means the raw return, in its original language, unedited โ a translation, a tidy-up or a splice of two non-adjacent fragments is a NEW artifact, and pasting one under a "verbatim" label is fabrication, not formatting. Translate beside it if the reader needs it (ๅๆ: โฆ ๏ผ ่ฏ: โฆ), never instead of it: the moment a quote is something you typed, the cost barrier is gone and the tier collapses to 3. A lane whose return was not pasted is not-run.
Tier 3 โ a status line: a disclosure. A number the graded party typed. It is not a check. Predicates โกโโค, โง and โฌ read only this tier โ they exist so an omission is visible, not so a lie is impossible. (โฆ, โฉ, โซ, โญ and โฏ each carry a recompute half over artifacts the graded party did not author; their remaining operands sit here.)
Honesty: this loop's floor is that a determined main agent can fabricate a tier-2 artifact and every tier-3 line. What it cannot fabricate cheaply is tier 1, and what it cannot fabricate at all is the intersection โซ recomputes from two artifacts it did not write. That is the real backstop, and it is one predicate wide.
The floor this cannot reach is named in the closing honesty boundary.
Terms
Each term's rules live in its section. This list is glosses and pointers.
- slot โ one of the three verdict-bearing reviewer lanes (Code Reviewer / Reality Checker / Codex). Only a slot carries a verdict. Everything else โ ยง1c augments, ยง1e, ยง1f, anchors, the bot โ is findings-only: it feeds triage and holds no verdict.
- CR / RC / MCE / ASE โ Code Reviewer / Reality Checker / Minimal Change Engineer / Security Engineer (ยง1's ladder).
- anchor โ the independent artifact you set-diff ยง1b's failure table against, to answer "did the table miss something?". Strong = mechanical output from one of ยง1's three anchor forms, artifact attached, passing ยง1's acceptance test. Weak = the CR's prose checklist โ both sides are prose and can co-miss the same point. (The cognitive sense of "anchoring" is called priming throughout.)
- category โ a kind of failure point, from exactly this closed set: โ guard / early-return / error branch / exception catch; โก assert / validation / exit-code; โข state transition (restart, reconnect, renewal, cleanup, dry-run); โฃ claimed-pass point (a test assertion, a cassette โ a recorded network fixture โ a recomputed column, a doctor check); โค config/entry fan-out (ยง1b step 0). Two more โ narrative drift (doc says X, code does Y) and forward fragility (correct now, silently breaks later) โ that no stock anchor form covers, so the loop discloses them instead of gating on them; a custom executable invariant that does cover one lifts it like any strong anchor. A category is in scope when the round's code has a point of that kind โ never "when you happen to have an anchor for it".
- set dimension โ a thing with a set of legitimate members some guard must cover: a role set, a config-key set, an enum's arms, the CLI entries. Check:
declared-coverage set (what the guard reads) vs actually-effective set (the full set in the code). Not equal = a finding.
- distribution โ the kind of defect a reviewer is structurally able to see. Two reviewers on one distribution find the same bugs; a fourth on a covered distribution is worth โ 0. A different model family is the one distribution same-family lanes cannot supply.
- the evaluator โ the small model a completion harness runs at each turn's end, literally checking the predicates in the last section, reading only the transcript (which carries the run's tool records โ see below). No harness โ no evaluator; the main agent applies the same predicates to itself and the closing honesty boundary bites harder.
- bot โ an always-on PR bot (BugBot, Copilot review) if the repo has one. The loop never invokes one; it merges findings that already exist. No bot โ nothing to merge.
- fix-induced blocker โ a blocker or major in the triage list (post-grading, post-dedup) whose location falls inside the previous round's
Landed:. On prose, location overlap is not enough โ the finding must quote text Landed: added or changed AND name a requirement that did not exist before that rewrite. A whole-section rewrite (ยง3) makes all of a section's text "changed", so text-overlap alone would count every re-expressed pre-existing rule as a regression; the requirement test is what separates a genuine fix-induced defect from a rule the rewrite merely restated. When the artifact carries native requirement/decision IDs (an OpenSpec change, a numbered spec), the intersection is by those IDs โ a verbatim string-match present in both pastes, so the evaluator still recomputes it and the graded agent cannot author it. Otherwise that same second condition is evidenced by quoting the pre-rewrite artifact โ the pre-rewrite text of the heading-bounded section the finding names, absence shown over that bounded span. Both branches are evidence forms for "did not exist before", never a further test stacked on top of it, and the quote is the main agent's to attach as the row enters the triage list โ a reviewer writing a finding cannot know what the previous round removed. So a row that reaches โซ carrying no quote is an incomplete triage row, never a row shown not to be a regression: scoring a missing quote as not-fix-induced is exactly how a loop would launder its own defects into a clean Regression: 0. Regression: is the count of surviving fix-induced blockers/majors, recomputed from the two artifacts rather than read as a number โ written N of M, where M is the round's triage blocker/major total (registered rows included in M; a โฏ-valid registered row is excluded from N and stays in M). On prose the pre-rewrite quote rides the pasted triage list, so โซ can still recompute.
- tool record โ the platform-written record of a tool call in this run (Claude Code: the session log; generically, whatever the platform itself logs). A path "has a tool record" when it appears in the arguments or output of a call this run actually made โ the check is a lookup in that log, never a recollection.
- instrument partition โ the loop's own verification instruments as opposed to the production source; authoritative definition at ยง2 step 2(a).
- delivery form โ
production (the default) / prototype / demo; a downgrading form comes only from a quoted user sentence (ยง1d); severity reads it (Verdict normalization).
- residual-floor register โ ยง2 step 2's disposition for instrument-partition findings only production-unreachable inputs reach; โฏ verifies it, and it is never fed to the RC.
- cap / patch count โ the round cap (default 10; behaviour in Termination) and the patch count (3; rule in ยง3).
Verdict normalization
Subagents routinely go off-spec: wrong token, guessed severity, no verdict line. The main agent normalizes every verdict itself โ never get dragged by the token a subagent wrote. (And this is exactly why the raw returns are pasted: normalization overwrites them, so the evaluator needs both the raw and the normalized side.)
Severity โ every finding carries exactly one:
- blocker โ broken on ship: wrong results / data corruption / crash, security hole, breaks a dependency contract or schema, core path unusable. Must fix to pass.
- major โ serious defect: design flaw, missed failure mode or edge, clearly inconsistent with existing code, missing a key check. Fix by default.
- minor โ local issue, doesn't affect correctness. Fix if cheap.
- nit โ pure style / format. Usually skip.
Severity reads the delivery form (Terms). At demo/prototype, a finding whose fix would add a guardrail the requirement never asked for โ rate limiting, authn/z, hardened input validation, quota, audit logging โ grades minor advisory, never blocker/major: reviewers otherwise stack production guardrails onto a demo round after round. Two walls, both absolute: an exploitable defect in code that exists keeps its full severity at any form (a demo that leaks secrets is still broken โ when both readings apply, this wall wins), and the form's only source is ยง1d's quote (Terms).
Verdict tokens: pass = APPROVE (the RC tends to write CLEAR; same token); reject = CHANGES-REQUESTED (NEEDS WORK is a synonym).
Re-grade first. Promote the under-marked (especially a subagent that rejects while labelling its reason minor); demote the over-marked. A demotion holds only if you name which severity definition the original grade got wrong; otherwise the finding stays unresolved. A blocker/major is resolved only when fixed, explicitly demoted / ruled not-applicable by that rule, or validly registered per ยง2's residual-floor disposition (โฏ holding) โ a verbal "noted, skipping" with the severity unchanged is unresolved.
Normalization rule: any unresolved blocker/major โ the round is a reject, even if every subagent wrote APPROVE; only minor/nit left โ a pass. Throughout, "pass / reject" means this normalized verdict.
APPROVE-DEGRADED, CAPPED and NOT-CONVERGED are main-agent terminal tokens โ never in a slot.
The loop
1. Dispatch reviewers
Dispatch all three slots in parallel, in one message. Each prompt is self-contained and carries: the artifact + the change; the truth source (spec / contract / existing code โ for the RC a to-be-proven baseline, never settled fact); what prior rounds fixed, so reviewers hunt new ship-blockers (except the RC, which gets ยง1b's de-polluted form); the agreed scope (ยง1d) with the [out-of-scope] tagging demand; the severity definitions; and the output contract โ end with the findings list (severity / location / explanation / fix) and, as the final line, exactly one verdict token. On a prose artifact, location names the heading-bounded section as well as the line โ line numbers move when a section is rewritten; sections don't, and the section is what Landed: intersects on.
Not-yet-implemented artifact โ the proposal's own deliverables are not missing prerequisites. When the artifact is a change not yet built (an OpenSpec change / proposal / spec), every reviewer prompt carries this rule, the RC's de-polluted form included: the files, functions, fields, endpoints and components the proposal's own tasks will create or modify are the deliverables โ their absence from the current codebase is the expected state, never a finding. "Referenced symbol doesn't exist yet", "baseline unproven because the code isn't there", and an internal build-order forward-reference among the proposal's own tasks are the work, not defects in it โ a to-be-proven baseline means don't assume the existing system already works, never the proposal's own future work is a gap. A prerequisite gap is real only for something (a) no task in the proposal creates, (b) it never declares as an existing assumption, and (c) not self-evidently present โ an unstated external or pre-existing dependency (a third-party API; an existing column the change reads but no migration adds; a service it assumes is deployed). Internal contradictions (proposalโspec mismatch, a spec no task satisfies, a data-flow that never connects) and genuine design flaws stay in scope, graded normally. Tell: a finding whose fix is "prove the baseline" or "add a task to build X" where building X is already the proposal's job is the reviewer reporting the work as its own blocker โ drop it.
Paste each slot's return โ verdict line and findings list โ verbatim into the transcript. A slot with no pasted return is not-run (Tier 2: paraphrase is laundering).
The three slots:
- Code Reviewer โ correctness, contracts, edges, security, consistency with existing code. Mandatory deliverable โ the guard checklist: every guard/check point with
file:line, over the same categories and scope as ยง1b's enumeration. This is the loop's weak anchor. For every checkpoint consuming a set dimension, add three columns โ declared-coverage set โ actually-effective set โ equal? โ because writing the two sets side by side turns "the guard covers less than exists" into a comparison the reviewer cannot skip past.
The prior-fix context primes the CR's findings, never its enumeration. The checklist demand is the same fixed wording every round, and it sits before any prior-fix context in the CR prompt โ the enumeration instruction itself never mentions rounds or regions. A checklist is an enumeration, not a judgment โ and a CR told "round Nโ1 fixed region R, hunt new blockers" under-enumerates R, so the anchor thins over exactly the region the loop's own fix just touched, anchor โ table comes back empty there, and the loop stops without ever re-anchoring it. That is the mechanism by which a loop certifies the defect it just introduced.
A CR APPROVE without its deliverable is not a pass โ the slot is not-run(missing guard checklist). One exception: an artifact with no guard points at all (a pure-prose proposal) โ an empty checklist that states the scope it enumerated over counts as produced.
- Reality Checker โ dispatched per ยง1b.
- Codex โ
subagent_type: codex:codex-rescue, dispatched read-only: it is the review lane and can never be the fixer. A different model family is the one distribution the other two lanes cannot supply โ a same-family Independent Reviewer would be a fresh view, not a new distribution. It returns prose by default; the prompt must explicitly demand the final verdict line. (/codex:adversarial-review is manual and emits no verdict line โ this is the only Codex path the loop may auto-dispatch.) Absent โ the loop degrades honestly per Termination; there is no same-family substitute. Security-artifact failure mode (recoverable). When the artifact is security-sensitive โ a parser, input validator, sanitiser, or regex/ReDoS surface โ an attack-framed prompt (attack, exploit, construct a ReDoS input, adversarial) can trip Codex's own cybersecurity content filter, which aborts the turn (Codex error: This content was flagged for possible cybersecurity risk โฆ Turn failed) and yields no verdict. Frame the request as correctness, not exploitation โ describe the function and its correctness floor and ask for an input that produces wrong or incorrect output: equivalent finding power, no abort. This not-run is recoverable โ re-dispatch once with the reframed prompt before recording it, and name it not-run(content-filter) so a reader tells a recoverable filter-trip from a genuinely silent slot or a terminal API error.
Resolution ladder โ how a named lane or fixer (CR / RC / MCE / ASE) becomes a running subagent. Three tiers, resolved per lane at dispatch; echo the tier. Codex never enters the ladder: it dispatches directly as codex:codex-rescue.
The catalog is a prerequisite the user installs (per the README); the loop reads it and never writes to it โ and neither does anything it dispatches: a reviewer that stamps APPROVE on code comes from a checkout the user chose to trust, at a revision they control. Missing โ the lane degrades and says so; the loop never installs it.
Two identifiers, named apart, because conflating them silently drops a lane through every tier: the type-name is the string a platform would register a lane under (Code Reviewer); the catalog path is where the file lives (engineering/engineering-code-reviewer.md). The catalog's frontmatter name: is the type-name, not the filename slug. The table below is a lookup key, not a registry โ it says which string to search the host's subagent list for; it never says the host has it.
| lane | type-name (the string to look up) | catalog path (local tier) |
|---|
| CR | Code Reviewer | engineering/engineering-code-reviewer.md |
| RC | Reality Checker | testing/testing-reality-checker.md |
| MCE | Minimal Change Engineer | engineering/engineering-minimal-change-engineer.md |
| ASE | Security Engineer | security/security-appsec-engineer.md |
(ASE is the trap: the catalog file's frontmatter reads Application Security Engineer, which is registered nowhere and matches no filename. A lane that looks itself up under that string finds no catalog file and halts.)
Read the catalog before you resolve any lane. The read is the only step of this ladder with a falsifiable artifact: a file either is at that path or it is not, and a tool record says which. "The host has this type registered" is a sentence, and a sentence costs nothing to write โ put it first and the halt below is one an agent can talk its way past without ever touching the filesystem.
| step | act | outcome |
|---|
| 1 โ resolve | read the lane's catalog path under ~/.agency-agents/ (a git clone of the catalog; nests two levels). Confirm the file's frontmatter name: equals the lane's type-name โ ASE excepted: its file reads Application Security Engineer; accept that known alias (the table's type-name is the dispatch string) | no file โ the lane is unresolved โ halt, below |
| 2 โ dispatch | the file is there, so the lane is real. Its type-name is an installed subagent type on this host โ dispatch that subagent natively, marker [registered] โ strongest: the host owns the persona and its tool scope. Otherwise embed the file's body, minus frontmatter, as a general-purpose agent's persona, marker [local: <catalog path>] | each marker carries what falsifies it โ the path a reader can stat, or (โฎ) the dispatch record that must name the type |
An unresolved lane halts the run โ PREREQUISITE-MISSING (<lane> โ see README), a hand-back like OUT-OF-SCOPE-PENDING, not a verdict: the catalog is a prerequisite the user installs, and a run without a real reviewer has nothing to certify with.
Deterministic strong anchors โ optional to run the loop, required for a clean APPROVE; findings-only. The main agent runs these before dispatching; hits become ยง2 findings, artifacts become ยง1b's anchors. Three forms:
- Executable invariant check โ a change-impact rule for a bug category ยง1b has already written up (ยง1b's invariant library). Not available on round one of a fresh artifact โ the library is empty until a recurring bug has been written into it. Say so rather than inventing one.
- Off-the-shelf static analysis โ shellcheck / clippy / ruff / an existing CI contract check / AST or grep extraction of guard points.
- New-dimension fan-out grep โ a real, reproducible grep of the diff's new dimensions across every repo consumer, hunting
declared โ effective and "same error, different exit codes per entry point".
Acceptance test โ all three strong forms. An anchor counts as strong for category X only if it left its command + output and its output covers every file:line the CR checklist listed under X. Without this, a zero-warning ruff run "strong-anchors" categories โ and โก having certified nothing, and the clean tier is buyable with a linter. The scan proves what it scanned; only an independent enumeration says whether it scanned enough, and the CR checklist is the round's only independent enumeration. Compared during ยง1b's reconciliation, after the CR returns. CR columns missing or empty โ nothing to compare โ weak. A strong anchor proves "the scanned set is clean", never "the scan was complete" โ pattern completeness is not mechanically provable, which is why the clean tier is rare and honestly so.
A fourth form โ implement-and-run (not-yet-built proposals, weak by default). When the artifact is a change not yet built whose rules are executable โ mechanically implementable logic with an observable result (a computed expected value, a grader's decision, a state transition) โ the main agent may implement those rules and run them, feeding what the run actually does into ยง2 as findings. This catches what a pure-prose double pass structurally misses: a rule whose stated and executed behaviour diverge โ the defect (a signature that cannot express its own contract, two constants that compose wrong) that reading nods past but running exposes. It is findings-only, single-family, weak by construction: the main agent writes the implementation and runs it, so nothing independent exists to set-diff it against โ the deliverables aren't in the codebase yet, the CR checklist is empty, and the acceptance test above has nothing to compare โ so a round resting on it still carries [weak-reconciliation: not-covered]. Much of this is already in scope ("a spec no task satisfies" is a rule its own execution would expose); this form only names the technique and binds it to a disclosure. State the disposition on the Anchors: line, required on a not-yet-built proposal: implement-and-run: ran (weak) when it ran; implement-and-run: not-run when the rules are executable but it did not โ the terminal token then carrying [mechanic: not-built] (Termination), because the loop must not certify executable rules it never ran; or implement-and-run: n/a (no executable rules) when there are none.
Echo this status block every round. A failed subagent returns empty, indistinguishable from "no problems" โ so an empty slot never defaults to pass. Slots carry the normalized verdict plus the resolved tier.
Slots: Code Reviewer=APPROVE [registered] โ Reality Checker(ยง1b)=CHANGES-REQUESTED [local: testing/testing-reality-checker.md] โ Codex=not-run(empty)
Augment: none
Scope fence: agreed scope anchored | form: production (default) | out-of-scope findings: 0
Anchors: โ strong(invariant cmd#1) โคstrong(grep cmd#2) โฃweak
Simplicity: net +18 this round / +42 cumulative (measured, from Landed:) | would-remove: -140 cumulative | over-eng: 1 open
Legibility: unfollowable 0
Spot-audit: row 14 | re-ran empty-input | observed exit 1 vs claim exit 0
Landed: src/auth.py | 61 +42/-19, src/cli.py | 8 +8/-0
Regression: 2 of 7 (triage blocker/major โฉ last round's Landed) | hits: src/auth.py:96, src/auth.py:131
Residual-floor: tests/grader.sh:41 (a: instrument โ | b: trials-5 config, adversarial-only input)
On a prose round the Landed:/Regression: pair reads instead โ the changed hunks verbatim, additions and removals both; the additions are what next round's findings must be quoted against:
Landed: ยง3 -"must rewrite the section" +"A fix landing in a section that has already taken 3 of
this loop's insertions must rewrite the section instead of adding a caveat." | ยง1e
+"net: counts what ยง1e's own findings would remove."
Regression: 1 of 6 (a triage blocker/major quoting text Landed: added) | hits: ยง1e's "would remove"
Canonical skip strings โ a lane that did not run says so in the string the evaluator matches, and never the bare word none where a gate reads "no anchor at all":
| line | skip string | when |
|---|
Anchors: | n/a (0 in-scope categories; weak anchor = the produced-empty CR checklist) | pure prose |
Scope fence: | no full requirement context, skip | ยง1d โ only beside the search's command + hit-count |
Simplicity: | no code or prose this round, skip | ยง1e |
Legibility: | no prose in this artifact at all, skip | ยง1f |
Spot-audit: | no verified-safe rows | ยง1b |
Landed: | n/a (no fix dispatched) | the terminating round, by construction |
Regression: | n/a (no prior fix) | round 1, or after a fix-less round |
Residual-floor: | none | no registered rows (ยง2) |
The Landed:/Regression: skip strings matter more than they look: the round that passes is the round that finds nothing and therefore fixes nothing. Without its skip strings, the convergence predicate would deadlock the one round allowed to terminate.
Paste the triage list (ยง2) into the transcript every round. It is the second of the evaluator's three artifacts, and an input it cannot see is an input it cannot check.
1b. Failure enumeration (Reality Checker slot)
Semantic reviewers verify the happy path plus already-written assertions and systematically miss failure paths ร edges ร false-green (a false green: a signal that reports success while the thing it certifies is broken or unexercised) โ nothing forces them to enumerate, and reading beside green tests primes "it was tested" into "it is settled". The RC slot removes both causes: a forced enumeration structure, fed de-polluted input.
De-polluted input. The diff + truth source marked to-be-proven; all evaluative framing stripped (no "already APPROVE'd N rounds", "tests pass", "fixed last round"). The one prior-round artifact it gets is a neutral row ledger โ row ID + boolean "did this round's fix touch this row or its dependencies", terminal-state values and praise excluded (verified-safe is itself a verdict; re-feeding it primes re-confirmation). The ledger answers "which rows need re-verification", never "which rows may be skipped".
Step 0 โ new-dimension lateral fan-out (before the enumeration; decides which unchanged consumers the table must include). Steps 1โ2 are vertical and miss the lateral class: the diff introduced a new dimension and some unchanged consumer was never updated. Mechanically extract the diff's new dimensions (new config keys, new enum arms, new role/capability constants, changed exception โ exit-code mappings); grep every consumer of each dimension across the repo (preflight, validation, attribution, probes, each CLI entry, except blocks); pull every consumer the diff didn't touch into the table.
Two honesty rules. The executor sets the anchor strength โ the RC hand-listing consumers is weak; the main agent's real pre-dispatch grep with command + output is strong (subject to ยง1's acceptance test). And running step 0 never makes category โค "covered" โ scope comes from the code having that kind of guard; a guard with no main-agent artifact stays in scope under a weak anchor and caps the tier, else a category and its anchor would be self-produced by one prose step.
Step 1 โ mechanical enumeration (list, don't judge). Every point of categories โ โโฃ plus step 0's pulled-in consumers, in a table with file:line. Scope = changed diff lines + directly-impacted unchanged code: unchanged callers of a changed contract/signature/return semantics, called helpers, unchanged cleanup/finally paths, unchanged tests and CI whose assertions depend on changed behaviour โ regressions hide in code the diff made newly reachable or newly dead. An empty table states the scope it enumerated over.
Step 2 โ per-row adversarial. For each row, instantiate the failing inputs applicable to that row's kind from the menu {empty / null / malformed / timeout / partial-write / restart-mid-op / concurrent dual-driver / returns-error-not-value / token-expired / zero-row query / transient error that clears on retry} โ a menu, not a per-row quota: demanding all eleven of every row forces fabricated evidence, the very false-green this pass hunts. Record "observed behaviour vs contract claim". Any reports-success-while-the-underlying-thing-broke-or-lied = blocker (fault-tolerant degradation is not lying โ the contract baseline tells them apart).
Contract silent on a failing input โ record "behaviour undefined" โ never pass by default. It is a finding, graded like any other: undefined behaviour that would produce a wrong result is blocker/major (fix the contract, or the code); genuinely don't-care is accepted-degraded with minor named. It is never verified-safe, and never left as a bare unresolved row โ every real diff has an input its contract is silent on, so leaving them unresolved would make every real diff unpassable.
Step 3 โ out-of-table tail. Untestable acceptance, pseudo-acceptance, whole-design unprovables โ appended at the end.
Harness false-green sub-template. When the diff touches any green-signal-producing file (by content intent, not directory: tests/, *.sh, conftest.py, test config, *.bats, Dockerfile, CI yaml, a doctor script, a soak run), additionally walk: vacuous assert (always-true, or asserting on the mock itself) / exception swallowed then marked success / cassette recorded from a broken run / flush-writing an empty or garbage file / write fails yet marked flushed / a check returning 0 on query failure or zero rows. This is where the evidence itself lies โ orthogonal to production failure injection, hence its own list.
Completeness reconciliation. The table cannot attest itself (prose reviewers satisfice), so set-diff it against an anchor. Bidirectional, on file:line, mandatory:
| direction | meaning | terminal state |
|---|
| anchor โ table | omission โ an anchor point absent from the table | unresolved |
| table โ anchor | unverified row โ a file:line in neither the diff nor the anchor | unresolved; two possible causes (a fabricated row, or a legitimately-impacted line the anchor missed) โ don't presume which; settle only after adding or confirming the anchor |
1:1 pin (hard rule): each anchor point maps to exactly one table row โ a merged mega-row doesn't count as enumerating its members, else anchor โ table is always empty and the diff is theatre. The pin binds only under a fine-grained anchor; under a weak one, a merged row is the declared incompleteness.
In-scope categories = every category the round's code actually has a guard point for โ the union of the ยง1b enumeration, the CR checklist and all strong anchors, decoupled from which categories have anchors. The gate reads categories โ โโค only; drift and fragility never enter it โ they can neither block nor unlock a tier, only force the disclosure suffix: without strong-anchor coverage of drift/fragility, every terminal token carries [narrative-drift/forward-fragility: not-covered]. Honesty boundary: under weak reconciliation, scope-truth itself comes from prose enumeration โ if both prose passes miss a whole category it silently drops out; only a strong anchor decouples scope from prose.
Anti-vacuum (pure proposal). An empty in-scope set is never read as "every in-scope category has a strong anchor, vacuously". On a pure-prose artifact the ยง1b table is empty by design, so gate clauses 1โ2 (Termination) would evaluate to true without executing โ the whole mechanical apparatus vacuously satisfied, leaving only "three reviewers found nothing", which is precisely the false-green this loop exists to hunt. Therefore, on an empty ยง1b table, gate clauses 1โ2 are satisfied instead by ยง1f: the cold read ran with its return pasted, unfollowable = 0, and every Q4/Q5 item carries a disposition. ยง1f is the lane that does the real work on prose; on that artifact class it is the gate, not an advisory. The tier is APPROVE [in-scope: empty/pure-proposal] โ a clean pass whose suffix discloses the empty in-scope set; it is a coverage gap, not a degradation.
Executable invariant library. For each recurring bug category, one lightweight executable change-impact rule: pattern hit = flag + demand evidence. Example (capability-lifecycle): a diff adding requires_capabilities: [X] must show โ X's provider, โก its acquisition path (eager or lazy, and when), โข where preflight checks X relative to that acquisition โ any missing โ that row unresolved. One rule per real recurring bug lifts that category from weak prose to strong deterministic; a new category's first occurrence still slips (weak reconciliation and the bot backstop it โ then write its invariant).
Bot findings are pure increments: they enter triage but never enter the coverage proof, never raise a tier, never remove a disclosure suffix. Running ยง1b โ or the bot โ is not coverage; only anchors are.
Every row ends in exactly one of five terminal states (the gate checks terminal states, not "has a disposition"); rows that became findings merge into ยง2:
verified-safe โ verified against its applicable failing inputs, behaviour matches contract, evidence attached (which inputs + observed vs claim). A bare label counts as unresolved. The main agent spot-audits at least one claimed row per round โ among verified-safe rows, the one whose failing input is cheapest to actually re-run (a command over a code walk, a walk over a judgment); state the pick in the Spot-audit: line: re-check its evidence against the truth source, re-running the cheapest applicable failing input, and echo it. A round with any verified-safe row and no Spot-audit: line audited nothing โ and the spot-audit is the only thing standing between a fabricated evidence field and a pass.
fixed โ became a finding, fixed (pointer attached).
accepted-degraded โ explicitly demoted or ruled not-applicable, with an auditable reason keyed to a severity definition; no named definition โ still unresolved.
registered (residual-floor) โ disposed per ยง2 step 2 with its (a)(b) evidence attached, โฏ holding; a registration where either clause fails is unresolved.
unresolved โ not verified, not fixed, reason doesn't hold, or a terminal state claimed without evidence.
Expectation setting. Without a strong anchor per in-scope category โ most small repos โ the steady state is APPROVE [weak-reconciliation: not-covered]: the change is clean, its completeness merely unproven. That is a disclosure suffix, not a degradation โ APPROVE-DEGRADED is reserved for a real accepted degradation. To retire the suffix and reach a bare clean APPROVE, add the anchors.
1c. Adaptive augmentation (optional, findings-only, no slot)
The main agent may add specialists per change domain โ along exactly two axes, both meaning "a new distribution":
- Knowledge axis โ named domain knowledge the slots lack: reentrancy, WCAG contrast, query-plan pathology, RTOS timing, tax lawโฆ
- Method axis โ a clearly different review method: structural design, threat modelling, forward fragility. Not another generic semantic reviewer.
Trigger pre-pass (before dispatch; leave the matrix in the transcript): scan changed-file globs ร content greps ร subject โ matched domain โ named gap or lens โ resolvable specialist? โ dispatch, or skip with the reason recorded. Mapping examples, non-authoritative โ the registry wins (installed types + the local catalog outrank this list): *.solยทdelegatecall โ Blockchain Security Auditor; DB migration โ Database Optimizer; ARIA โ Accessibility Auditor; CI/IaC โ DevOps Automator; proposal/design text โ Software Architect; security-sensitive change โ ASE, or AI-Generated Code Security Auditor when the code under review was itself assistant-authored (hardcoded secrets / broken row-level security / prompt-injection sinks assistants ship by default); multi-tool or long-lived codebase with doc-vs-code divergence โ Codebase Archaeologist (a method-axis lens that is the one augment actually anchoring this loop's narrative-drift / forward-fragility categories, which are otherwise disclosed, not gated).
Three add gates โ all must hold, or don't add. The burden of proof is on the adder; a reviewer whose new distribution you can't name is itself a defect โ over-augmentation is the vacuous-assert of review lanes.
- You can name the new distribution ("frontend-related" is not one).
- The domain is actually touched โ a matrix hit, not a "maybe".
- It resolves to at least one confirmed specialist (
0 confirmed โ SKIP; >1 handled below). Primary: the ladder's local path match. Fallback for a sub-concept with no filename (reentrancy โ Blockchain Security Auditor): grep -ril "<term>" ~/.agency-agents/*/ --include="*.md" 2>/dev/null โ the */ keeps it inside the division directories the ladder resolves from, and an empty or errored listing is 0 confirmed โ a candidate generator only. Both paths end in the same confirm: read each candidate's frontmatter name:, confirm the role is the domain, drop anything without frontmatter (that check is what rejects READMEs and decoys). Then: >1 โ prefer the candidate whose frontmatter name: is the domain (accessibility โ Accessibility Auditor); still >1 โ dispatch the top two and merge their findings โ they are findings-only, one extra augment costs a round and a wrong SKIP costs the finding. 0 confirmed โ SKIP.
Two ways to skip a matrix hit, graded apart:
| skipped becauseโฆ | grade |
|---|
| the expert genuinely doesn't exist (not registered, not in the catalog) | not a finding โ disclose it: the Augment: line carries expert unavailable with the grep command and its candidate count pasted, and the terminal token carries (<domain>: expert unavailable, generic coverage only) |
| the expert was resolvable and you skipped anyway | a major you did not fix โ justify it like any other |
Augment findings feed ยง2 only โ no slot, no verdict, and they never erase the CR checklist anchor (even the correctness-class ASE stays findings-only). Paste each augment's return verbatim, exactly as a slot's โ an augment is a lane (Terms), so Tier 2 binds it: a lane whose return was not pasted is not-run, and a finding credited to an unpasted augment did not run and does not count. The paste is the only thing a reader can diff the main agent's transcription of those findings against. Echo each with its tier: Augment: +Blockchain Security Auditor(knowledge:reentrancy)[local: security/security-blockchain-security-auditor.md]; else Augment: none.
Method-axis backfill when Codex is absent: a different model family has no same-family substitute. A method-axis specialist (Software Architect for the structural view) may backfill to reduce the miss rate, but never lifts the [third-slot: not-run] disclosure โ increment-only, no slot.
1d. Scope fence (mandatory when full requirement context exists)
Prevents scope creep laundered as bug-fixing: reviewers propose "robustness", triage fixes by default, and the loop ends having built features nobody asked for.
Raise the fence, once at start. If the context contains a complete requirement description the AI and user settled on (explicit goal / acceptance / boundaries โ not one vague instruction), extract and restate an agreed scope list: what's in, what's out, which contracts the user already approved. The test for "complete": every item on the list quotes a sentence the user actually wrote in this session โ a boundary you cannot quote is a boundary you invented. If writing it forces a guess, skip the fence (echo the canonical skip string from the Canonical-skip-strings table) rather than fence on a guess โ but a skip is valid only after Look-before-you-skip below: its search's command + hit-count must sit beside the skip string. Never block, never invent scope. Look before you skip. The requirement often lives in a file this session never quoted โ a linked spec, an OpenSpec change, a design doc โ and the session-only test above cannot reach it, so a skip that never searched reads identically to one that searched and found nothing. Run one search for a requirement artifact and paste its command + hit-count, the evidence ยง1's anchors already owe; a boundary quoted from one of those requirement artifacts (path + the quoted sentence) is as quotable as one typed here โ provided a user sentence settled that artifact's scope, by writing the requirement or by adopting the artifact. The relaxation moves where the settled text lives, never whether anything was settled โ otherwise an assistant-authored draft no user adopted, the artifact under review above all, would let the checked party write its own fence. Zero hits skips honestly; a non-zero count that still skips must name why none of those hits carried a quotable boundary โ a search proves you looked, never that there was nothing to find. The fence also names the delivery form (Terms): quote the user sentence establishing demo/prototype if one exists; no quotable sentence โ production โ the fail-safe direction, because a guessed downgrade waives guardrails the user never waived. The form needs one quotable sentence, not a complete fence: a skipped fence still names the form when such a sentence exists โ echo it as form: demo ("<quote>") โ and a sentence arriving mid-run sets the form from the next round; a quote elicited by the main agent's own question carries that question echoed beside it. Never ask a self-answering form question โ one carrying its own recommended answer (is this just a demo?): a neutral question the user happens to answer with a form is fine, and its echoed eliciting question (โฉ) lets a reader verify it did not lead โ but the fence quotes the user's own words, never a form you fed them. One demo quote opens two doors at once: severity drops that whole guardrail class to minor advisory (Verdict normalization), and ยง1e's never-simplify set stops protecting it โ so a question carrying its own recommended answer would manufacture the waiver instead of finding it. A skipped fence attaches [scope-fence: not raised] to the terminal token โ the opt-out costs its disclosure, and a fence raised on unquotable boundaries is visible to anyone who asks for the quotes.
The scope binary (triage runs it once per blocker/major):
- In-scope: fixes a failure path that would cause wrong results / crash / corruption / contract mismatch under the agreed behaviour โ without adding user-visible behaviour, interfaces, config or dependencies, and without changing an approved contract โ fixed by default.
- Out-of-scope (big change): needs to add features / config / a CLI entry / a dependency not in the requirement, change an approved contract, or materially expand implementation scope (a new cross-module subsystem, an abstraction layer, retry / persistence / concurrency / cache machinery nobody asked for). Tiebreak for "materially": if the fix introduces something the user would now have to know about or operate โ a new flag, file, daemon, dependency or changed contract โ it is out; an internal guard confined to existing files and knobs is not.
Handling: don't auto-fix, ask immediately, never launder. On a hit, pause and report: the finding, why it crosses, the minimal in-scope fix vs the crossing fix, and a request for authorization. Before authorization: no fix dispatch, and no silent demotion to accepted-degraded to keep the round clean โ that is one more false-green. Authorized โ scope updates, finding becomes in-scope. Declined โ accepted-degraded, reason "out of scope, user declined".
Prompt landing, every round: reviewer prompts carry the agreed scope + the [out-of-scope] tag demand; the ยง3 fix spec pins "only touch X, add no new Y". The hard pause is Termination's OUT-OF-SCOPE-PENDING row.
1e. Simplicity counter-pressure (findings-only, no slot)
The three slots are all additive and the fixer is only locally minimal โ nothing else in the loop perceives bloat, so each round trends the artifact up: severity drains while lines climb, and Termination fires on a diff far larger than necessary. This is the subtractive counter-pressure.
Run on every round whose diff contains code or prose โ the ratchet argument holds verbatim with "clauses" for "lines". On the loop's first round, run it over the whole artifact, not just the diff, and carry net: forward cumulatively: a per-round diff lens sees one individually-justified insertion at a time, and the accrued total is invisible to it by construction โ which is exactly how a loop adds 60% to a document while every round's addition looks necessary. Neither code nor prose โ the skip string. Dispatch review-loop:simplicity-lens when registered (it carries the rubric), else a general-purpose subagent carrying the rubric, in parallel with ยง1.
Rubric (self-contained). One line per finding: file:line: <tag> <what>. <replacement>. โ tags delete: ยท stdlib: (hand-rolled what the stdlib ships) ยท native: (the platform already does it) ยท yagni: ยท shrink:. On prose only three apply: delete: (a rule nobody will follow; a section restating another) ยท yagni: (a branch that can never fire) ยท shrink: (same rule, fewer words). End with would-remove: -N this round, -M cumulative or Lean already. Ship. (the status block's over-eng: counts ยง1e findings still open.) The ladder: stdlib > native > installed-dep > one line > minimum code.
Never flag the never-simplify set: validation at trust boundaries ยท error handling that prevents data loss ยท security ยท accessibility ยท anything the user explicitly requested โ not "anything a prior round's triage requested", which would exempt the loop's own output from the lens built to counter it ยท a hardware calibration knob ยท the one runnable check. The set's security and validation entries protect what the delivery form requires, never what nobody asked for: at a ยง1d-quoted demo/prototype, guardrail machinery the requirement never named (the severity clause's list) is flaggable yagni: โ the severity clause's walls still hold.
Report-only. Findings ride ยง2 as minor advisory, never auto-dispatched to the fixer โ a prose pass can't prove a line dead ("no caller" by grep โ unreachable), and auto-acting on the label is the mislabel โ delete โ re-add hazard. Promotable to major only when the bloat itself breaks correctness or a contract; never blocker. would-remove: counts what ยง1e's own findings would remove โ a proposal, and tier 3.
net: is a different number, and the gate reads it: the round's MEASURED delta (lines on code, words on prose), recomputed from Landed:, echoed beside its cumulative sum โ net: +18 this round / +42 cumulative. They are easy to conflate and the conflation is fatal: ยง1e can honestly report would-remove: -500 in a round that actually added 400 words, so a gate reading ยง1e's number would never fire on the growth it exists to catch.
1f. Legibility counter-pressure (cold-read lens โ findings-only, no slot; the gate on prose)
The second ratchet. ยง1e counters the code ratchet; this one fires when the artifact is prose a human has to read โ a spec, a proposal, an ADR, an OpenSpec change, a SKILL.md, a README, a design doc. Three things happen every round, and no other lane can see them, because every other lane is a reviewer and none is a reader:
- a fix lands where the finding was found, not where a reader needs it;
- a term the loop coined mid-run is never defined;
- a rule gets restated somewhere else in slightly different words โ now no copy is authoritative, so nobody dares change any of them.
The end state is a patch pile: a document only the loop that produced it can read. The loop's exit is "no reviewer can find a hole"; the reader's is "I can act on this correctly". Those diverge, and every round widens the gap. On an artifact with no code (ยง1b's table empty), this lane is the pass gate (ยง1b's anti-vacuum rule) โ because it is the only one doing work there.
Run on every round whose artifact is prose โ including the fix-less terminating round, where the cold read is the gate. Not "whose diff touches prose": the terminating round is by construction the round that fixes nothing, so a diff-scoped trigger would skip ยง1f on the one round that must satisfy the gate, and the pure-prose class could never pass. The skip string applies only when the artifact contains no prose at all.
The cold read. Dispatch review-loop:cold-reader when registered (its tool surface is Read-only โ the isolation is structural), else a general-purpose subagent โ and constrain what it may read, not just what you tell it โ a subagent has tools and will otherwise read git history and the neighbours, becoming a warm reader with a fresh transcript. Pin verbatim:
Read exactly these files and no others: <artifact read-set> โ the artifact, plus (only when it is one file of a co-authored bundle: an OpenSpec change, a multi-file spec) the sibling files it was written against, each named explicitly by the main agent. Do not read git history, do not read files outside this set, do not search the web. Answer only from that text. Behaviour this text explicitly delegates to a named, locatable authority โ a spec path, a source file, a contract fixture it points at โ is a dependency, not a missing local definition; flag it only when the reference is missing, ambiguous, contradictory, or the artifact introduces a new local boundary the authority cannot determine.
A return that cites any file, commit, or conversation outside the declared read-set is a warm read โ discard it and re-dispatch once (the citations are in the return; the check is a read, not trust).
Ask these five, and let it pick its own targets in 2 and 3 โ if you pick them, you are the exam-setter, the graded party, and the most primed actor in the loop:
- What is this for, and when should I use it โ and when should I not?
- Pick the most consequential scenario this document describes, and walk me through what happens. Say exactly where you would stall.
- Pick the rule you would most likely need to change; where do you edit, and what else would that break?
- Every load-bearing term this artifact introduces or redefines that a reader must understand to implement, operate, test, or change it โ quote where each first appears. A standard technical noun the artifact did not coin (a widely-known tool, protocol, or concept) is not this โ only the project's own coinages count.
- Every rule you could not follow โ quote it; say exactly what is ambiguous or impossible.
Its return goes into the transcript verbatim (the evidence rule; a Legibility: line with no pasted return is not run).
Two finding kinds from the read (the finding is always the reader's quote โ never a count of your own):
- unfollowable โ a rule the reader quoted in Q5. A rule no reader can satisfy is decoration โ worse than an absent rule, because it manufactures the appearance of a check that isn't there. Separate two kinds before counting.
unfollowable-local: the artifact's own rule is self-contradictory, or leaves an execution rule the read-set cannot supply โ major, fixed by default, and only these count toward the gate. external-reference-required: the rule points at an external artifact the cold read was forbidden to open, and the reference is pinned โ path + version/commit + digest, all three present โ so an implementer who fetches exactly that artifact can follow it; the cold reader's inability to open it is an isolation artifact, not an unfollowable rule. It is not counted; it rides the terminal token as [external-refs: N pinned, not inlined]. A reference missing its digest is not pinned โ it can drift, so it stays unfollowable-local; the one exception is a deliberately user-controlled prerequisite (the reviewer catalog, a host capability), pinned by ownership โ the user installed it at a revision they control; it must still be named (a catalog path, a host-capability name), but the digest is waived, and the version too where the prerequisite has none (a bare host capability). An unnamed reference cannot be ownership-pinned. The Legibility: line carries the unfollowable-local count, because โช bites on it.
- undefined โ a term the reader quoted in Q4 (which now counts only the artifact's own coinages, never standard technical nouns). Findings-only, riding triage: a term only the loop understands must be reverse-engineered by the next reader.
On the loop's first prose round, run the read over the whole artifact, not just the diff โ a diff-scoped pass can only slow the next pile, never clean the one you came for.
The counts are not scores (two honest runs won't agree); the finding is the reader's quote โ the number only makes it visible in the echo.
Severity: minor advisory, riding ยง2, never blocking โ except an unfollowable rule, which is a major, fixed by default. A review-demotion of an unfollowable must quote the reader's Q5 text and name why the reader, not the author, was wrong; anything less is the author grading their own prose.
Honesty boundary. ยง1f is weak-reconciliation class: a fresh reader and you are both prose passes and can co-miss. Legibility: clean proves one reader could follow this text once. ยง1f is subject to itself โ a legibility rule that exempts itself is the first rule a reader stops believing.
2. Triage
Merge findings from all sources โ three slots, anchors, augments, ยง1e, ยง1f, bot โ into one deduped list (never two fixes for one spot); re-grade per Verdict normalization. Paste the result: it is the evaluator's second artifact, and the only place the normalized severities exist.
- Scope binary first (fence up): out-of-scope blocker/major โ pause for authorization; never auto-fix, never launder.
- Residual-floor register second (the instrument arms-race exit โ without it, every round's fix to a grader spawns the next round's finding). A blocker/major is registered instead of fixed when both hold, each with pasted evidence: (a) its
file:line lies in the instrument partition โ the loop's own verification instruments (grader/checker scripts, test fixtures, CI contract files, eval configs) as opposed to the production source, fixed by file role in this spec and never declared per-run (a per-run declaration would let the graded party widen it over a core file); a dual-role or role-ambiguous file (a Makefile that builds and checks, a fixture generator) is not in the partition, and ยง1b's green-signal list is a different set; (b) an input-source artifact in the transcript, with a tool record postdating the last Landed: that touched that artifact or the production source it characterizes โ no such Landed: in the run โ any tool record for it satisfies freshness (staleness needs an intervening fix) (the eval's trials/threshold config, a fixture baseline, the honest producer's output contract), shows the failing input is one the production source never emits โ an input a sloppy-but-honest producer can emit (a hedged re-statement, a formatting habit) fails this clause, and presence of the artifact alone retires nothing. Echo it on the Residual-floor: line; dispatch no fix for it. Registration is re-derived each round from (a)(b), never carried as a verdict โ the RC's ledger stays de-polluted, and the RC re-raising a registered row is the loop working, not churn.
- In-scope blocker/major โ fixed by default. A major you won't fix โ review-demoted with its named definition, never skipped verbally.
- Minor by cost-benefit; nit usually skipped.
- ยง1e / ยง1f findings ride as
minor advisory, report-only โ except an unfollowable rule (ยง1f): major, fixed by default.
- Tell the user what was fixed and what was skipped.
3. Dispatch fixes
The dispatch table splits by artifact, because "minimal" means opposite things in code and prose. For code, minimal = the smallest textual diff โ that discipline prevents scope creep. For prose, the smallest textual diff is an inline caveat at the finding site โ exactly the ยง1f patch-pile generator. A prose fix minimizes the semantic delta instead.
| fix target | dispatch | minimality metric |
|---|
| code โ local bug, single module, mechanical per spec | MCE (ladder) | smallest textual diff |
| code โ cross-module / architecture or data-flow rethink / schema or API migration / needs a test strategy | a general-purpose subagent with the full context โ never codex:codex-rescue: ยง1 dispatches it read-only, so it cannot apply a fix; and if it did, next round the Codex slot would review its own family's output, destroying the one distribution it exists to supply | smallest coherent change |
| prose โ mechanical (wording, format, filling in an already-decided design) | main agent edits directly | โ |
| prose โ semantic (a rule wrong / ambiguous / unfollowable / restated) | never MCE โ its persona ("smallest possible diff, touch nothing unrelated") is the patch generator and will fight the spec. Main agent, or a general-purpose subagent carrying the rewrite contract | smallest semantic delta, textual diff unbounded |
The rewrite contract (pinned into every prose-semantic fix spec): rewrite the containing section from its purpose is the default, and an inline caveat at the finding site is tolerated only while the section has taken fewer than 3 of this loop's insertions โ at the 3rd (Terms: patch count) the section is a patch pile and a from-purpose rewrite is mandatory, never another caveat. Every rule keeps exactly what it required before, except the rule the finding names โ and making an unfollowable rule followable is in-scope by construction: a requirement nobody could satisfy was never a requirement. One authoritative statement per rule; the others point at it. Each rule carries, in one clause, the failure it prevents โ a rule with no why is the one the next reader deletes as noise, and then the loop rediscovers the hole. That clause is a clause, not a paragraph โ one why-clause, never an escort argument (a paragraph defending the draft the rule replaced). Land each rule where a reader needs it, not where the finding was found. A re-expression that preserves what the rules require is in-scope by construction โ it does not trip ยง1d; changing what a rule requires is a scope change and does.
Echo Landed: after the round's fixes land โ the diff they actually produced (the evidence table's code form), never the ranges the spec aimed at. Aim is a prediction the fixing party authors: declare two lines, ship a two-hundred-line rewrite, and next round's findings truthfully fall outside it โ no lie required. A diff is a fact anyone can re-derive, and it is in post-fix coordinates, the same ones next round's findings use, so an insertion cannot push the code it broke out of its own footprint.
Every fix spec (any lane): file, fix, acceptance per problem; the ยง1d scope pin at the top. The ponytail ladder is pinned for each triage-approved fix: the laziest fix that holds (stdlib > native > installed-dep > one line > minimum code); no new abstraction / config / dependency unless the finding's correctness requires it; a deliberate simplification carries a ponytail: comment naming its ceiling and upgrade path โ non-contractual: ยง1b still judges that line against the truth source next round. Never simplify the never-simplify set (ยง1e). For large cross-module fixes, split into disjoint write scopes, or keep the fix in the main thread when delegation would add coordination risk.
Fixer boundaries: uncontested items first; pause and report only when a discovery would make the spec wrong, introduce a regression, or require expanding scope; record adjacent small issues without self-expanding. A finding unfixable without crossing scope โ report back only (ยง1d).
4. Re-review
Back to ยง1, three slots in parallel โ Codex may recover mid-run: a slot that returned nothing is not-run(empty), re-dispatched next round.
Termination
A Codex non-verdict (anything unparseable = not-run): re-dispatch it next round with a stricter prompt; a round that ends with the slot still silent discloses it โ the terminal carries [third-slot: not-run], and a clean APPROVE is out of reach (the tier table). No retry ceiling, no escalation machinery: the disclosure is the whole mechanism.
Pass gate โ every pass-class termination requires all four:
- every ยง1b row has a terminal state in {verified-safe, fixed, accepted-degraded, registered (residual-floor)} โ
unresolved is a terminal state and does not satisfy this;
- the bidirectional reconciliation passed; no anchor at all = no pass; a prose-only anchor passes here but caps the tier;
- no unresolved blocker/major after normalization;
- this round dispatched no fix โ
Landed: reads n/a (no fix dispatched). Without this, a round that finds nine blockers, fixes all nine and normalizes them to fixed satisfies clauses 1โ3 and passes โ and those nine fixes are never reviewed by anything. A fix is only ever validated by the round that follows it.
On an artifact with an empty ยง1b table (pure prose), clauses 1โ2 are vacuous โ they are satisfied instead by ยง1f: the cold read ran with its return pasted, unfollowable = 0 (the unfollowable-local count โ ยง1f; a pinned external-reference-required is a disposition, not a residual), and every Q4/Q5 item carries a disposition (ยง1b's anti-vacuum rule).
"All three slots wrote APPROVE" is not a pass โ that would make the exit condition the very false-green the loop hunts.
The terminal-token table โ the single authority. Take the first matching row โ subject to one subordination to the pass gate: a pass-class row (token APPROVE / CLEAR / APPROVE-DEGRADED) is a match only when the pass gate (above) holds; on a failed gate it is not a match and control falls through to the halt rows below it. Without this, at the cap a gate-failing round with positive net: matches the bloat row before CAPPED and ships a pass-class token over unresolved blockers โ first-match order otherwise lets the lone pass-class row that omits the gate outrank the halt. A row routed through the root-cause step may resolve to its sanctioned continuation instead of its token.
| condition | token |
|---|
| a lane has no catalog file at its path (unresolved, ยง1) | PREREQUISITE-MISSING (<lane> โ see README) โ a hand-back, not a verdict; the user installs the prerequisite (or declines) and the run resumes or ends there |
| an unadjudicated out-of-scope blocker/major (ยง1d) | OUT-OF-SCOPE-PENDING (N left) โ no token, and no coverage-gap or degrade suffix (the [form: โฆ] advisory still rides per โฉ); outranks everything, including the cap. Halt for the user; the harness stops auto-continuing. If the user never replies, the loop stays halted โ no timeout converts silence into consent. No fence up โ never triggers |
Regression: โฅ1 for two consecutive rounds, and not converging-with-regressions (defined below the table) | after the root-cause step (below the table) โ NOT-CONVERGED (fix-induced blockers in 2 consecutive rounds; N items left, listed) โ a terminal hand-back, not a pass. The loop is not iterating toward a pass; its fixes are producing the next round's findings. It fires with or without a cap, which is what closes the "delete the cap line to loop until pass" hole. Two, not three: a replay of a real six-round run showed a threshold of 3 fires exactly where the loop stopped by exhaustion anyway. And the asymmetry is not close โ a false positive costs a hand-back the user overrides in one sentence; a false negative costs another N rounds of the loop certifying the defects it is inserting |
a tool record in this run wrote into ~/.agency-agents | CAPPED (catalog self-installed) โ terminal, not a pass: the personas came from a checkout the loop installed, not one the user chose. A catalog the user installs mid-run is theirs, and resolves as local from the next round |
| the pass gate fails, or a slot rejected, and rounds remain | no token โ continue |
net: > 0 for two consecutive rounds | APPROVE-DEGRADED (bloat: +N over 2 rounds, no lane removed anything) โ one reason inside the degradation row's single parenthesis when others apply; recompute net: from Landed: yourself, do not read the number. Three lanes hold a verdict and all three are additive; ยง1e is the one that subtracts and it holds none. Without a gate that reads this value, โญ checks only that the line is present, and net: +400 passes every check โ the loop's sole anti-bloat instrument reporting the bloat it does not stop |
| all rows โ {verified-safe, fixed} โง every in-scope category strong-anchored โง the in-scope set is non-empty โง Codex gave a verdict | clean APPROVE / CLEAR |
| all rows โ {verified-safe, fixed} (no real degradation), with any coverage gap: a weak-only category ยท Codex absent ยท an empty in-scope set (pure proposal) ยท a skipped scope fence | APPROVE / CLEAR carrying the matching coverage-gap suffix(es) โ [weak-reconciliation: not-covered] ยท [third-slot: not-run] ยท [in-scope: empty/pure-proposal] ยท [scope-fence: not raised] โ plus any unconditional suffix from "Suffixes on every terminal token" below (e.g. [narrative-drift/forward-fragility: not-covered]); this row lists only the coverage-gap suffixes, not the full suffix set. The change is clean; the suffix discloses unproven completeness, never a degradation โ so the subject stays APPROVE and keeps carrying the one bit "was anything actually degraded?". Stop immediately once the only open suffix reason is one no further round can change โ a pure proposal (the artifact class), Codex still silent after re-dispatch (no retry ceiling โ the disclosure, not a count, is what lets the run stop), or a weak-only category whose every runnable anchor form was already attempted this run. |
a real degradation: an accepted-degraded row or a โฏ-valid registered (residual-floor) row โ bloat (above) is the third degradation source, and every applicable reason merges into the one parenthesis | APPROVE-DEGRADED (<all reasons, one parenthesis>) โ the subject means a degradation was accepted, no longer diluted by coverage gaps. |
| the cap is reached with anything unresolved | after the root-cause step (below the table) โ CAPPED (cap-reached, N items left), listing them โ terminal, not a pass. Under a harness with no cap set, CAPPED does not exist |
The root-cause step โ mandatory before NOT-CONVERGED, or CAPPED at the cap, is written โ a bare hand-back quits at the loop's hardest moment. At each trigger, dispatch one fresh root-cause analyst โ review-loop:root-cause-analyst when registered (no Edit/Write tools; its Bash is read-only by persona โ a contractual boundary, not a structural one), else a general-purpose subagent; a fresh uncommitted context: none of this run's reviewers, fixer, or third slot โ carrying the fix-induced chain when one exists (else the surviving findings and their round history), authorized to analyze only, never to edit. Its output contract, exactly one of: (i) a structural fix approach โ a different design, not another patch on the same spot; (ii) a residual-floor registration recommendation with ยง2's (a)(b) evidence; (iii) confirmation that no viable path exists โ its menu offers dispositions (accept the hand-back, raise the cap, floor-register candidates), not fixes. An off-spec return (its VERDICT: is not exactly one of structural-fix/residual-floor/no-viable-path, its per-verdict payload โ (i)'s approach+acceptance criteria, (ii)'s (a)(b) evidence, (iii)'s why-both-routes-fail โ is missing, or its fix menu lacks 2โ4 costed options) is discarded and re-dispatched once; a second off-spec return exhausts the step โ the terminal emits carrying both raw returns labeled not-run(analyst-offspec): disclosure, not retry, the third slot's own pattern. The sanctioned continuation โ once per run: on (i) the loop resumes for the recommended fix round plus its validating re-review (gate clause 4: a fix only passes through the round that follows it); on (ii) for one re-review round in which triage applies the recommended registration under โฏ. Echo Continuation: (i|ii), R<n>โR<m> on the rounds it spans. The continuation fails when its final round does not end in a pass-class token; a fix-induced blocker there emits the terminal directly, no second continuation. On (iii), with the continuation already spent, or after it fails โ the terminal emits, carrying the current analyst's verbatim analysis and its fix menu; โฏ voids a hand-back without them.
Converging-with-regressions โ the one exception to the two-round rule. Regression: โฅ1 for two consecutive rounds is not NOT-CONVERGED when, across those rounds, both hold: the surviving blocker/major count strictly decreases (recomputed from the pasted triage lists, never read as a number), and no requirement recurs โ no round's fix-induced finding names a requirement a prior round's fix-induced finding already named (by native ID where the artifact has one, else by the requirement the finding quotes; on a code artifact with neither, recurrence = the two findings' locations overlap the same file:line span โ no overlap โ not recurring). That is a loop closing its own holes, not one manufacturing them, so it continues on the no-token "rounds remain" row โ but at most twice: a third consecutive regression round, or any round where the count fails to drop or a requirement recurs, is NOT-CONVERGED regardless of cap. The two-round rule's asymmetry still holds โ this exception fires only on evidence the defect set is shrinking, and its bound is what keeps it from becoming the "loop until pass" hole.
Suffixes on every terminal token (disclosures โ not on their own a degrade reason โ the lone exception is [residual-floor: N registered] below, which sets the APPROVE-DEGRADED subject): [narrative-drift/forward-fragility: not-covered] unless both have strong-anchor coverage ยท [weak-reconciliation: not-covered] when any in-scope category is weak-only โ the scanned set is clean, its completeness merely unproven ยท [mechanic: not-built] when the artifact is a not-yet-built proposal whose rules are executable and no round ran implement-and-run over them (ยง1) โ the loop must not certify executable rules it never ran ยท [third-slot: not-run] if Codex stayed silent ยท [in-scope: empty/pure-proposal] on an empty in-scope set (prose-only artifact) ยท [scope-fence: not raised] if ยง1d was skipped ยท (<domain>: expert unavailable, generic coverage only) if any round's Augment: line said so ยท [external-refs: N pinned, not inlined] if ยง1f disposed any Q5 rule as external-reference-required ยท [form: demo|prototype] on pass-class tokens whenever a non-production form licensed any downgrade this run (hand-backs carry it advisorily โ โฉ) ยท [residual-floor: N registered] whenever the terminating round's register carries โฅ1 โฏ-valid row (N = their count; the suffix lists the registered class rather than chasing production-unreachable inputs forever); any โฏ-valid registered row is a real degradation and sets the APPROVE-DEGRADED subject (the token table's degradation row).
Pairing with a completion harness
A harness (/goal on Claude Code) re-reads the transcript at each turn's end with the evaluator and restarts the turn while the completion condition is unmet. The status block is echoed every round; the terminal token appears only on the terminating round.
Run an adversarial review loop on this change per review-loop.
COMPLETE when the latest round ends in a terminal token: APPROVE, CLEAR, APPROVE-DEGRADED,
CAPPED, or NOT-CONVERGED. The last two are terminals, not passes โ they END the run; they do
not restart the turn. A round ending OUT-OF-SCOPE-PENDING or PREREQUISITE-MISSING halts for the user instead.
A PASS-CLASS token (APPROVE / CLEAR / APPROVE-DEGRADED) additionally requires:
1. Both experts (Code Reviewer, Reality Checker) returned a verdict, and their returns are
pasted verbatim in the transcript.
2. Codex returned a parseable verdict, or its absence is disclosed on the
terminal as [third-slot: not-run].
3. The pass gate holds (see Termination). On a pure-prose artifact the ยง1b clauses are
satisfied by ยง1f instead: the cold read ran, its return is pasted, unfollowable = 0.
4. A clean APPROVE additionally needs: every ยง1b row verified-safe or fixed, a strong anchor
for every in-scope category, and a Codex verdict. A weak-only category, absent Codex, empty
in-scope set, or skipped scope fence keeps the subject APPROVE with the matching [disclosure]
suffix โ a coverage gap, not a degradation. Only a real degradation โ an accepted-degraded row
(or the bloat / residual-floor cases) โ is APPROVE-DEGRADED. A lane
resolving at neither tier is PREREQUISITE-MISSING โ halt for the user, no token.
ECHO every round: the Slots line with resolved tiers; Augment; Scope fence; Anchors; Simplicity;
Legibility (on prose rounds); Spot-audit; Landed; Regression; Residual-floor; Continuation (on
its rounds). PASTE every slot's
return verbatim, and the triage list. The evaluator reads those artifacts, not the numbers.
RECOMPUTE Regression yourself from the pasted triage list and the previous round's Landed โ
do not take the number on faith. TWO consecutive rounds with a fix-induced blocker โ the
root-cause step (Termination); a bare hand-back does not COMPLETE.
ROUND CAP 10 (raise it freely; to loop until pass, delete this line โ NOT-CONVERGED still applies).
The evaluator judges literally โ each predicate independently checkable; any hit โ continue.
Three short-circuits, first: a round ending OUT-OF-SCOPE-PENDING or PREREQUISITE-MISSING is halt-for-user โ no predicate applies. A round ending NOT-CONVERGED is a terminal hand-back: โกโโช and โฌโโฎ are inapplicable and the run ENDS โ it is written because items are left, so leaving โข (a slot rejected) and โฅ (an unresolved row) armed would restart the turn on the one honest report the loop cannot afford to punish, and with the cap deleted it would never end. โ (the pasted artifacts they read), โซ and โฏ still apply: โซ checks the token was owed; โฏ checks it carries the analyst's return.
Suffix kinds, once for all predicates: match tokens by subject as a whole token, ignoring parenthesized suffixes (APPROVE does not match APPROVE-DEGRADED).
Continue if any holds:
- โ the latest round is missing the status block, or any slot's return is not pasted verbatim (a slot with no pasted return is
not-run) or the triage list is not pasted โ these are the artifacts every predicate below reads;
- โก the CR or RC slot is empty /
not-run;
- โข any slot is
CHANGES-REQUESTED;
- โฃ the Codex slot is
not-run(...) and the terminal token lacks the [third-slot: not-run] disclosure;
- โค the final token's subject isn't one of
APPROVE / CLEAR / APPROVE-DEGRADED / CAPPED / NOT-CONVERGED โ omitting either terminal from this list makes an honest failure report restart the turn instead of ending it;
- โฅ the ยง1b table is missing, has an
unresolved row or a row with no terminal state, or failed the reconciliation โ including no anchor at all. (On a pure-prose artifact the table is empty by design: โฅ is then satisfied by ยง1f's return being pasted with unfollowable = 0, per the anti-vacuum rule.)
- โฆ the token is
APPROVE/CLEAR while the Anchors: line shows an in-scope category weak-only and the token lacks the [weak-reconciliation: not-covered] disclosure, or a row is accepted-degraded or โฏ-valid registered (residual-floor) (that subject must be APPROVE-DEGRADED, not clean-APPROVE), or the ยง1b table is empty on a code diff;
- โง drift/fragility has no strong-anchor coverage and the terminal token lacks
[narrative-drift/forward-fragility: not-covered]; or the artifact is a not-yet-built proposal whose terminating Anchors: line carries no implement-and-run: disposition, or carries implement-and-run: not-run while no round showed implement-and-run: ran and the terminal token lacks [mechanic: not-built] โ a not-yet-built proposal must run its executable rules, declare it has none (implement-and-run: n/a), or disclose it never ran them;