Convergence Loop
Overview
A crash-safe orchestration engine for work too big for one pass: run rounds of small auditor+skeptic micro-batches over a rotating decomposition axis, persist every verified finding to an on-disk ledger immediately, and stop only when consecutive rounds produce zero new findings at the severity that matters. High signal beats high count; durable state on disk beats heroic context.
When to use / when NOT
Use when:
- Whole-repo or multi-surface audit/review/migration. The sibling skills release-audit, handoff-readiness, docs-deploy-readiness, security-surface-audit, and jargon-leak all run on this engine.
- The run will outlive one session or usage-limit window.
- A single pass already found real issues — more probably remain.
- Findings need an auditable, resumable trail a human can inspect mid-run.
A single pass is enough when:
- One file, one diff, or one PR — use one auditor + one skeptic, no loop.
- The property is objectively checkable (types, tests, lint, build) — run the checker instead.
- The user asked for a time-boxed quick look.
- The task is generative (build a feature) — this is an audit substrate, not a build planner.
Complements nuke-audit (a full audit→fix-spec methodology with its own built-in convergence loop — it does NOT run on this engine); convergence-loop is a standalone substrate for charters that lack one: the focused audits in this repo (release-audit, handoff-readiness, docs-deploy-readiness, security-surface-audit, jargon-leak) and any custom fan-out you compose.
How it works
Detect the project shape (discover before applying). Read package manifests, workspace layout, docs, and CI config to inventory the auditable surfaces. (Illustrative example only: a repo might contain a web app, a CLI, a component registry, and a docs site — never assume this.) Output: a scope inventory (path groups per surface) and the axis menu below pruned to what actually exists.
Set up durable state on disk. Create a findings ledger and a rounds log (formats in Output). These are the only shared memory. The orchestrator NEVER pulls source files into its own context — agents get paths, return short structured results, and durable detail lives in these files.
Write the charter. The exact question every auditor answers, a severity scale (critical/high/medium/low/info), and explicit scope. Pull audit content from focused skills by name — see code-audit, reusability-audit, anti-slop, security-review, accessibility-compliance, react-senior-guide, humanizer — do not re-explain them inside the charter.
Round loop with rotated axes. Every round covers the same scope decomposed on a DIFFERENT axis. A fixed axis structurally cannot see what another axis exposes:
| Axis |
Batch = |
Uniquely surfaces |
| by-directory |
one package/top-level dir |
local file-level issues |
| by-feature |
one user-facing feature slice |
cross-directory inconsistency inside a feature |
| by-layer |
UI / state / API / persistence |
contract drift between layers |
| by-data-flow |
one datum followed end to end |
validation gaps, double transforms, trust boundaries |
| by-user-journey |
one workflow start to finish |
dead ends, broken flows, copy/UX seams |
| by-failure-injection |
one area under "what if X fails/empty/slow/huge" |
missing error, empty, and race handling |
Micro-batch unit (the atomic step). One batch = one workflow: 2 auditor agents in parallel → cross-paired skeptic verifiers → immediate ledger merge. Everything before the current batch is already persisted, so a mid-batch death costs at most one batch. Anti-pattern: "all auditors, then all skeptics, then one final clerk" — a single limit hit loses everything. Each batch is a persisted script whose configuration (paths, charter, model ids, ledger path) is a LITERAL inside the script — never external args, which can arrive undefined and crash the run before any agent starts.
Skeptic pass. Every candidate finding is verified by an independent agent that did NOT author it, charged to REFUTE: (a) real at the cited lines — read them; (b) duplicate of any ledger F- or R- entry; (c) intentional per project contracts (AGENTS.md, docs, ADRs); (d) inside the charter's scope; (e) would the fix improve things or just churn? Uncertain on any point → reject, recorded as R- with the reason so future skeptics dedupe against rejections too.
Convergence check. A round is dry when it produces zero NEW accepted findings at-or-above the active severity threshold (e.g. medium+). Converged = 2 consecutive dry rounds. Also set a hard round cap (e.g. 6–8); if capped without convergence, report CAPPED — never claim done. Raising the threshold after early rounds (report only medium+ later) is a legitimate way to chase the signal that matters, but it leaves an uneven low/info layer across the codebase — record every threshold change in the rounds log and surface it in the final report.
Failure and resume. Relaunch a failed batch from its persisted script as a FRESH run. Do not cache-resume agents killed on a limit — it is unreliable.
Optional babysitter. If the environment offers scheduled triggers: a low-frequency tick (e.g. every 30–60 min) that, when the chain is idle AND the last batch failed with a usage/rate-limit signature, relaunches exactly that batch script. Safety cap: stop after N (e.g. 3) non-limit failures of the same batch. End condition: converged or capped → delete the trigger. Session-lived convenience, not required.
Model pinning. Pin every spawned agent to an explicit model id in the spawn call so the human can see in the workflow view which model ran each role. Never rely on implicit inheritance when the human cares which model audits versus verifies.
detect_shape() -> surfaces, axis_menu, scope
init LEDGER, ROUNDS_LOG; round=1; dry=0; threshold=medium
while dry < 2 and round <= CAP:
axis = rotate(axis_menu, ROUNDS_LOG)
for batch in partition(scope, axis):
script = write_batch_script(LITERAL config: paths, charter, models, ledger)
run script: [auditorA ∥ auditorB] -> candidates
[skeptic(B verifies A) ∥ skeptic(A's candidates never self-verified)]
merge: accepted -> F-ids, rejected -> R-ids; append ROUNDS_LOG
on failure: relaunch script FRESH (no cache-resume)
dry = (new F >= threshold this round == 0) ? dry+1 : 0
round += 1
report(CONVERGED | CAPPED, ledger stats, threshold history, gaps)
Quality bar
| Check |
Bar |
| Batch atomicity |
Ledger merge happens inside every batch, never deferred to a final clerk |
| Skeptic independence |
No agent verifies its own findings; skeptic charter says REFUTE; uncertain = reject |
| Ledger durability |
Stable F-/R- ids, append-only, on disk before the next batch starts |
| Orchestrator context |
Orchestrator routes paths and merges short results; it never reads source files itself |
| Axis rotation |
No two consecutive rounds on the same axis; axes chosen from detected shape |
| Convergence honesty |
2 consecutive dry rounds at stated threshold, or report CAPPED explicitly |
| Threshold transparency |
Every threshold raise logged per round and named in the final report |
| Resume-safety |
Config inlined as literals; failed batches relaunched fresh from their script |
| Model pinning |
Every spawned agent carries an explicit model id visible to the human |
Output
Ledger (<workdir>/LEDGER.md, append-only, one block per entry):
## F-0042 [high] src/checkout/total.ts:88
Round 3 · axis: by-data-flow · batch 3.2
Claim: discount applied after tax, contradicting pricing docs
Evidence: <1–3 quoted lines>
Fix: apply discount before tax; add regression test
Status: open
## R-0017 [rejected: intentional]
Candidate: "magic number 250 in debounce" — documented in perf notes
Rounds log (<workdir>/ROUNDS.md):
### Round 3 · axis: by-data-flow · threshold: medium+
- batch 3.1 [ok] +2 F (F-0040, F-0041) · 3 rejected
- batch 3.2 [relaunched x1: usage limit] +1 F (F-0042) · 1 rejected
New at threshold: 3 -> dry_streak = 0
Final report:
# Convergence report — <charter>
Status: CONVERGED (2 dry rounds at medium+) | CAPPED at round 8 — NOT converged
Rounds: 6 · axes: directory, feature, data-flow, failure-injection
Threshold history: R1–2 all severities · R3+ medium+ (low/info layer uneven)
Accepted: 1 critical · 4 high · 9 medium · 12 low · 7 info · Rejected: 23
Ledger: <path> · Rounds log: <path>
Top findings: F-0007 [critical] <one line> ...
Honest gaps: batch 4.3 failed 3x non-limit (scope X uncovered); low/info uneven after R2
Common mistakes
- Monolithic pipeline ("all auditors → all skeptics → one clerk"): one limit hit destroys hours. Merge per micro-batch.
- Self-verification: the author confirming its own finding is theater. Cross-pair skeptics.
- Orchestrator reading source: blows its context and dies mid-run. Paths out, short results in.
- Fixed axis every round: structural blind spots persist. Rotate the decomposition.
- Chasing finding count: skeptics exist to lower count and raise signal. Reward dryness.
- Cache-resuming a limit-killed agent: unreliable. Relaunch the batch script fresh.
- External args into batch scripts: undefined args crash before any agent starts. Inline literals.
- Implicit model inheritance: the human cannot see what ran. Pin explicit model ids.
- Silent threshold raises: the uneven low/info layer becomes invisible. Log and report it.
- Declaring convergence at the cap: capped ≠ converged. Report honestly.
- Re-explaining library skills in the charter: reference code-audit, anti-slop, security-review, etc. by name instead.
1---2name: convergence-loop3description: Use when orchestrating a large multi-agent audit, review, sweep, or migration that must converge rather than finish in one pass — whole-repo quality audits, release or handoff readiness checks, security surface audits, jargon sweeps, docs reviews, or any long subagent fan-out at risk of usage limits, crashes, or lost context mid-run. Triggers include "run to convergence", "multi-round audit", "resumable audit", "batch the auditors", "keep auditing until dry".4---56# Convergence Loop78## Overview910A crash-safe orchestration engine for work too big for one pass: run rounds of small auditor+skeptic micro-batches over a rotating decomposition axis, persist every verified finding to an on-disk ledger immediately, and stop only when consecutive rounds produce zero new findings at the severity that matters. High signal beats high count; durable state on disk beats heroic context.1112## When to use / when NOT1314**Use when:**15- Whole-repo or multi-surface audit/review/migration. The sibling skills release-audit, handoff-readiness, docs-deploy-readiness, security-surface-audit, and jargon-leak all run on this engine.16- The run will outlive one session or usage-limit window.17- A single pass already found real issues — more probably remain.18- Findings need an auditable, resumable trail a human can inspect mid-run.1920**A single pass is enough when:**21- One file, one diff, or one PR — use one auditor + one skeptic, no loop.22- The property is objectively checkable (types, tests, lint, build) — run the checker instead.23- The user asked for a time-boxed quick look.24- The task is generative (build a feature) — this is an audit substrate, not a build planner.2526Complements nuke-audit (a full audit→fix-spec methodology with its own built-in convergence loop — it does NOT run on this engine); convergence-loop is a standalone substrate for charters that lack one: the focused audits in this repo (release-audit, handoff-readiness, docs-deploy-readiness, security-surface-audit, jargon-leak) and any custom fan-out you compose.2728## How it works29301. **Detect the project shape (discover before applying).** Read package manifests, workspace layout, docs, and CI config to inventory the auditable surfaces. (Illustrative example only: a repo might contain a web app, a CLI, a component registry, and a docs site — never assume this.) Output: a scope inventory (path groups per surface) and the axis menu below pruned to what actually exists.312. **Set up durable state on disk.** Create a findings ledger and a rounds log (formats in Output). These are the only shared memory. The orchestrator NEVER pulls source files into its own context — agents get paths, return short structured results, and durable detail lives in these files.323. **Write the charter.** The exact question every auditor answers, a severity scale (critical/high/medium/low/info), and explicit scope. Pull audit content from focused skills by name — see code-audit, reusability-audit, anti-slop, security-review, accessibility-compliance, react-senior-guide, humanizer — do not re-explain them inside the charter.334. **Round loop with rotated axes.** Every round covers the same scope decomposed on a DIFFERENT axis. A fixed axis structurally cannot see what another axis exposes:3435 | Axis | Batch = | Uniquely surfaces |36 |---|---|---|37 | by-directory | one package/top-level dir | local file-level issues |38 | by-feature | one user-facing feature slice | cross-directory inconsistency inside a feature |39 | by-layer | UI / state / API / persistence | contract drift between layers |40 | by-data-flow | one datum followed end to end | validation gaps, double transforms, trust boundaries |41 | by-user-journey | one workflow start to finish | dead ends, broken flows, copy/UX seams |42 | by-failure-injection | one area under "what if X fails/empty/slow/huge" | missing error, empty, and race handling |43445. **Micro-batch unit (the atomic step).** One batch = one workflow: 2 auditor agents in parallel → cross-paired skeptic verifiers → immediate ledger merge. Everything before the current batch is already persisted, so a mid-batch death costs at most one batch. Anti-pattern: "all auditors, then all skeptics, then one final clerk" — a single limit hit loses everything. Each batch is a persisted script whose configuration (paths, charter, model ids, ledger path) is a LITERAL inside the script — never external args, which can arrive undefined and crash the run before any agent starts.456. **Skeptic pass.** Every candidate finding is verified by an independent agent that did NOT author it, charged to REFUTE: (a) real at the cited lines — read them; (b) duplicate of any ledger F- or R- entry; (c) intentional per project contracts (AGENTS.md, docs, ADRs); (d) inside the charter's scope; (e) would the fix improve things or just churn? Uncertain on any point → reject, recorded as R- with the reason so future skeptics dedupe against rejections too.467. **Convergence check.** A round is *dry* when it produces zero NEW accepted findings at-or-above the active severity threshold (e.g. medium+). Converged = 2 consecutive dry rounds. Also set a hard round cap (e.g. 6–8); if capped without convergence, report CAPPED — never claim done. Raising the threshold after early rounds (report only medium+ later) is a legitimate way to chase the signal that matters, but it leaves an uneven low/info layer across the codebase — record every threshold change in the rounds log and surface it in the final report.478. **Failure and resume.** Relaunch a failed batch from its persisted script as a FRESH run. Do not cache-resume agents killed on a limit — it is unreliable.489. **Optional babysitter.** If the environment offers scheduled triggers: a low-frequency tick (e.g. every 30–60 min) that, when the chain is idle AND the last batch failed with a usage/rate-limit signature, relaunches exactly that batch script. Safety cap: stop after N (e.g. 3) non-limit failures of the same batch. End condition: converged or capped → delete the trigger. Session-lived convenience, not required.4910. **Model pinning.** Pin every spawned agent to an explicit model id in the spawn call so the human can see in the workflow view which model ran each role. Never rely on implicit inheritance when the human cares which model audits versus verifies.5051```text52detect_shape() -> surfaces, axis_menu, scope53init LEDGER, ROUNDS_LOG; round=1; dry=0; threshold=medium54while dry < 2 and round <= CAP:55 axis = rotate(axis_menu, ROUNDS_LOG)56 for batch in partition(scope, axis):57 script = write_batch_script(LITERAL config: paths, charter, models, ledger)58 run script: [auditorA ∥ auditorB] -> candidates59 [skeptic(B verifies A) ∥ skeptic(A's candidates never self-verified)]60 merge: accepted -> F-ids, rejected -> R-ids; append ROUNDS_LOG61 on failure: relaunch script FRESH (no cache-resume)62 dry = (new F >= threshold this round == 0) ? dry+1 : 063 round += 164report(CONVERGED | CAPPED, ledger stats, threshold history, gaps)65```6667## Quality bar6869| Check | Bar |70|---|---|71| Batch atomicity | Ledger merge happens inside every batch, never deferred to a final clerk |72| Skeptic independence | No agent verifies its own findings; skeptic charter says REFUTE; uncertain = reject |73| Ledger durability | Stable F-/R- ids, append-only, on disk before the next batch starts |74| Orchestrator context | Orchestrator routes paths and merges short results; it never reads source files itself |75| Axis rotation | No two consecutive rounds on the same axis; axes chosen from detected shape |76| Convergence honesty | 2 consecutive dry rounds at stated threshold, or report CAPPED explicitly |77| Threshold transparency | Every threshold raise logged per round and named in the final report |78| Resume-safety | Config inlined as literals; failed batches relaunched fresh from their script |79| Model pinning | Every spawned agent carries an explicit model id visible to the human |8081## Output8283Ledger (`<workdir>/LEDGER.md`, append-only, one block per entry):8485```text86## F-0042 [high] src/checkout/total.ts:8887Round 3 · axis: by-data-flow · batch 3.288Claim: discount applied after tax, contradicting pricing docs89Evidence: <1–3 quoted lines>90Fix: apply discount before tax; add regression test91Status: open9293## R-0017 [rejected: intentional]94Candidate: "magic number 250 in debounce" — documented in perf notes95```9697Rounds log (`<workdir>/ROUNDS.md`):9899```text100### Round 3 · axis: by-data-flow · threshold: medium+101- batch 3.1 [ok] +2 F (F-0040, F-0041) · 3 rejected102- batch 3.2 [relaunched x1: usage limit] +1 F (F-0042) · 1 rejected103New at threshold: 3 -> dry_streak = 0104```105106Final report:107108```text109# Convergence report — <charter>110Status: CONVERGED (2 dry rounds at medium+) | CAPPED at round 8 — NOT converged111Rounds: 6 · axes: directory, feature, data-flow, failure-injection112Threshold history: R1–2 all severities · R3+ medium+ (low/info layer uneven)113Accepted: 1 critical · 4 high · 9 medium · 12 low · 7 info · Rejected: 23114Ledger: <path> · Rounds log: <path>115Top findings: F-0007 [critical] <one line> ...116Honest gaps: batch 4.3 failed 3x non-limit (scope X uncovered); low/info uneven after R2117```118119## Common mistakes120121- **Monolithic pipeline** ("all auditors → all skeptics → one clerk"): one limit hit destroys hours. Merge per micro-batch.122- **Self-verification**: the author confirming its own finding is theater. Cross-pair skeptics.123- **Orchestrator reading source**: blows its context and dies mid-run. Paths out, short results in.124- **Fixed axis every round**: structural blind spots persist. Rotate the decomposition.125- **Chasing finding count**: skeptics exist to lower count and raise signal. Reward dryness.126- **Cache-resuming a limit-killed agent**: unreliable. Relaunch the batch script fresh.127- **External args into batch scripts**: undefined args crash before any agent starts. Inline literals.128- **Implicit model inheritance**: the human cannot see what ran. Pin explicit model ids.129- **Silent threshold raises**: the uneven low/info layer becomes invisible. Log and report it.130- **Declaring convergence at the cap**: capped ≠ converged. Report honestly.131- **Re-explaining library skills in the charter**: reference code-audit, anti-slop, security-review, etc. by name instead.