Skip to content

Adversarial Convergence Loop

Use when independent review produces findings and authorized work fixes them: pre-commit review, review/remediation pairs, root-cause fixes, report revision. A review-only request never authorizes edits. Canonical procedure; other plugins ship generated byte-identical copies — installation never needs a sibling plugin.

Scope and authority

Before first review, record objective, specification, reported symptom, allowed files, implementation author, acceptance checks, existing workflow/run. Preserve unrelated changes. Unknown failures need root-cause evidence before a fix. Parent owns edits, integration, gates, acceptance; reviewers return evidence, not permission.

Begin with independent baseline review W_0; for unchanged scope and reviewed coverage, every later review must strictly decrease the nonnegative integer total score until clean. There is no arbitrary three-review cap: strict integer decrease bounds the sequence to at most W_0 + 1 completed reviews, including the baseline. A round = independent challenge, validated findings and table, parent-owned feasible fixes, and escalation of unfixable severe findings. Fixes need a later independent review before closure; no round may end with an unreviewed fix presented as clean. Plan the evidence and next review before dispatch. This does not expand a parallel wave or bypass a caller's stricter reviewer budget, admission, provenance, approval, or retry rules.

Each reviewer gets: specification, symptom, exact current diff, relevant source and acceptance evidence, scope exclusions, requested checks. Never supply the implementation narrative or fork the implementing agent's conversation. Identify earlier findings by stable signature when asking for verification; retain original evidence without the implementer's justification. Supply source inline when the reviewer can't read files — paths alone aren't inspectable context. Record actual reviewer identity and coverage. Claimed independence or a ledger field is not proof of runtime provenance.

A brief that has the reviewer run adversarial shell or CLI cases requires: case files written with a file-editing tool, never a shell heredoc (a terminator line inside a case ends it early and the rest runs as commands); every oracle run that can reach gh or git with a fake binary first on PATH plus dummy credentials (GH_TOKEN=dummy, GH_HOST=x.invalid). The lead's own shell commands follow the same two rules.

Budget each challenge for at most 1,500 output tokens when the runtime exposes an enforceable per-reviewer limit; otherwise request a concise response under that target and record that the limit is advisory. Never truncate, omit, or silently discard a finding to meet the target. Narrow irrelevant context before dispatch; if complete findings exceed the budget, retain them and report the overrun or capacity gap rather than claiming a hard cap.

Before the first fix, build one discovery inventory from the requested scope: source paths, material unchanged producers and consumers, and known cross-path interactions. Assign each to a reviewer or mark it unreviewed with a reason; complete the first challenge of every admitted part before treating the finding set as a baseline. A byte-complete context or chunk manifest proves delivery, not that a reviewer inspected every path. If a necessary part cannot be reviewed, preserve its findings and coverage gap, then stop any overall clean claim. Do not compare total scores as a measure of resolver quality when the source scope or reviewed coverage changes; show the scope change and compare previously open signatures separately.

For every new finding after the baseline, record whether its failing source was present in an earlier reviewed snapshot, introduced by a resolver edit, added to the requested scope, or cannot be dated. Cite the earlier/current source or coverage evidence. An earlier byte-identical file can still contain a missed defect; a newly inspected file is not automatically a newly introduced defect. Keep the finding new in the old/new table when first reported, regardless of this origin classification.

State-changing workflows: include producer, its next consumer, next ordinary user action in the review boundary, plus relevant unchanged callers. Ask whether the verified postcondition actually establishes the consumer's precondition, and whether all local checks could pass while the user's intended result fails. Prior design approval and tests asserting the selected operation are insufficient evidence of that end-to-end property. Request a concrete counterexample or outcome-based probe for material assumptions on identity, ownership, destination, lifetime, retry, resume. Record missing downstream context as a coverage gap; zero findings does not establish uninspected workflow safety.

No permitted independent route available: stop the independent loop, reason independence-unavailable. Accessible parent-owned source inspection may continue within authorization, but it's sequential review without independent coverage. Preserve rejected specialist evidence separately; never relabel it accepted, substitute parent tests for independent review, or launch another paid/rejected route without required approval or changed evidence.

Before retry or a different route, record the observed failure class and its evidence. Unknown receipt rejection is not evidence of transient transport failure. Retry only when the owning workflow's retry contract permits that class; otherwise diagnose, or use a genuinely different permitted route with its limitations disclosed.

Review, triage, fix, verify

  1. Challenge and collect findings. Freeze complete scoped source before dispatch: staged, unstaged, relevant untracked content, not only committed HEAD. Record revision, exact diff SHA-256, coverage and exclusions; preserve source, diff and returned report unchanged. Join the independent review before editing. Give each finding a stable signature, severity, evidence, affected files and structural flag; reconcile duplicates without dropping earlier findings.
  2. Report past and new findings. Validate each finding against specification and source; reject a false positive only with explicit refutation evidence. Identify a stable signature by the failed behavior and invariant, not only the particular path used to trigger it; map a sibling bypass back to the earlier signature and retain its original evidence. Carry prior signatures and closed entries so regressions reopen under the same signature. Classify the source origin of each first-reported finding as described above. Calculate the score from open and fixed-pending-verification findings. After each completed validated challenge, show the cumulative old/new table once before fixing, dispatching or stopping. No table is due while a challenge is pending or before the first validated round; give a plain status instead. The initial review has no score ratio.
  3. Resolve every feasible finding. Before dispatching a fix, write its failed invariant, original counterexample, next ordinary consumer, and the related routes or write sites that could violate the same invariant in the existing Remediation section of loop-report.md. Give the resolver those facts and the bounded source, without claiming a particular patch will close them. Fix valid findings within the authorized scope where they occur, using the smallest coherent change. Before accepting the resolver handoff, inspect the actual changed source and run a failing-before/passing-after check at the ordinary consumer for the original counterexample and at least one distinct sibling route when one exists; if no safe executable probe exists, record the source proof and its execution limit. Self-reported files_changed, a passing unit test for the selected line, and a patch summary alone do not establish closure. A structural flag or repeated signature alone is not a stop; it signals contract impact or a possible shared cause to investigate. Do not widen scope, alter a public API, or cross an approval boundary without required authority. Mark a local fix fixed-pending-verification until a later independent reviewer checks a newly frozen snapshot; tests cannot replace that review. For every open finding, record the parent action in loop-actions.json: fix, escalate, or defer, with evidence, owner and next action. A fix action's evidence names the invariant, original case, consumer check, sibling check or explicit absence, and source paths; after the fix, the parent compares those paths with the actual diff and records the result. The checker validates record shape, while the parent validates truth. defer needs a concrete reason why the lower-tier finding cannot feasibly be fixed now.
  4. Escalate unresolved severe findings. If a security, critical or high finding cannot be resolved within current scope and authority, record escalate, stop dependent work and ask for the exact missing decision with evidence, attempted fixes, owner and next action. Never defer those tiers. Do not claim completion or commit with an open security or critical finding. Report unresolved lower tiers too at a stop; neither a green test nor a lower score silently closes them.
  5. Repeat or stop. Freeze the corrected source and return to step 1 while the unchanged-scope total score strictly decreases and no hard stop applies. Stop when an independently reviewed current snapshot is clean or the score plateaus/non-converges. A stricter caller reviewer or retry budget still stops its dependent route. Missing independence, stale evidence, or required approval also stops the dependent route. Before clean acceptance, recapture source and require exact equality with the final reviewed snapshot; complete caller gates, artifact validation, promotion and output. A clean loop is not a merge decision or commit authorization.

After every completed challenge, after triage and finding validation but before any next fix, review or stop, record that round in the append-only round log (see Evidence ledger). Codex Rig stages it and runs PLUGIN_ROOT/bin/python PLUGIN_ROOT/shared/adversarial_loop.py --ledger <run-directory>/loop-ledger.json --append --progress, which appends the round and prints the full cumulative progress table on stderr while leaving JSON stdout unchanged. Consumers without that helper render the same table from validated rounds. Show it once for each newly completed round; never show a partial or unreviewed row.

The progress table has exactly these columns:

Iteration Critical High Medium Low Nits Weighted score

Each numeric cell is the literal old + new split: old counts currently open signatures seen in any prior completed round, including closed-then-reopened signatures; new counts currently open signatures first seen in this round. The first row uses 0 + current in every numeric column. Count only open and fixed-pending-verification; exclude verified-fixed and rejected. The Critical display combines security and critical, while the score retains weights security 20, critical 10, high 6, medium 4, low 2, and nit 1; Weighted score is old weighted + new weighted and equals W_n. An empty ledger prints no progress table or unreviewed zero row.

The default table is one aggregate: each row sums every open finding from every reviewer, area and domain in the loop's reviewed scope for that round. Render a per-area or per-domain table only when the user asks for that filter, and never replace the aggregate with per-area tables or extra columns. When a round widens the reviewed scope, keep the aggregate row and state the added areas and their weighted contribution in one line of prose below the table.

The stderr transcript includes a legend explaining the old/new split and preserves all prior rows. Call --progress only for a newly completed round; repeated status updates do not repeat the table. This is an agent delivery instruction, not a machine-enforced chat count: the helper is a repeatable renderer, and the host supplies no transcript receipt to this validator. The in-turn progress output is separate from the final result table, whose no-review row remains an explicit unavailable result rather than a fabricated clean zero.

Table delivery checkpoint

Show the old/new convergence table only after a newly completed, validated challenge, before the next fix, review or stop. Include every completed row from the checker, not only the latest row. A pending reviewer, provisional finding or parent-only inspection is not a completed row. If no round is validated, explain the unavailable evidence in prose without a not-run/N/A progress table. A later status update, approval question or pause without a new round gives the score series or relevant finding in prose without repeating the table. The canonical final Results table remains required by its output contract; do not add a duplicate convergence table to the final handoff.

Scores and stop rules

Sum weights of open findings once per stable signature:

security critical high medium low nit
20 10 6 4 2 1

W_n = score at independent review n, initial W_0. r_n = W_n / W_{n−1}; never divide when the previous score is zero — that run is already clean. Severity follows demonstrated impact. Explain disputed assignments; never inflate stylistic issues or soften security defects for a better score.

Report both carried-open weight and first-reported weight with the total. Carried-open weight measures the remaining prior finding set after fresh verification; first-reported weight measures discovery during this round, including preexisting defects missed earlier and defects introduced by remediation. A falling carried-open score does not excuse new findings, and a rising total does not prove every resolver failed. Preserve the conservative total-score stop below; do not reset the baseline or relabel old findings to make its ratio pass.

Hard stops override score improvement:

  • Scope or authority missing for a structural fix stops that dependent fix, not all unrelated authorized work. Structural = a contract change such as script arguments/output, module boundary, shared schema or skill order; record its blast radius and request required approval. A structural finding that can be fixed within current authority stays in the loop.
  • The same open signature in two consecutive reviews requires root-cause evidence before another fix attempt, not an automatic stop while the total score still converges. Apply the fixed recurrence policy; a third occurrence stops the affected workstream and asks for human direction.
  • An unresolved security, critical or high finding that cannot be fixed within current authority must be escalated with owner/action. Open security or critical forbids completion and commit regardless of trend.
  • Missing independent coverage, invalid evidence or stale final snapshot cannot produce a clean decision.
Condition Reading Action
W_n == 0 and current independent coverage is valid clean Complete the owning workflow's remaining gates.
0 < r_n < 1.0 converging Resolve feasible findings and continue while the unchanged-scope total integer score strictly decreases and no authority/evidence or stricter caller-budget stop applies.
r_n == 1.0 plateau Stop this approach; ask for the next scoped decision.
r_n > 1.0 non-converging Stop; another unchanged attempt is not justified.
Stricter caller reviewer or retry budget exhausted with findings open caller-budget Stop that dependent route; escalate unresolved severe findings, disclose all residue and request direction.

Stopping on a plateau is a valid process outcome — not successful remediation, not proof the task is permanently impossible. Preserve the score series and stopped ledger. Further work needs the specific missing decision or materially changed scope/evidence; a new invocation alone never resets recurrence or bypasses a stop. A user-approved continuation keeps all stopped ledgers, reports and score series byte-exact, links the prior run and records the renewed authority and current baseline; it never silently resets prior findings, recurrence or coverage obligations.

Evidence ledger

Keep loop-ledger.json, its round log loop-rounds.jsonl, and loop-report.md in the owning run. Ledger records observed facts; a checker establishes structural consistency, not reviewer authenticity or source correctness.

Current schema version 2 splits the ledger so completed rounds are only appended, never rewritten:

  • Header loop-ledger.json: schema_version, implementation_author, current_snapshot — never rounds. Rewrite it in place only to move current_snapshot.
  • Round log loop-rounds.jsonl: one round object per line in index order. An absent log means no completed round. Never edit or rewrite an earlier line; a later verdict on a finding is the next round's disposition.
  • Recording a round: write it as one JSON object to <run-directory>/loop-rounds.jsonl.rec with the file tool, then append it. Codex Rig runs PLUGIN_ROOT/bin/python PLUGIN_ROOT/shared/adversarial_loop.py --ledger <run-directory>/loop-ledger.json --append; it validates the extended ledger, appends one line, and deletes the staged file, or exits nonzero, appends nothing, and keeps the staged file for repair. Claude plugins run their own bin/append_ledger.py <run-directory>/loop-rounds.jsonl, then validate the assembled rounds.
  • Historical schema 1 kept root rounds inline in one rewritten loop-ledger.json. The checker still reads those archives and the continuation digest of their exact bytes; current result validation requires schema 2.
  • Snapshot: nonempty revision, lowercase 64-hex diff_digest of exact retained snapshot bytes.
  • Round: one-based index (score labels start at W_0; no fixed upper index), reviewer, snapshot, report_path, findings.
  • Reviewer: nonempty identity, boolean independent. Identity differs from the implementation author; record effective host provenance in the report, including unavailable verification. Empty rounds list records unavailable independent review, never a clean review.
  • Finding: stable signature, tier, boolean structural, disposition, list of nonempty evidence strings. Dispositions: open, fixed-pending-verification, verified-fixed, rejected; last two require closure/refutation evidence. Carry every prior signature, tier, structural classification into later rounds. Reopened findings retain their signature.

Each report includes inspected snapshot and coverage, reviewer route/provenance limits, original findings, refutations, verification evidence, any missing inputs. Retain round-<index>.diff and current.diff for digest checks; report paths resolve inside the run. Unchanged snapshot digest does not prove independence. Changed snapshot invalidates prior closure evidence relevant to that change.

loop-actions.json is a separate parent-authored action record, not reviewer evidence. Current schema version 2: root schema_version, rounds; each completed round has its one-based index and actions list. Each currently open or fixed-pending-verification signature appears exactly once with signature, decision (fix, escalate, defer), nonempty evidence, owner, next_action, and root_cause (object or null). A fix action's evidence has five nonempty entries beginning exactly invariant:, original:, consumer:, sibling:, and source-paths:; sibling: gives a distinct route or explains why none exists. These record the closure plan before dispatch; update the same entries with actual outcomes before the next review. A repeated open signature chosen for another fix needs root_cause with nonempty claim, evidence, falsification, and rejected_alternative; a third occurrence cannot be fixed again in the same run. A severe open signature must choose fix or escalate, never defer. Validate with PLUGIN_ROOT/bin/python PLUGIN_ROOT/shared/adversarial_loop.py --ledger <run-directory>/loop-ledger.json --actions <run-directory>/loop-actions.json before the next review and at handoff. The checker can still read schema-1 archives; current result validation requires version 2. It verifies record completeness and consistency, not the truth of feasibility, cause, or fix; parent and independent review retain that judgment.

Handoff and remediation

Start with topic and outcome — e.g. “The parser review stopped because the same boundary defect remained in two independent rounds.” Never open with an unexplained “both,” “that,” “blocked,” or internal error code. State each fact once; don't repeat the entire conversation.

For every stop with open findings, report score series (W_0 → W_1 → W_2), per-tier counts, each remaining finding's disposition and reason, evidence, owner, next action. Distinguish code changes from evidence-only closure and target integration. Passing gates doesn't close findings; zero implemented fixes must be explicit.

Show only completed-round scores: first-round stop = W_0, second-round stop = W_0 → W_1. Three-label series is an example, not a requirement to invent rounds. No independent round: show not-run and the unavailable reason; never assign zero — implies a clean review.

Explain why the loop stopped in plain English and what can safely continue. Recommend one concrete remediation action and name meaningful alternatives. Ask only for the missing decision or authority; never ask the user to repeat approval already given. After authorized remediation, resume the first unmet checkpoint of the original workflow and finish its normal closing checks. Don't stop merely to tell the user to rerun a command the agent can already execute safely.