Adversarial Convergence Loop¶
Use for an explicitly requested iterative independent review → authorized-fix loop, including pre-commit review, review/remediation pairs, root-cause fixes, or report revision. One bounded independent plan or implementation challenge does not automatically start this loop. 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.
Pre-baseline attestation. Before dispatching the baseline review W_0 of a diff the parent or its resolver authored, write it to a randomly named file in the system temp directory (for example from mktemp), outside the workspace a reviewer reads; keep its path only in the parent's context, never in the run directory, a brief or a sentinel: per changed behavior, its invariant, the sibling routes and write sites that could violate it, the ordinary consumer, a failing-before/passing-after check as its literal command line plus its result, and the source paths — the facts step 3 otherwise requires only after a finding. Order: attest, fix every gap within authorized scope (outside scope: record an exclusion or escalate), freeze the source, dispatch. The reviewer never receives the attestation or its conclusions; hand it explicit files, never the run directory, which it could list. A reviewer that nevertheless reports having read it makes that round's independence doubtful: record the limit. It is not evidence for scoring, closure or clean acceptance, and a gap closed this way is not a finding and has no score; count such gaps in attested_gaps. It does not apply to review-only requests, code the parent did not author, or a continuation of a prior ledger, where earlier signatures would make an off-ledger fix a hidden recurrence.
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.
Brief wording pilot. For a loop that is not a filter or gate review and is not a continuation whose prior ledger holds an open security or critical finding, run adversarial_loop.py --ledger <run>/loop-ledger.json --brief-arm once before the first dispatch (Claude plugins: python "$_AL", resolved as under Evidence ledger). lean states each brief item once: diff and source as paths the reviewer can read instead of inline copies (the diff path is the frozen round-<n>.diff or a snapshot copy, never the live tree, so the bytes match the recorded diff_digest), requirements as a short checklist instead of restated rule prose. full is the brief above. Both carry the same specification, scope, prior signatures with original evidence, model and independence rules. A reviewer that cannot read files, a workflow that fixes its brief shape (such as challenge-resolve's frozen context), or any doubt about coverage makes it full. Record the wording actually sent as brief in the cost log. After about 10 loops compare the arms by first-reported weight after round 1, rounds per run, brief_bytes and duration_s; a security, critical or high finding first discovered after a lean baseline pauses the lean arm: the parent writes a brief-arm-pause file beside the run directories (--brief-arm then prints full for the runs that share that parent directory; write the file beside another skill's run directories to pause them too) and deletes it once the cause is understood. Compare arms within one attestation stratum (attested_gaps null versus an integer), since a continuation is never attested.
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.
A filter or gate review (deny-list, allowlist, sandbox, permission, approval or danger gate, or any parser that decides whether input may run) also inventories the attack surface, not only source paths. Build a coverage table from the filtered language's authoritative grammar, citing the manual or specification sections used (syntax, expansions, options, compound forms): one row per construct class, one column per reader that judges it (every lane, mirror and lexer). Each cell is admitted-by-spec, refused or unmodeled, with the deciding file:line. An unmodeled cell is a finding, never a coverage note. Every review round re-fills the whole table against its frozen snapshot, not only the changed lines, and a clean claim needs a complete table. Rows come from the source, never the reviewer's choice: at least one per cited grammar subsection. Each refused cell cites the code that refuses it. Before accepting a clean table, the parent reads the cited code of every refused cell; a citation that does not refuse that construct makes the cell unmodeled. Keep each round's table as its own file in the run beside its report: the report at that round's report_path names the file, and a finding raised from a cell cites the file and row in its evidence. Compare it with the previous round's table; a later bypass inside a cell an earlier round marked refused reopens that cell's invariant. Probe or fuzz search supplements the table and never replaces it: a search that finds no bypass proves only its own inputs.
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, and store the class as an origin: earlier-snapshot|resolver-edit|scope-added|undated entry in that finding's evidence; the append action warns, never refuses, when it is missing or names another class on an open finding first reported after round 1. Where the ledger's finding evidence must equal the reviewer's report (challenge-resolve), the parent never appends the entry: it records the class and its cited evidence under the signature in loop-report.md, and the append warning stays because the ledger cannot carry it. 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.
A reviewer round that ends without a deliverable — no final reply, a refused findings write, an unparsable envelope, a response stopped by a host safety classifier, a reply truncated in transit (its required closing block, such as the envelope or ## Confidence, missing) — or returns a blocked envelope is an agent failure, never a silent not-run. A truncated reply never counts as a complete round with fewer findings: where the host can continue that reviewer, ask it for only the remainder after its last complete finding, in replies short enough to pass the transport, and keep asking while each reply adds new content; a remainder that ends with the closing block completes the round. A reply that adds nothing new, or a reviewer the host cannot continue, records the truncation as the failure cause. Record it in loop-report.md with its cause and evidence (the host's refusal text, the classifier stop, the tool error); never append it to loop-rounds.jsonl, where an empty round would read as a clean review. Re-dispatch that reviewer once with a brief that removes the cause; for a refused write, require the findings inline in the final reply (Claude Code refuses subagent writes to generic report names such as report.md or findings.md with Subagents should return findings as text, not write report files.); never spend the re-dispatch on the same write under another name. This corrected re-dispatch is a new request with a changed brief, not a retry of the same one; it still needs any approval its route requires and counts toward the workflow's attempt cap where one exists. When no attempt remains, the required approval is not held, or no brief can remove the cause, treat the route as failed without a re-dispatch. If the same reviewer route fails again or is treated as failed, switch to another permitted independent route when one exists; stop as independence-unavailable only when none remains, with every failure record preserved.
Review, triage, fix, verify¶
- 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. - 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. Name the signature after the violated invariant (
case-arm-body-judged), never after the route that broke it (nested-case-arm-unjudged); the route belongs in evidence. Give each reviewer the list of known invariant signatures, and map every returned finding to one of them or record the new invariant it violates. Recurrence and the third-occurrence stop count per invariant: a finding recurs when its signature, itsinvariant:evidence, or a key connected transitively through earlier findings appeared in any earlier round, so a new route to a known invariant is a repeat, never a new finding. 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 andfixed-pending-verificationfindings. 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. - 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
Remediationsection ofloop-report.md. Give the resolver those facts and the bounded source, without claiming a particular patch will close them. For a filter or gate, related routes means every reader that judges the invariant (each lane, mirror and lexer in the coverage table) and every construct class in the grammar source that can reach it, not only the routes the reviewer supplied. Fix a repeated invariant by changing the deciding reader's default to refuse what it does not positively model, never by adding the newly found route to a list. In an allowlist filter, a check that refuses only inputs found in a hand-written set (characters, words, option names) admits everything the set omits: record each such check as a finding, or cite the admit check that bounds it. Add regression cases per construct class from the grammar source, not only the reviewer's inputs. Price a tightened default against the real inputs the filter must keep admitting (shipped commands, recorded traffic); an ordinary input it newly refuses needs the user's acceptance before the change lands. 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-reportedfiles_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 fixfixed-pending-verificationuntil a later independent reviewer checks a newly frozen snapshot; tests cannot replace that review. For every open finding, record the parent action inloop-actions.json:fix,escalate, ordefer, with evidence naming its invariant (invariant:), owner and next action. Afixaction's evidence names the invariant, original case, consumer check, sibling check or explicit absence, and source paths, plus aprobe:entry holding the literal failing-before/passing-after command line when an executable probe exists, so the parent can rerun it after later edits and before the next round (the helper never runs it; a reviewer sees it only as its own original counterexample, never as a resolver account); 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.deferneeds a concrete reason why the lower-tier finding cannot feasibly be fixed now. - Escalate unresolved severe findings. If a
security,criticalorhighfinding cannot be resolved within current scope and authority, recordescalate, stop dependent work and ask for the exact missing decision with evidence, attempted fixes, owner and next action. Neverdeferthose tiers. Do not claim completion or commit with an opensecurityorcriticalfinding. Report unresolved lower tiers too at a stop; neither a green test nor a lower score silently closes them. - 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. Claude plugins ship a byte-identical copy of the helper in bin/ and run it the same way through the resolver under Evidence ledger: python "$_AL" --ledger <run-directory>/loop-ledger.json --append --progress. 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 | Security | Critical | High | Medium | Low | Nits | Weighted score |
|---|---|---|---|---|---|---|---|
Each numeric cell is the literal old + new split: old counts currently open findings whose signature or invariant: evidence appeared in any prior completed round, including closed-then-reopened findings and new routes to a known invariant; new counts currently open findings with neither seen before. The first row uses 0 + current in every numeric column. Count only open and fixed-pending-verification; exclude verified-fixed and rejected. Security counts security findings only and Critical counts critical findings only; the score weights are 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.
For progress and schema-3 recurrence, every completed round retains the signature and normalized invariant: keys of every finding regardless of disposition (open, fixed-pending-verification, verified-fixed, rejected), connects keys through earlier findings, and counts each earlier round holding connected keys once.
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 add columns beyond the fixed list above. 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.
- An invariant recorded in any earlier review, under the same signature, the same
invariant:evidence, or keys connected transitively through earlier findings, 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,criticalorhighfinding that cannot be fixed within current authority must be escalated with owner/action. Opensecurityorcriticalforbids 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.
At a plateau or non-converging stop, run the helper with --plateau-kind and name the kind in the handoff: stuck (every open finding was seen in an earlier round, so the fix did not land: require root-cause evidence and a different approach), whack-a-mole (every open finding is new, so the touched area hides siblings: run a sibling sweep or a broader review of it), or mixed (both). It is informational: it never changes the decision, the stop, or the user's choice.
Evidence ledger¶
Keep loop-ledger.json, its round log loop-rounds.jsonl, and loop-report.md in the owning run. Write the header before recording the first round, on every runtime: a loop run without a ledger cannot apply the root-cause and third-occurrence rules. 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— neverrounds. Rewrite it in place only to movecurrent_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.recwith the file tool, then append it. Codex Rig runsPLUGIN_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 a byte-identical copy of the same helper. A rule file gets no plugin-root substitution and the shell sets none, and this rule ships unchanged in every plugin, so it cannot name its own; every installed copy of one release is the same file. Resolve the newest installed copy whose version directory carries no.orphaned_atmarker, falling back to the source tree:_AL="$(ls -td ~/.claude/plugins/cache/borda-ai-rig/*/*/bin/adversarial_loop.py 2>/dev/null | while IFS= read -r _C; do [ -e "${_C%/bin/adversarial_loop.py}/.orphaned_at" ] || { echo "$_C"; break; }; done)"; [ -z "$_AL" ] && _AL="plugins/cc_foundry/bin/adversarial_loop.py", thenpython "$_AL" --ledger <run-directory>/loop-ledger.json --append. - Cost log
loop-metrics.jsonl(schema 2, advisory): after each completed round write one object toloop-metrics.jsonl.recand add--append-metricsto the same helper call as--append.routeisparent, oragent:,bridge:,codex:orother:plus a label (agent:foundry:challenger,bridge:review); the helper warns on any other shape so routes can be joined to outcomes. Fields:schema_version,kind(round),round,route,brief(fullorlean, the wording actually sent),brief_bytes,duration_s(a nonnegative number of seconds),tokens_in,tokens_out,tokens_total,attested_gaps(nonnegative integers;attested_gapsisnullafter round 1 and in any loop without an attestation, since attestation precedes only an authored baseline; a written attestation records an integer,0included); an unknown value isnull, never guessed. A Claude background agent's completion notification reports<usage><subagent_tokens>…</subagent_tokens>…<duration_ms>…</duration_ms></usage>: recordsubagent_tokensastokens_totalandduration_msdivided by 1000 asduration_s, withtokens_inandtokens_outnullbecause the notification gives no input/output split. A route that reports no total leavestokens_totalnull. Schema 1 records (schema_version1, notokens_total) stay accepted; Codex Rig'schallenge-resolvewrites them. A later record for the same round supersedes the earlier one. A rejected record only printswarning: metrics-rejected:<codes>and keeps its staged file: the round, the progress table and the exit status are unaffected. The log never feeds the score, recurrence, or clean acceptance. - Historical schema
1kept rootroundsinline in one rewrittenloop-ledger.json. The checker still reads those archives and the continuation digest of their exact bytes; current result validation requires schema2. - Snapshot: nonempty
revision, lowercase 64-hexdiff_digestof exact retained snapshot bytes. - Round: one-based
index(score labels start atW_0; no fixed upper index),reviewer,snapshot,report_path,findings. - Reviewer: nonempty
identity, booleanindependent. 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, booleanstructural,disposition, list of nonemptyevidencestrings. 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. A finding's evidence carries itsinvariant:line, worded as its action records it; the progress table counts a finding old when its signature or that line appeared in an earlier round.
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 3: 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). Every action's evidence, whatever its decision, has a nonempty entry beginning exactly invariant:. A fix action's evidence also has nonempty entries beginning exactly 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. An action's identity is its signature plus every invariant: text in its matching ledger finding (blanks collapsed, case folded); every invariant the action records must also appear there after normalization, so the ledger alone defines identity for recurrence and progress. It recurs when any of those keys, or keys connected transitively through findings of earlier rounds, appeared in any earlier round, whatever disposition that finding recorded; a finding with no action still contributes all its ledger identity keys, and findings sharing a key in one round are one occurrence. A recurring action chosen for another fix needs root_cause with nonempty claim, evidence, falsification, and rejected_alternative; once those connected keys appeared in two earlier rounds, it is a third occurrence and cannot be fixed again in the same run. The signature key catches a reworded invariant on a known finding, but only the invariant key links a new route, so reuse the same invariant wording for every route of one invariant. 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 (Claude plugins: python "$_AL" with the same arguments, _AL resolved as above) before the next review and at handoff. The checker still reads schema-1 and schema-2 archives under their own evidence and signature-only recurrence rules; current result validation requires version 3. 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.