| name | quality-gate |
| description | Iterative red-teaming of any artifact (design docs, plans, code, hypotheses, mockups). Loops until clean or stagnation. Invoked by artifact-producing skills or their parent orchestrator. |
| origin | crucible |
Quality Gate
All subagent dispatches use disk-mediated dispatch. See shared/dispatch-convention.md for the full protocol.
All subagent returns (red-team agents, judges, fix agents) use the Ledger Return Protocol. Every subagent returns exactly one Evidence Receipt per shared/return-convention.md; the orchestrator applies the two-tier receipt linter (see the "Receipt Linter (Ledger Return Protocol)" section below) to every Task return before acting on the declared VERDICT.
The gate maintains an Invariant Cairn per shared/cairn-convention.md. Each gate round is a cairn phase. See ## Cairn (Layer 3) below.
Shared iterative red-teaming mechanism invoked at the end of artifact-producing skills. Provides rigorous adversarial review as the core quality mechanism.
Announce at start: "Running quality gate on [artifact type]."
Skill type: Rigid -- follow exactly, no shortcuts.
Execution model: When this skill is running, YOU are the orchestrator. You drive the loop, dispatch fix agents and reviewers as subagents, track scores, and make escalation decisions. All references to "the orchestrator" in this document refer to you.
Receipt Linter (Ledger Return Protocol)
Apply Tier 1 (structural) and Tier 2 (witness verification) lint per shared/return-convention.md to every Task return before acting on the declared VERDICT. The canonical grammar (CLAIM citations, WITNESS rules, verb-binding, byte-range limits, lint-failure handling) lives in that document. Build, siege, and audit apply the same linter.
The linter is a deterministic runtime tool: orchestrators MUST run python3 scripts/rcpt_verify.py --tier2 --strict --root <dispatch-root> --ledger <dispatch-root>/receipt-ledger.jsonl <receipt> on every received receipt before acting on its VERDICT, and apply the shared convention's in-context pseudocode ONLY as the fallback when the tool is unavailable. --strict hard-FAILs only resolvable path-shaped artifacts on a sha256/witness mismatch; an unresolvable bare basename stays UNVERIFIABLE (never a false FAIL); always pass --root <dispatch-root> explicitly. The obligation to lint every return is unchanged — only the mechanism moves to the tool.
Quality-gate-specific obligations: Receipts from red-team, fix, judge, verifier (the fix-verification dispatch; the persistence-checker JSON output is consumed directly, not receipt-linted — see Persistence Check), and dependency-audit subagents are all linted before their VERDICT is consumed. A lint failure is treated as structurally BLOCKED regardless of declared VERDICT — see "Lint failure handling" in the shared convention.
Red-team receipts lint clean (#366). Red-team returns a structured RCPT v1.1 receipt (not prose), so its receipt passes Tier-1/Tier-2 normally — it does not lint-to-BLOCKED. The red-team findings themselves come from the cited artifact the receipt pins (round-N-findings.md, an [FINDINGS_OUTPUT_PATH] the orchestrator supplies — see the score step and writer-inversion below), which the orchestrator reads directly; the receipt's VERDICT is the witness-verified PASS/FAIL boundary plus the supersession/tripwire anchor, not the findings channel. This makes the :44/:56/:62 couplings operational (red-team genuinely emits a receipt).
Cairn (Layer 3)
Per shared/cairn-convention.md. Quality-gate-specific bindings:
- Phase mapping. One cairn phase per gate round:
round/1, round/2, …. A round begins at red-team dispatch, ends at judge verdict (either PASS/escalate or loop-again with score delta recorded).
- Phase transitions. At each round-exit, append a LEDGER line
round/N | dispatches=<red-team+judge+fix> receipts=<same> verdict=<PASS|FAIL|MIXED> | <score delta + key finding>. Advance PHASE to the next round on loop; advance to terminal/N on PASS or escalation.
- Terminal phase. When the gate returns PASS to its caller, OR when it escalates (stagnation / 15-round limit / architectural concern). Delete
active-run.md on terminal; keep cairn-<run-id>.md.
- Mandatory-invariant categories. Each round-exit MUST capture any finding that survived into the fix journal with severity ≥ Significant and a note on why — these are the load-bearing constraints for any later round's red-team. Also capture the score trajectory (
score-delta: -2) for stagnation-detection audit.
- Reconciliation. Full 5-rule pass. Rule 4 (invariant-receipt liveness) drives the orchestrator to retire invariants whose originating finding was fixed by the fix-agent (via Layer 2
SUPERSEDED_BY) — keeping the invariants list from ballooning across long gates.
Tripwire Manifest Sweep (Layer 2)
Starting with convention v1.1, every QG subagent (red-team, judge, fix-agent) returns a receipt carrying TRIPWIRE:, SUPERSEDES:, and (when applicable) TRIPWIRE-CHILD: lines. Full grammar in shared/return-convention.md.
Manifest: After each Task return (post-lint), append:
<rcpt-sha256-prefix-12> <skill>/<dispatch-id> <verdict> TRIPWIRE: <predicates> [SUPERSEDED_BY=<prefix>] [keys=quality-gate:<k>:<v>,…] [files=<path>:<h6>,…]
Namespace CLAIM-key discriminators as quality-gate:<key> (e.g. quality-gate:severity-max:minor) — prevents collision with build/siege keys.
Sweep (dispatch-loop clause): The orchestrator MAY NOT dispatch the next round until it has: (1) linted; (2) appended; (3) processed SUPERSEDES; (4) evaluated self-checks; (5) evaluated forward-checks against every active prior entry (TRIPWIRE ∪ TRIPWIRE-CHILD); (6) Read each firing M's full receipt and narrated the re-read; (7) then dispatch.
Fix-agent supersession. A QG fix-agent supersedes the prior FAIL red-team receipt. SUPERSEDES: <fail-prefix> + cited CLAIM + exec/grep witness with ran=TRACE#N. Tier-2 re-runs the witness against the fix — only survives if clean.
Fix-agent superseding-witness by artifact class (#366). Because the red-team FAIL receipt is now a real supersession anchor, the convention's witness-evidence requirement (a FAIL / SUSPICION ≥ 0.30 predecessor demands the superseding WITNESS be kind ∈ {exec, grep} + ran=TRACE#N) is live on the fix-agent's superseding receipt:
- Test-less artifacts (test-less design / plan / doc gates — the dominant QG case): the fix-agent's superseding receipt carries a
grep witness against the revised artifact proving the superseded red-team finding-anchor text no longer appears (kind=grep, ran=TRACE#N), plus the justification CLAIM citing the FAIL receipt's prefix (from=<fail-prefix>#…, per the SUPERSEDES Tier-1 justification requirement in return-convention.md). This is what makes the supersession survive Tier-2 — the original concern demonstrably no longer reproduces.
- Artifacts WITH tests: the existing
run-tests exec witness applies (the run-tests mandatory-work declaration above).
Clean-PASS TRIPWIRE predicate (#366, SP2). Per the convention's TRIPWIRE-none rule at return-convention.md (the Tripwire Manifest section, ~:427), TRIPWIRE: none is permitted only on a PASS receipt with SUSPICION=0.00; a FAIL red-team receipt carries TRIPWIRE: verdict=FAIL. This is a pointer to the canonical rule, not a redeclaration of the grammar (per CLAUDE.md "link, never copy").
Stagnation-judge tripwires. A stagnation judge's receipt declaring TRIPWIRE: peer-dispatch-disagrees(count) lets a later round's divergent issue-count fire a re-read, surfacing judge-vs-judge disagreement without a separate escalation channel.
Mandatory-work declarations for quality-gate subagent types:
- Red-team agent:
read-artifact, emit-findings.
- Judge agent:
read-findings, emit-scores.
- Fix agent:
read-findings, apply-edits, run-tests (if tests exist for the artifact's subtree).
On lint failure: treat as structurally BLOCKED regardless of declared VERDICT. Re-dispatch with lint errors appended to the brief, or escalate.
Consensus Detection
At the start of the quality gate, check whether the consensus_query MCP tool
is available in the current environment:
- If the tool is available: consensus-eligible rounds will use multi-model
dispatch (see Multi-Model Red-Team Review and Multi-Model Consensus in
Stagnation Detection below).
- If the tool is not available: all rounds use standard single-model dispatch.
No degradation, no warnings — the gate behaves exactly as it did before
consensus was introduced.
Do NOT:
- Prompt the user to set up consensus if it is unavailable
- Log warnings about missing consensus configuration
- Change any scoring, stagnation, or escalation logic based on consensus availability
Consensus is a near-transparent enhancement. Its presence improves coverage;
its absence preserves all standard exit paths. The one documented asymmetry:
consensus presence enables one additional pre-threshold escalation path — see
Pre-Threshold Consensus Carve-Out. This is the only place where consensus
availability changes the gate's exit set.
External Model Review (Optional)
At the start of the quality gate, check whether the external_review MCP tool
is available in the current environment AND skills.quality_gate is enabled in
the external review config. If either check fails, skip all external review
steps silently — no warnings, no prompts.
When It Runs
Every red-team round, alongside the host red-team dispatch. Call
external_review with:
prompt: contents of skills/shared/external-review-prompt.md
context: the same artifact context given to the red-team subagent
skill: "quality_gate" (top-level argument for per-skill toggle enforcement)
metadata: {"skill": "quality_gate", "round": N} (traceability)
Consensus Bridge (Round 1, then every max(1, suppression_threshold // 3) rounds, up to round 15)
On consensus-eligible rounds where both consensus_query and external_review
are available:
- Run
external_review FIRST, before calling consensus_query
- Only bridge reviews where
error is null. Skip errored reviews — their
empty content would corrupt the consensus signal.
- Pass the non-errored external review responses as the
additional_responses
parameter to consensus_query
- The aggregator deduplicates findings across all models (consensus + external),
surfaces cross-model disagreements, and tags external-unique findings with
confidence levels
- On non-consensus rounds, external review runs independently — its findings
are appended to round output but not routed through the aggregator
Scoring Invariant (INV-2)
CRITICAL: External findings do NOT affect the scoring algorithm.
- The weighted score (Fatal=3, Significant=1) is computed from host red-team
findings ONLY
- External findings are appended to round output for visibility
- External findings are added to the fix journal context (so the fix agent sees
them as additional perspective)
- External findings are NEVER inputs to the stagnation detection scoring
This invariant is load-bearing. The quality gate's convergence guarantees depend
on a single, consistent scoring source. Mixing external signal into scoring
would create non-deterministic stagnation behavior.
Graceful Degradation
external_review tool not available (MCP server not running): skip silently.
- Response
status is "unavailable" (no config or disabled): skip silently.
- Response
status is "error" (all models failed): skip silently, note
failure in round output. Distinct from "unavailable" — means the feature is
configured but every model errored.
- Response
status is "partial" (some models failed): include available
reviews, note which models failed in round output.
- External review timeout or failure never blocks or delays the host red-team
round.
Anti-Rationalization Table — quality-gate
| Rationalization | Rebuttal | Rule |
|---|
| "This finding is minor, I'll just fix it inline instead of dispatching a fix agent." | Orchestrator-applied fixes break separation of concerns and corrupt the fix journal. Fix-agent overhead for trivial fixes is negligible; the risk of conflation is not. | All fixes route through the fix agent — no exceptions, no matter how small. |
| "Round N fixed everything, I can return PASS without another red-team round." | Fixing is not passing. A fresh red-team round is the verification step. Skipping it is a skip disguised as a pass. | The gate is only PASS after a fresh red-team round returns 0 Fatal, 0 Significant. |
| "The red-team finding is wrong / overblown, I'll mark it resolved without a fix." | Rationalizing away findings defeats the point of adversarial review. If a finding is wrong, the fix agent explicitly justifies dismissal in the fix journal — the orchestrator does not dismiss findings unilaterally. | Every Fatal/Significant finding is either fixed or documented as dismissed by the fix agent with reasoning. |
| "The score went up but I can tell it's close, skip the stagnation judge." | Stagnation detection uses weighted score, not orchestrator intuition. Score-based inline judgment is the exact failure the judge exists to catch. | Dispatch the stagnation judge whenever score is not strictly lower than the prior round. |
| "Round 15 hit — I'll squeeze in one more round, surely the next will pass." | The 15-round limit is a circuit breaker, not a suggestion. Exceeding it silently is how runaway loops happen. | At round 15, escalate to the user with full round history — never silently continue. |
| "Score went up at round 4 — that's a regression, I should escalate now." | Pre-threshold suppression is deliberate: most artifacts converge within a few rounds and early escalation interrupts that. Record the regression in round-N-score.md and keep looping. Exception: if score increased at BOTH round 3→4 AND round 4→5, that's sustained regression — escalate. | Single-round regressions before suppression_threshold are suppressed; sustained regressions (2 consecutive strict increases) escalate at any round. |
| "Round 6 scored higher than round 5, and round 5 scored higher than round 4 — but suppression should still apply, right?" | No. The sustained-regression hard exit overrides suppression. Two strict score increases running is a structural signal that further looping will not help. | At round 3+, check score(N) > score(N-1) > score(N-2) every round; if true, escalate immediately regardless of suppression. |
| "We're at round 8 and stuck — the user would want to know." | The suppression rule exists precisely because intuition about "stuck" is often wrong before the threshold. Trust the rule; the loop continues unless one of the structural exits (sustained-regression, no-op-fix, architectural-block) fires. | Pre-threshold escalations require a structural exit or explicit user interrupt — orchestrator judgment does not qualify. |
| "This is a hypothesis artifact and we're at round 4 — but pre-threshold suppression should apply." | No. Hypothesis artifacts default to suppression_threshold: 3. At round 4 the threshold has already been crossed; normal escalation applies. Always read the threshold from skill arguments, not from memory of the default for code. | Always read suppression_threshold from the current invocation's arguments, never assume 10. |
| "This is a small auth fix, siege is overkill — skip it." | Detection exists because intuition about "small" security changes is unreliable. The cost of one siege dispatch is ~6 Opus agents; the cost of missing a regression in a security PR is unbounded. | Never skip siege when detection fires unless skip_siege: true is explicitly set with documented reason. |
| "Detection fired on keywords in a design doc, but the doc isn't really about security." | The confidence threshold (≥2 categories OR keyword + dedicated ## Security section) already filters single-word noise. If detection still fires, the doc has real security content. | Trust the confidence threshold. To override, set skip_siege: true with reason in the gate-verdict marker. |
| "The user said 'move on', that's approval to skip the gate." | General feedback is never skip approval. Skip requires an unambiguous instruction specifically referencing the gate. | Only an explicit, gate-referencing instruction counts as skip approval. |
| "Skip look-harder on this clean round — it converged fast and the artifact is obviously fine." | Speed of convergence is not signal of correctness; the entire premise of look-harder is that a single-pass clean round can hide Significants under standard rubric. The skip conditions are MECHANICAL (circuit-breaker at round 15, tail-rubric-already-applied when the round ran with the addendum already in-band), not judgment-based. | Look-harder fires on every first-clean round of a chunk; only the documented skip conditions short-circuit it. No "this one feels safe" exceptions. |
| "Skip the persistence checker — round N's fix verifier already confirmed which findings were unresolved, so running the checker on round N+1 is redundant." | The verifier checks whether this specific fix attempt resolved the targeted findings (single-channel, fix-side). The persistence checker checks whether round-(N+1)'s red-team independently raises the same complaint (single-channel, review-side). Two single channels do not equal two-channel corroboration. | Run the persistence checker whenever the trigger fires (round N+1 non-clean AND ≥1 round-N Unresolved verdict). The verifier and persistence checker are independent corroboration channels by design. |
| "Tail-rubric is too aggressive on round 6 of this threshold-10 gate — the standard rubric is fine, let the round stand without the addendum." | The tail-rubric trigger is mechanical (suppression_threshold ≥ 5 AND LOCAL round ≥ ceil(suppression_threshold * 0.6)), NOT a judgment call. Late-round inflation is the failure mode the addendum exists to suppress; declining to apply it at the trigger round is exactly when it's most needed. | Apply tail_rubric: true mechanically. The addendum's anti-undergrade clause preserves real Fatals; the demotion is targeted at speculative Significants only. |
Skill Arguments
| Argument | Type | Default | Effect |
|---|
suppression_threshold | int | (artifact-type lookup, see below) | The round number at which suppressed escalations (single-round stagnation, single-round regression, diminishing returns) become live. Below this round, only sustained-regression, no-op-fix, architectural-block, and user-interrupt can exit pre-clean. Above it, all escalation logic applies. |
interactive | bool | true if invoked from a standalone session, false if invoked by a parent orchestrator (build, debugging, spec). Detection: The orchestrator infers interactive from the presence of the Context from invoking orchestrator block in its dispatch context: BOTH Phase AND PipelineID present → treated as sub-skill invocation (interactive: false). Either field alone or both absent → standalone (interactive: true). Sub-skill parents (debugging, spec) MUST follow the build pattern; any parent that fails to pass Phase+PipelineID will cause QG to default to interactive: true and emit between-rounds check-ins, which will hang non-interactive pipelines. Parent skills can also explicitly pass interactive: false to override detection if they do not have a natural Phase/PipelineID to provide. Explicit interactive: argument overrides detection in either direction. | When true, the orchestrator emits a between-rounds check-in at round ceil(suppression_threshold/2) offering the user options: continue, escalate-now, or skip. Non-interactive contexts skip this prompt. |
force_siege | bool | false | When true, always dispatch crucible:siege in parallel with the first red-team round regardless of security-surface detection. Use for: explicit security PRs, scheduled security audits, post-incident review. |
skip_siege | bool | false | When true, never dispatch siege even if security-surface detection fires. Use for: artifacts the user already siege-tested separately, or repeated re-runs after siege already passed. Mutually exclusive with force_siege — passing both is an error. |
cost_cap_threshold | int | null | 3 if suppression_threshold > 3 else null (auto-null on hypothesis/mockup/translation) | LOCAL round at which the cost-cap prompt fires (interactive only). Set to null to disable. Auto-null when suppression_threshold ≤ 3 to avoid collision with the existing DIMINISHING_RETURNS judge verdict at the same round. See ## Cost-Cap and Diminishing-Return Signals for behavior. (Added by #303.) |
dr_signal_findings | int | null | 2 if suppression_threshold > 3 else null | Count of NEW (delta-vs-prior-round) Fatal+Significant findings at or below which the DR signal fires (interactive only). Same auto-null rule as cost_cap_threshold. (Added by #303.) |
Artifact-type-aware default for suppression_threshold:
| Artifact Type | Default Threshold | Rationale |
|---|
code | 10 | Build-pipeline economics; large diffs benefit from sustained iteration |
design | 10 | Iterative refinement on complex documents |
plan | 10 | Same as design |
hypothesis | 3 | 1-2 sentence artifacts; 10 rounds is wildly disproportionate |
mockup | 3 | Visual artifact; convergence is fast |
translation | 3 | Mock-to-Unity translation; fast iteration loop |
The threshold can be overridden per invocation. Build typically uses the defaults; debugging's Phase 3.5 hypothesis review uses the hypothesis default (3); a user running /design directly inherits the design default (10) unless they pass --suppression-threshold N.
No new public arguments for #265 mechanisms. Look-harder verification, tail-rubric, and the persistence checker are mechanically derived from existing suppression_threshold + artifact type + LOCAL round number. There is no --look-harder / --tail-rubric / --persistence-check argument — callers do not opt in or out. The mechanisms fire on their structural triggers; the only externally observable change is new fields in the verdict marker and convergence-log.
Interactive check-in (when interactive: true): After round ceil(suppression_threshold/2) (e.g., round 5 for threshold=10, round 2 for threshold=3) completes without clean pass, emit:
"Quality gate round N (suppression active until round T). Score progression: [list]. Continue, escalate now, or skip gate?"
The user's response routes to: continue (loop with suppression intact), escalate now (treat the next round's stagnation/regression signal as live regardless of suppression), or skip (terminate with Verdict: ESCALATED, reason "user-skipped"). One check-in per gate run; not repeated.
How It Works
-
Receives: artifact content, artifact type, project context
1.5. Calibration advisory (print-only, at gate entry). Resolve scripts/brier_advisory.py by absolute path from the plugin root (same resolution as the ledger emit below) and run python3 <script> advisory quality-gate. If it prints a line, surface that line to the user verbatim before the first red-team round; if it prints nothing, say nothing. The script reads the central store (~/.claude/crucible/ledger/brier-rolling.json + falsification.jsonl, override CRUCIBLE_LEDGER_DIR) and is silent unless this skill has ≥5 falsifiable verdicts with a Brier > 0.25 over trustworthy (≤30-day-old) reconciliation data. It honors CRUCIBLE_CALIBRATION_DISABLED=1 as a graceful skip. If the script can't be resolved, skip silently — a missing advisory must never block the gate. No behavior change; this is an advisory print only.
-
Pre-flight dependency audit (delegated). As of 2026-05-16, dependency-vulnerability scanning is crucible:dependency-audit, invoked by the parent orchestrator in parallel with quality-gate. Quality-gate itself no longer runs this step. If invoked standalone on a code artifact and the user expects dependency scanning, point them to /dependency-audit.
2.5. Security surface detection and siege dispatch. Run the detection heuristic (see Security Surface Detection and Siege Dispatch). If security_surface: detected AND skip_siege: false, OR if force_siege: true, dispatch crucible:siege in parallel with the first red-team round. The two skills proceed independently. The orchestrator awaits both before terminal verdict.
-
Prepares the artifact for review (see Artifact Preparation below)
-
Invokes crucible:red-team as a single-pass reviewer (one dispatch = one review round) via subagent_type: crucible-red-team, which pins this single-model red-team dispatch to Opus regardless of the orchestrator's own model (agents/crucible-red-team.md; the prose model line in red-team/SKILL.md is now descriptive only). On consensus-eligible rounds (Multi-Model Red-Team Review), the single-model dispatch is replaced by consensus_query(mode: "review"), whose model membership is resolved by the operator's consensus_query configuration — NOT by this pin. Quality-gate owns the iteration loop; red-team produces findings for one round and returns. Red-team does NOT run its own stagnation loop when invoked by quality-gate. If the harness reports that subagent_type: crucible-red-team fails to resolve (the agent defs are not installed on this machine — see shared/harness-adapter.md §8), the dispatch falls back to general-purpose on the orchestrator's inherited model and the Opus recall guarantee is not enforced; in that case the orchestrator emits a one-time visible warning ("agent type crucible-red-team not installed; the red-team is running on the inherited/session model — recall guarantee NOT enforced; install per harness-adapter §8") rather than degrading silently. The trigger is the observable type-resolution failure, not a transcript or metadata read.
-
If red-team finds zero Fatal and zero Significant issues: artifact is a candidate PASS — the round is candidate-clean, but the terminal verdict is deferred until look-harder verification completes. Order of operations on a candidate-clean round (0 Fatal, 0 Significant) — see ## Security Surface Detection and Siege Dispatch > Decision and Dispatch > Awaiting siege for cross-reference:
-
Await siege completion (if dispatched and still running) — see ## Security Surface Detection and Siege Dispatch > Decision and Dispatch > Awaiting siege.
-
If SiegeVerdict != PASS: skip look-harder + Minor Issue Handling, write verdict marker with Verdict: ESCALATED, Reason: siege-blocked, surface siege findings, and exit. Look-harder is SKIPPED entirely — the round was never going to be PASS.
-
Look-harder precedence gate. Before invoking look-harder, evaluate Exit Precedence slots #3 (sustained-regression) and #4 (no-op-fix) per existing logic. (This is a distinct mechanism from Exit Precedence's "Pre-precedence resolution" step at ## Escalation > Exit Precedence — that step is specifically the no-op → architectural re-dispatch path, run before precedence evaluation on rounds with active architectural candidates. The "Look-harder precedence gate" here is a much narrower check: it inspects whether a non-Clean-Pass slot would have won precedence on this candidate-clean round, and short-circuits look-harder if so.) A 0F/0S round cannot logically co-fire with sustained-regression (the weighted score on 0F/0S is 0, so score(N) > score(N-1) > score(N-2) cannot hold), but no-op-fix CAN co-fire (the fix agent may have returned a byte-identical artifact that the red-team also finds clean). If either slot fires, look-harder is SKIPPED ENTIRELY — the round was never going to be PASS. Look-harder is reached only when Clean Pass (slot #1) would otherwise win precedence.
-
Look-harder verification (Component 1 / #265). Re-dispatch of red-team (subagent_type: crucible-red-team, the same enforced Opus pin as every red-team round — "same model" here means the pinned red-team model, NOT the orchestrator's) with the shared tightened-rubric addendum (skills/quality-gate/tightened-rubric-addendum.md) concatenated to red-team-prompt.md body by the orchestrator. The re-dispatch is the same model, same artifact, fresh dispatch — only the rubric is tightened; no prior-round context leaks (anti-anchoring preserved). Look-harder is SKIPPED on the following conditions; the orchestrator records LookHarderSkippedReason in the verdict marker and proceeds to sub-step 5:
circuit-breaker — global round 15 (runaway protection takes precedence; this matches the pre-precedence architectural re-dispatch behavior of the 15-round limit).
tail-rubric-already-applied — the candidate-clean round's red-team dispatch already carried tail_rubric: true (i.e., LOCAL round ≥ ceil(suppression_threshold * 0.6) on a suppression_threshold ≥ 5 gate). The same-model re-dispatch with the identical addendum adds no signal beyond sampling variance.
- Already fired this chunk —
look-harder-fired-on-round is non-null in any prior round-N-flags.md of the in-progress chunk per the all-files recovery scan (see Compaction Recovery). Skip silently; no LookHarderSkippedReason recorded (the field is for circuit-breaker / tail-rubric-already-applied only).
- Co-fire precedence: when BOTH
circuit-breaker AND tail-rubric-already-applied apply simultaneously, circuit-breaker wins and is the recorded reason. (Note: co-fire is reachable on threshold-5 / threshold-6 gates where LOCAL round 15 is also a tail-rubric round; the precedence rule disambiguates regardless of frequency.)
If look-harder is NOT skipped, dispatch as a fresh Task (same disk-mediated dispatch convention as every red-team round). Output is persisted to round-N-look-harder.md. Phase 2 of the write-ordering protocol (see below) updates round-N-flags.md with look-harder-fired-on-round: <LOCAL N> AFTER look-harder resolves.
-
If look-harder returns 0F/0S: confirms the candidate-clean round. Execute Phase 2 of the write-ordering protocol (re-open round-N-flags.md and set look-harder-fired-on-round: <LOCAL N>, full-file replacement). Then proceed to sub-step 5 (Minor Issue Handling). LookHarderFiredCount is incremented; LookHarderRounds is NOT appended (only non-clean fires are listed).
-
If look-harder returns Fatal/Significant: the candidate-clean round is DEMOTED. The orchestrator MUST execute the following three writes in strict order, with no other dispatches interleaved (this ordering is load-bearing for recovery — see the Demotion crash-window rule below):
- Persist
round-N-look-harder.md with the demoting findings.
- Overwrite
round-N-findings.md with the look-harder findings (INV-A17) — the original 0F/0S findings file is replaced.
- Execute Phase 2 of the write-ordering protocol: re-open
round-N-flags.md and set look-harder-fired-on-round: <LOCAL N> (full-file replacement).
After step 3, the round becomes a normal non-clean round and proceeds to the fix loop (step 6 below). Do NOT re-dispatch siege — siege's prior verdict carries forward; the existing siege-await ordering applies on the next candidate-clean round without a fresh siege dispatch. The terminal sentinel round-N-complete.md is NOT written (round became non-terminal). LookHarderFiredCount is incremented and the round's LOCAL number is appended to LookHarderRounds. round-N-look-harder.md is retained as a separate telemetry artifact.
Stale-pin inertness (#366) — no supersession needed. The candidate-clean PASS red-team receipt pinned the pre-overwrite round-N-findings.md sha256 in its ARTIFACTS; the INV-A17 overwrite makes that pinned hash stale. This stale pin is inert and needs no SUPERSEDES:: (a) the candidate-clean PASS receipt's Tier-2 ran once at insertion against the then-current file (pre-overwrite) and passed, and (b) no manifest-sweep step re-hashes a prior entry's pinned ARTIFACTS against disk after insertion (see the SP3 negative invariant in the invariant table) — so nothing ever reads the stale pin again. The demotion is recorded by the existing INV-A17 mechanism (the overwrite + the Phase-2 flags write), not by supersession: a demotion ("the prior clean verdict was wrong", whose witness MUST fire) is the opposite of supersession's semantics ("the prior concern no longer reproduces", whose witness must NOT fire), so SUPERSEDES: is the wrong primitive for a demotion. The look-harder FAIL receipt is therefore a normal fresh manifest entry, not a superseding one.
Look-harder does NOT increment the gate's round counter (INV-A2). It is a verification step within slot #1; if it confirms, slot #1 stands; if it demotes, slot #1 is invalidated.
-
Proceed to Minor Issue Handling (quick-fix pass on consolidated minors). Minor Issue Handling does not re-trigger siege — it operates on a known-passed artifact.
-
After Minor Issue Handling: write final artifact to scratch directory, write verdict marker with Verdict: PASS, Reason: clean-pass, output consolidated Minor observations from all rounds (see Minor Issue Handling), surface pre-flight audit results (if any) alongside gate results, clean up, and return.
Write-ordering protocol (two-phase) for round-N-flags.md on candidate-clean rounds where look-harder fires:
- Phase 1 (end of red-team round, before look-harder dispatch). Existing semantics. Write
architectural-candidates: [...] per the Compaction Recovery section. Add the new key look-harder-fired-on-round: null to the same file in the same write.
- Phase 2 (after look-harder dispatch resolves, immediately after
round-N-look-harder.md is persisted). Re-open round-N-flags.md via the Write tool, set look-harder-fired-on-round: <LOCAL N> (the LOCAL round number within the current chunk), and write back as a full-file replacement (same pattern as the convergence-log update — not append).
On non-candidate-clean rounds (the round had Fatal/Significant), look-harder is not dispatched and Phase 2 is skipped; the key remains null. On skipped look-harder (circuit-breaker / tail-rubric-already-applied), Phase 2 is also skipped and the key remains null. Recovery interprets null as "look-harder not yet fired in this chunk, eligible to fire" and a populated value as "look-harder already fired in this chunk, skip per INV-T8."
Crash-window analysis. If the orchestrator crashes between Phase 1 and Phase 2 (look-harder dispatch in flight), recovery scans all round-N-flags.md files in the in-progress chunk's directory and sees null for the in-flight round. Recovery re-dispatches look-harder. Look-harder is protocol-safe to re-run (same model, same artifact, fresh framing) because no fix dispatch has yet consumed the first dispatch's findings — the second dispatch's findings are authoritative. round-N-look-harder.md is overwritten by the re-dispatch; this is the documented exception to the "no overwrites" convention for round-N artifacts.
Demotion crash-window rule. The strict write order (1: persist look-harder findings, 2: overwrite findings, 3: Phase 2 flags) collapses the demotion crash window onto Phase 2: if Phase 2 ran to completion, the findings overwrite is guaranteed to have happened before it. Recovery therefore treats look-harder-fired-on-round: <N> (Phase 2 populated) as authoritative evidence that the demotion's findings-overwrite (INV-A17) also completed; no separate consistency check is required. If recovery instead observes round-N-look-harder.md present AND Phase 2 still null, the crash landed between step 1 and step 3, and recovery step 6b's "re-dispatch look-harder" path applies — which transparently re-runs steps 1-3 in order. Out-of-order writes are forbidden: an orchestrator MUST NOT, for example, write Phase 2 before overwriting findings, or skip step 2 entirely. The recovery semantics depend on step ordering being honored.
-
If red-team finds Fatal or Significant issues:
a. Dispatch a separate fix agent (see Fix Mechanism below) — receive revised artifact, append to fix journal
b. Dispatch Fix Verifier (see Fix Verification below) — one Sonnet check per fix round
c. Append verifier output to fix journal under ### Verifier Assessment heading; write verdict summary to round-N-verification.md
d. If Fatal-severity Unresolved: flag as "prior unresolved Fatal — must address" in next round's fix dispatch (binding, one-round grace)
e. If Significant-severity Unresolved: appended to fix journal as informational context
f. Invoke a FRESH red-team on the revised artifact (no anchoring)
-
Track weighted score between rounds (Fatal=3, Significant=1):
Score source (#366). The weighted score is computed by the orchestrator counting the cited findings file's ### Fatal Challenges / ### Significant Challenges sections (the entries under each heading) — not from the receipt's CLAIMS. The receipt's SEVERITY-COUNTS: line and CLAIMS *-count= values are reviewer-declared cross-checks: on disagreement with the orchestrator's own section count, the orchestrator trusts its own count for scoring and flags the discrepancy in the narration log. This keeps the score un-spoofable — a fabricated declared count cannot move it. The receipt's role is narrower than the score: trust-check VERDICT boundary, supersession anchor (:56), tripwire participation (:44), and hash-pinned findings artifact.
Findings path & writer-inversion (#366). The [FINDINGS_OUTPUT_PATH] is orchestrator-supplied: the orchestrator supplies it = the round's round-N-findings.md path when it composes the red-team dispatch (it already owns that path). The red-team reviewer is now the initial writer of round-N-findings.md (it WROTEs the file; the receipt's TRACE carries that write) — QG no longer transcribes the reviewer's prose return into that file; it only reads the cited artifact (for fix-agent context, the stagnation judge, and look-harder/persistence diffs). The prepended SEVERITY-COUNTS: first line is benign for the persistence-checker (it matches findings by title/root-cause, not line number) and tolerated by the look-harder format.
- Strictly lower score → progress, loop again
- Same or higher score → dispatch the Stagnation Judge (see Stagnation Detection below)
-
Read the judge's verdict and act on it (see Stagnation Detection below). See ## Stagnation Detection > Persistence Check for the orchestrator step that runs BEFORE judge dispatch (Component 4 / #265), and ## Stagnation Detection > Verdict-Level Promotion for the post-judge promotion step that may convert a PROGRESS verdict to STAGNATION, Reason: persistent-finding-corroborated.
-
Progress notification. After round ceil(suppression_threshold / 2) and every max(1, suppression_threshold // 3) rounds thereafter (rounds 5, 8, 11, 14 for threshold 10; rounds 2, 3, 4, ... for threshold 3), emit: "Quality gate round [N]: score progression [list]." If the judge was dispatched, append recurring/new counts. Informational only — no pause. (Start round uses ceil — rounds up — so the first notification lands no earlier than the midpoint; cadence uses max(1, // 3) — floors with a 1-minimum — so worked examples match: threshold 10 yields cadence 3, threshold 3 yields cadence 1.)
-
Pre-threshold escalation suppression. Before round suppression_threshold (default 10 for code/design/plan; 3 for hypothesis/mockup/translation — see Skill Arguments), the gate does NOT escalate to the user for stagnation, diminishing returns, or single-round regression. These signals are suppressed in favor of continued iteration — most artifacts converge to 0 Fatal / 0 Significant within a few rounds, and early escalation interrupts the user before that convergence has a chance to happen. The stagnation judge is NOT dispatched on rounds 1 through suppression_threshold - 4 (i.e., rounds 1-6 for threshold 10). On rounds max(1, suppression_threshold - 3) through suppression_threshold - 1 (rounds 7-9 for threshold 10), and only when suppression_threshold ≥ 6, the judge runs in silent mode to seed comparison history (see Stagnation Detection > Judge Dispatch). For thresholds < 6 (hypothesis, mockup, translation defaults), there are no silent-seed rounds — the judge dispatches only at round ≥ suppression_threshold in normal mode. Regression detection is recorded in the round notes but does not escalate on a single round.
Sustained-regression hard exit (convergence guarantee). Pre-threshold suppression does NOT extend to a regression that persists across two consecutive rounds. If score(N) > score(N-1) AND score(N-1) > score(N-2) (i.e., weighted score has strictly increased two rounds running), the gate escalates immediately regardless of round number. Report: "Sustained regression detected: scores [N-2: X, N-1: Y, N: Z] strictly increasing. Fix cycle is actively worsening the artifact. Escalating." This rule guarantees loop termination even under suppression — without it, an oscillating fix agent (score 4 ↔ 5 ↔ 4) could burn rounds 1-9 with zero progress. Two consecutive strict increases is a structural signal that no further looping will help; one increase remains suppressed because single-round noise is expected during convergence.
The only pre-threshold exits are: clean pass (0 Fatal, 0 Significant); architectural concerns declared via the fix agent's VERDICT: ARCHITECTURAL_BLOCK receipt (see Architectural Concerns Exit); sustained-regression hard exit (defined above); no-op fix detection (see Fix Mechanism > No-Op Fix Detection); consensus-stagnation pre-threshold escalation (ONLY when consensus_query is available; see Pre-Threshold Consensus Carve-Out); or explicit user interrupt (including the interactive check-in's "escalate now" response, see Skill Arguments). Beginning at round suppression_threshold, normal escalation logic applies (stagnation judge, single-round regression escalation, diminishing returns). When two or more exits would fire on the same round, apply the precedence rules in Escalation > Exit Precedence (first match wins).
-
Global safety limit: 15 rounds. This is a runaway protection circuit-breaker. If you hit 15, escalate to user with full round history. This limit applies regardless of the suppression_threshold rule.
Tail-Rubric Flag (Component 2 / #265)
Trigger. The orchestrator computes tail_rubric: true for a red-team dispatch IFF:
suppression_threshold ≥ 5, AND
- the current LOCAL round number ≥
ceil(suppression_threshold * 0.6).
For default thresholds in the enabled range:
suppression_threshold = 10 (code/design/plan) → trigger at LOCAL round 6.
suppression_threshold = 6 → trigger at LOCAL round 4.
suppression_threshold = 5 (cross-chunk integration round) → trigger at LOCAL round 3 (ceil(5*0.6) = 3).
suppression_threshold ∈ {3, 4} → tail-rubric DISABLED. The flag is never set on these gates regardless of round number (INV-T3).
Counter selection. The tail-rubric uses the LOCAL (per-chunk) round number, consistent with suppression_threshold, consensus cadence, look-harder, and silent-seed. Late chunks do NOT automatically have tail-rubric active from round 1 — each chunk's local counter governs.
Action. When tail_rubric: true, the quality-gate orchestrator concatenates skills/quality-gate/tightened-rubric-addendum.md to the red-team-prompt.md body BEFORE Task dispatch. This is the SAME addendum file used by look-harder (Component 1); one source of truth, two trigger paths. red-team-prompt.md itself is not modified — the orchestrator is the sole appender (INV-A4). Standalone red-team invocation (i.e., red-team called outside of quality-gate) does NOT use the addendum.
The dispatch file written by the orchestrator records tail_rubric: true so the candidate-clean-round look-harder skip condition (tail-rubric-already-applied) can read it from disk after the round completes.
Cross-chunk integration coverage. The cross-chunk integration round runs with suppression_threshold = 5 (see Chunked Gate Counter Semantics). The tail-rubric trigger at threshold 5 (LOCAL round 3+) ensures the integration surface — which carries cumulative residual risk from all chunks — benefits from late-round rubric tightening.
Interaction with the existing inflation check. The red-team-prompt.md body already has an inflation check on its severity rubric. The tail-rubric addendum tightens that check round-conditionally; it does not replace it. Early rounds use the existing rubric unchanged; tail rounds layer the shared addendum on top.
Multi-Model Red-Team Review (when available)
Applies to: Round 1, and every max(1, suppression_threshold // 3) rounds thereafter, up to round 15. For the default suppression_threshold of 10, this yields cadence 3 → rounds 1, 4, 7, 10, 13. For suppression_threshold of 3 (hypothesis/mockup/translation), this yields cadence 1 → rounds 1, 2, 3 (effectively every pre-threshold round — short-threshold artifacts have less room to converge, so multi-model coverage on every round is justified). The max(1, ...) floor handles thresholds 1-2 (rare) by collapsing to cadence 1.
Intermediate rounds: Standard single-model red-team dispatch (no change).
Tail-rubric and consensus rounds: When tail_rubric: true AND the round is consensus-eligible, the orchestrator concatenates the tightened-rubric addendum to the prompt body passed to consensus_query(mode: "review") the same way it does for single-model dispatch. Consensus participants see the tightened rubric uniformly; per-model variance applies to the tightening, not its presence.
On consensus-eligible rounds:
- Instead of dispatching a single red-team subagent, call
consensus_query(mode: "review") with the red-team prompt and artifact content
- The consensus response provides merged findings with per-finding severity (Fatal/Significant/Minor), confidence (High/Medium/Low based on model agreement), provenance (which models raised it), and unique findings flagged as "potentially novel"
- The orchestrator processes these findings exactly as single-model findings: compute weighted score, compare to prior round, dispatch fix agent if needed
- Findings from consensus rounds include provenance metadata in
round-N-findings.md
Cost control: The consensus dispatch replaces (not supplements) the single-model dispatch on eligible rounds.
Fallback: If consensus is unavailable on an eligible round, dispatch standard single-model red-team review.
At-threshold consensus (when consensus round == suppression_threshold): Consensus dispatches normally and produces findings; the orchestrator computes the weighted score from those findings; the standard Multi-Model Consensus path in Stagnation Detection (single-judge dispatch replaced by consensus_query(mode: 'verdict')) consumes those findings for the stagnation judgment. The Pre-Threshold Consensus Carve-Out does NOT apply at-or-above threshold — agreement_level becomes informational metadata only at that point.
Pre-Threshold Consensus Carve-Out
Consensus-eligible rounds inside the suppression window (i.e., consensus-eligible rounds < suppression_threshold) — for the default threshold of 10, these are rounds 4 and 7 — fall inside the suppression window. The red-team consensus dispatch still runs on these rounds and produces findings — but normally the stagnation signal it implies (e.g., score didn't improve) is suppressed.
Carve-out: When a consensus-mode red-team dispatch on a pre-threshold round returns findings whose Fatal+Significant count is identical to the prior round's AND the weighted score did not strictly decrease AND the consensus aggregator reports agreement_level >= 0.75 (75% of responding models converged on the same finding set), the orchestrator escalates immediately with verdict ESCALATED, reason "consensus-stagnation-pre-threshold". Report:
"Multi-model consensus at round N shows persistent findings with high model agreement (75%+). Suppression overridden — unanimity is stronger signal than the threshold heuristic. Escalating."
This preserves the value of the pre-threshold consensus investment without giving every consensus call escape-hatch power. Without the carve-out, those rounds pay full consensus cost for signal the loop is contractually deaf to.
Fallback: If agreement_level is unavailable in the consensus response, treat as < 0.75 (do not escalate).
Round-1 exclusion. The carve-out requires at least one prior round of findings for the "identical to the prior round's" comparison. It does NOT fire on Round 1, regardless of suppression_threshold. For suppression_threshold ≤ 3, this means the earliest carve-out is Round 2 (consensus rounds 1, 2, 3; pre-threshold rounds 1 and 2; round 1 excluded by this rule); for suppression_threshold = 10, the earliest carve-out is Round 4 (consensus rounds 1, 4, 7, 10; round 1 excluded). In all cases Round 1 is structurally ineligible because no prior-round comparison exists.
Cost-Cap and Diminishing-Return Signals (#303)
Two advisory signals layered over existing escalation paths. Neither introduces a new termination path; both are advisory-only in v0.1 — they surface cost/diminishing-return information to the user but never change the gate's verdict or loop behavior.
Per-Round Ledger
After each red-team round returns findings, the orchestrator writes round-N-ledger.md to the gate scratch directory. v0.1 enumerates every Fatal/Significant finding under ## Accepted. The ## Deferred section is present but always empty in v0.1 (no triage; deferral activates in v1.0 once corpus matures — see issue #305).
Emission is unconditional and independent of cost_cap_threshold / dr_signal_findings. Threshold-3 artifacts get the ledger but no prompts.
Ledger format:
# Round N Ledger
Artifact-type: <code | hypothesis | mockup | translation>
Total findings: N (F: x, S: y, M: z)
New since round N-1: K (on round 1, K = total findings — no prior round)
Accepted: P (all findings — v0.1)
Deferred: 0 (v0.1 — see issue #305 for v1.0)
DR signal: <fired | not fired>
Cost-cap signal: <fired | not fired>
## Accepted
- [Fatal] <finding-id>: <one-line summary>
- [Significant] <finding-id>: <one-line summary>
## Deferred (v1.0 — empty in v0.1)
(none)
Diminishing-Return Signal
Fires when dr_signal_findings != null AND the count of NEW (delta-vs-prior-round) Fatal+Significant findings is ≤ dr_signal_findings AND LOCAL round ≥ 2. (null disables the signal entirely per INV-303-4.)
Interactive (interactive: true): emit prompt:
"Quality gate round N surfaced only K NEW Fatal+Significant findings (≤ dr_signal_findings threshold). Diminishing returns reached. Continue or escalate?"
Choices: Continue / Escalate. No PASS exit from this prompt.
Non-interactive (interactive: false): log DR signal: fired in the round-N-ledger.md. No prompt. No behavior change. Loop continues per existing logic.
Cost-Cap Prompt
Fires when LOCAL round ≥ cost_cap_threshold (default 3). Interactive only.
Interactive:
"Quality gate round N (cost-cap threshold = T, cap exceeded). Score progression: [weighted scores list]. Continue or escalate?"
Choices: Continue / Escalate. No PASS-with-deferred exit in v0.1.
Counter semantics (chunked-gate): cost-cap uses the LOCAL (per-chunk) round counter. On a chunked gate, the cap fires once per chunk that reaches LOCAL round ≥ cost_cap_threshold. The cross-chunk integration round (where suppression_threshold = 5) fires cost-cap at its own LOCAL round ≥ 3. For builds with ≥3 chunks, consider passing cost_cap_threshold: 5 or null to reduce prompt frequency.
Non-interactive: log Cost-cap signal: fired in the round-N-ledger.md. No prompt. No behavior change.
Combined-Prompt Rule
When cost-cap and DR signals fire in the same round in interactive mode, emit a single combined prompt — not two sequential prompts:
"Round N: cost-cap exceeded (threshold T) AND diminishing returns (K NEW findings ≤ S). Continue or escalate?"
Non-Interactive End-of-Gate Summary
On gate termination (any verdict), the orchestrator emits a single summary line in the dispatch return to the parent skill:
CostCapSignals: <DR-fire-count>+<cost-cap-fire-count>/<rounds>
Example: CostCapSignals: 0+2/4 (zero DR fires, two cost-cap fires, four rounds). This gives the parent skill (build, spec, debugging) a structured signal without changing termination behavior. The same value appears as a verdict-marker field (see Verdict Marker spec below).
Non-Skippability
This gate cannot be bypassed without explicit user approval. Task size, complexity, or scope is never a valid reason to skip. The invoking skill is responsible for always dispatching the gate AND letting it run to completion.
The gate is not "done" until it completes with a clean round (0 Fatal, 0 Significant on a fresh review). Fixing findings and moving on without a verification round is a skip, not a pass. The iteration loop exists because fix agents introduce new issues or incompletely resolve old ones — fresh-eyes re-review catches what the fixer missed.
The only valid skip is an unambiguous user instruction specifically referencing the gate (e.g., "skip the quality gate"). General feedback like "looks good" or "move on" is not skip approval. Once a gate has run and presented findings to the user, the user's decision to proceed is authoritative.
Architectural Concerns Exit
A fix agent may encounter a finding that cannot be resolved by editing within the declared change boundary — the artifact's structure itself is the problem. This is the only non-clean exit that bypasses suppression at any round.
Declarant: The fix agent only. Red-team and verifier agents may flag architectural concerns in their output, but those route through normal severity (Fatal/Significant) — only the fix agent can declare an architectural exit.
Signal format: The fix agent's return receipt includes a VERDICT: ARCHITECTURAL_BLOCK line and a mandatory CLAIMS: citation describing the structural barrier. Format:
VERDICT: ARCHITECTURAL_BLOCK
CLAIMS:
- <Fatal/Significant finding id from the round's red-team findings>
- <one-sentence explanation of why this cannot be fixed within the change boundary>
WITNESS:
- kind: lint
- expect-fail: "fixable-within-change-boundary"
NEXT: orchestrator-escalate-architectural
Orchestrator action on ARCHITECTURAL_BLOCK:
- Verify the receipt parses per Tier 1 lint (see Receipt Linter).
- Write
gate-verdict-<run-id>.md with Verdict: ARCHITECTURAL and the standard fields.
- Surface to the user: "Architectural concern declared at round N by fix agent. Citation: [CLAIMS]. The artifact requires structural changes beyond the current change boundary. Options: (a) expand change boundary and re-run gate, (b) escalate to the parent skill (design or planning), (c) accept findings as-is."
- Do NOT loop further. ARCHITECTURAL is a terminal verdict.
Carve-out from Non-Skippability. Non-Skippability says "the gate is not done until 0 Fatal / 0 Significant on a fresh review." The ARCHITECTURAL exit is the documented exception: it acknowledges that some findings cannot be resolved without leaving the current artifact's scope. The exit is non-clean by design and routes the user to a parent-skill remediation rather than continued looping.
Anti-rationalization. ARCHITECTURAL is NOT an escape hatch for "this finding is hard" — the fix agent must articulate a structural reason in CLAIMS. Difficulty alone routes through normal Fatal/Significant fixing. The verdict is rare; in practice, expect 0-2 per pipeline.
Fix Mechanism
The orchestrator coordinates the loop but does NOT fix artifacts directly. Fixes are dispatched to a separate subagent to maintain separation of concerns between coordination, review, and remediation.
| Artifact Type | Fix Agent |
|---|
| design | Plan Writer subagent revises the doc |
| plan | Plan Writer subagent revises the plan |
| code | Fix subagent (new, not the original implementer) |
| hypothesis | Debugging skill's hypothesis refinement (see below) |
| mockup | Fix subagent |
| translation | Fix subagent revises the translation map |
Before dispatching the fix agent (code artifacts only): If crucible:checkpoint is available, create checkpoint with reason "pre-qg-fix-round-N". Non-code artifacts (design, plan, hypothesis, mockup, translation) skip this step — they are fully captured by the existing artifact-N.md snapshots.
The fix agent (the design / plan / code / mockup / translation rows above) is dispatched with subagent_type: crucible-qg-fix, which inherits the session model (agents/crucible-qg-fix.md) — the fix output is re-reviewed by the now-Opus red-team each round, so a weaker fixer costs at most an extra round, never a missed bug; do not pass a call-level model:. (The hypothesis row is the one exception — it routes to the debugging skill's own hypothesis-refinement agent, not crucible-qg-fix; that agent is outside this skill's surface.) The fix agent receives: (a) the current artifact, (b) the red-team findings, (c) project context, and (d) the fix journal from prior rounds (see Fix Memory below). It returns the revised artifact. The orchestrator writes the revised artifact to the scratch directory and dispatches the next red-team round.
The orchestrator never applies fixes directly. Even trivial fixes go through a fix agent to maintain separation of concerns. The cost of dispatching for a small fix is negligible; the risk of the orchestrator conflating coordination with fixing is not.
No-Op Fix Detection
A no-op fix is structural signal that the loop has zero forward momentum. The orchestrator detects no-op fixes via either of two conditions:
- Byte-identical artifact: The fix agent's returned artifact is byte-for-byte identical to the input artifact. Detect by SHA-256 comparison.
- All-Unresolved verifier: The fix verifier returns no Resolved findings (every targeted finding remains Unresolved).
When either condition is met:
- Record
no-op-fix: true in round-N-score.md
- Escalate immediately, regardless of round number — this overrides pre-threshold suppression. Report: "No-op fix detected at round N: [byte-identical artifact | verifier marked all findings Unresolved]. The loop has zero forward momentum. Escalating."
- Verdict:
ESCALATED (a no-op is not architectural — the fix agent declined to engage, not declared structurally unfixable). When the architectural-candidates list is empty (so no promotion re-dispatch fires), write Reason: no-op-fix. When the list is non-empty, the promotion path below governs the Reason token.
Architectural-candidate promotion path. If the no-op happened while the architectural-candidates list is non-empty (see Fix Verification), the orchestrator re-dispatches the fix agent ONE more time. The re-dispatch prompt enumerates ALL currently-set candidate finding-ids and instructs the fix agent to either (a) resolve any one of the contested Fatal findings, or (b) return VERDICT: ARCHITECTURAL_BLOCK with a CLAIMS citation describing the structural barrier — any one resolution path applies independently per candidate. This re-dispatch is executed as the Pre-precedence resolution step (see Exit Precedence) — it runs BEFORE precedence evaluation, ensuring the fix agent's second-chance declaration is never preempted by a higher-precedence co-firing exit. The re-dispatch round does NOT increment the gate's round counter (it is a remediation retry within the same no-op round). If the second dispatch produces a clean fix, the gate continues to the next red-team round normally. After the second fix dispatch (when it produced a clean fix, not another no-op or ARCHITECTURAL_BLOCK), the orchestrator runs the fix verifier on the second-fix artifact before the next red-team round. The verifier's output (including the semantic-equivalence: lines per Step 5) replaces the first-fix verification's output in round-N-verification.md. This ensures the architectural-candidates clearing rule has authoritative semantic-equivalence data even when a no-op was promoted to a clean fix mid-round. If the second dispatch returns ARCHITECTURAL_BLOCK, route to the ARCHITECTURAL exit (see Architectural Concerns Exit). If the second dispatch also produces a no-op, exit as ESCALATED with reason "no-op-with-architectural-candidate" and include both no-op receipts in the escalation output.
This rule is necessary because no-op rounds preserve the weighted score, which under pre-threshold suppression would otherwise loop without escalation. No-op detection is orthogonal to score trajectory.
Scope Anchoring for Fix Agents
Fix agents are prone to drift — addressing findings by adding unrequested features, restructuring documents, or expanding scope beyond what was asked. This costs real time in re-anchoring and rework.
Before dispatching each fix agent, the orchestrator MUST include in the fix prompt:
- Scope statement: "You are fixing ONLY the findings listed below. Do not add features, restructure the document, or make changes outside the scope of these findings."
- Change boundary: List the specific sections/files the fix agent is allowed to modify. If a finding requires changes outside these boundaries, the fix agent must flag it rather than making the change.
- Drift detection: After the fix agent completes, the orchestrator checks whether the fix touched files or sections not listed in the change boundary. If out-of-scope changes are detected: reject the entire fix round output, re-dispatch the fix agent with explicit instructions to omit the out-of-scope changes, and include the out-of-scope items as context for the next red-team round.
Why this matters: The #1 user friction with the quality gate is fix agents drifting from the original design by adding unrequested content. Scope anchoring turns "stop. skipping. steps." into a structural guardrail.
Fix Memory
Anti-anchoring is a property of review, not remediation. Reviewers need fresh eyes to avoid confirmation bias. Fix agents need institutional memory to avoid repeating failed strategies.
The quality gate maintains a fix journal (fix-journal.md in the scratch directory) that accumulates across rounds. After each fix agent completes, the orchestrator appends a structured entry:
## Round N Fix
- **suppressed-signal:** none | regression | sustained-regression | stagnation-would-fire | diminishing-returns | oscillation
- **no-op-fix:** true | false
- **Findings addressed:** [list of Fatal/Significant findings from round N, summarized]
- **Approach taken:** [1-2 sentence description of fix strategy]
- **Files changed:** [list of files modified]
- **Reasoning:** [why this approach was chosen over alternatives]
The suppressed-signal and no-op-fix fields are copied from round-N-score.md after the fix completes; they are not authored by the fix agent. Forge consumes these to detect early-thrash patterns.
On subsequent rounds, the fix agent receives the full fix journal. This gives the fix agent critical context:
- What approaches were already tried (avoid repeating failed strategies)
- Which files were already modified (avoid unknowingly reverting prior fixes)
- The reasoning chain across rounds (understand the trajectory of remediation)
Anti-anchoring is preserved. The fix journal is NEVER passed to the red-team reviewer. Reviewers see only the clean artifact. The journal flows exclusively through the remediation path: fix agent writes it, next fix agent reads it, orchestrator maintains it.
Round 1 fix agents receive an empty journal (no prior rounds). This is the only round where the fix agent works without remediation history.
Why this matters: Without fix memory, the most common causes of stagnation and oscillation are fix agents repeating failed approaches or unknowingly reverting prior fixes while addressing new findings. Fix memory turns these escalation events into solvable problems -- the fix agent can see what was already tried and choose a genuinely different approach.
Compaction recovery: The fix journal is written to fix-journal.md in the scratch directory alongside round scores and findings. It is recovered automatically when the orchestrator reads the scratch directory after compaction.
Fix Verification
After each fix agent completes and before the next red-team round, dispatch a Fix Verifier — a dedicated Sonnet agent that checks whether each fix actually resolves its stated finding. No re-fix sub-loop; the verifier checks once, and its output feeds into the fix journal for the next round.
Dispatch method: Task tool, subagent_type: crucible-qg-verifier (the agent def pins Sonnet — agents/crucible-qg-verifier.md; do not pass a call-level model:), same pattern as the stagnation judge. The verifier needs no file access; the orchestrator includes all input in the dispatch file directly.
Input the orchestrator provides:
- Round N findings (the findings the fix agent was asked to address)
- The current round's fix journal entry only — the
## Round N Fix section just appended (not the full journal)
- Prepared artifact:
- Non-code (design docs, plans, hypotheses, mockups, translations): post-fix version in full
- Code: diff + full post-fix source of files touched by the diff. For large implementations (>2000 lines), dispatch one verifier call per finding if context exceeds limits.
- The full content of
fix-verifier-prompt.md as the agent's instructions
Reading the verdict: The verifier returns a per-finding Resolved/Unresolved table and an overall PASS/FAIL.
Handling Unresolved findings:
- Fatal-severity Unresolved: Flagged as "prior unresolved Fatal — must address" in the next round's fix dispatch. This is binding with one-round grace: if the fix agent addresses it and the next red-team round does NOT re-raise the finding, the binding expires. If the verifier marks the same Fatal as Unresolved again (persistent disagreement), the verdict downgrades to informational AND the orchestrator appends that finding-id to the
architectural-candidates list for the next round's fix dispatch (see below). Sonnet should not permanently override Opus, but persistent verifier-red-team agreement that a Fatal cannot be fixed is structural signal — route it to the architectural exit rather than letting it churn silently.
- Architectural-candidate flag (set on persistent-disagreement downgrade): The next round's fix-agent prompt enumerates every finding-id currently in the
architectural-candidates list, e.g.: "The prior two consecutive verifier rounds both marked Fatal <id-1>[, <id-2>, ...] Unresolved while the red-team has re-raised [it/them]. If you cannot resolve any of these within the change boundary on this round, return VERDICT: ARCHITECTURAL_BLOCK citing one or more of these findings (see Architectural Concerns Exit) instead of producing a no-op fix."
- Significant-severity Unresolved: Appended to the fix journal as informational context. The next round's fix agent may address, disagree with, or deprioritize.
- All Resolved (PASS): Proceed to next red-team round normally.
- All Unresolved (verifier-PASS=false, no Resolved findings): This is structural signal that the fix round did no work. The orchestrator records
no-op-fix: true in round-N-score.md and applies the No-Op Fix Detector rule (see Fix Mechanism > No-Op Fix Detection).
Fix journal integration: The verifier's output is appended under a ### Verifier Assessment heading in the fix journal, distinct from the ## Round N Fix entry format. This keeps verifier assessments on the remediation path (fix agents see them) without contaminating the review path (red-team never sees them).
Anti-anchoring preserved: The verifier is on the remediation path — its output flows to fix agents only, never to the red-team reviewer. Same isolation as the fix journal itself.
Round counter unchanged: The verifier dispatch does not increment the round counter. It is part of the fix step, not a separate review round.
Stagnation Detection
A single stagnation pipeline with three optional model tiers, all gated by suppression_threshold:
- Orchestrator first-pass (always runs) — local arithmetic check on weighted score and Fatal count. Cheapest; deterministic; runs every round but only escalates at round ≥ threshold (with sustained-regression and no-op-fix as the at-any-round exceptions).
- Sonnet stagnation judge (runs at round ≥ threshold - 4, silent until threshold) — semantic comparison of finding sets across rounds. Verdict: PROGRESS / STAGNATION / DIMINISHING_RETURNS. Silent dispatches seed comparison history (see Judge Dispatch).
- Multi-model consensus (runs on Round 1 and every
max(1, suppression_threshold // 3) rounds thereafter up to round 15, when consensus_query available) — cross-model verdict on the same comparison inputs. Higher confidence; carries pre-threshold escalation power via the consensus carve-out (see Pre-Threshold Consensus Carve-Out).
The three tiers share the same trigger (same-or-higher weighted score, no Fatal improvement) but produce distinct signals at different cost points. The orchestrator first-pass is the always-on rail; the judge adds semantic recurring/new classification; consensus adds cross-model unanimity weighting. Each tier's verdict is reflected in round-N-score.md and round-N-comparison.md regardless of whether it escalates.
First-Pass Check (orchestrator — runs every round)
Stagnation uses weighted scoring (Fatal=3, Significant=1) AND Fatal count tracking.
Progress requires EITHER:
- Weighted score strictly lower than prior round, OR
- Fatal count strictly lower AND weighted score same-or-lower
If either condition is met → progress, loop again. No judge needed.
Pre-threshold gating. Before round suppression_threshold, the single-round regression and stagnation paths below do NOT escalate. Record the signal in round-N-score.md for audit purposes and continue looping. The single-round-regression check below applies only at round suppression_threshold and later. (See Skill Arguments for threshold defaults and overrides.)
Sustained-regression hard exit (applies at every round, including pre-threshold). If score(N) > score(N-1) AND score(N-1) > score(N-2) — two consecutive strict score increases — escalate immediately as a sustained regression. This rule overrides pre-threshold suppression and guarantees loop termination. Requires at least 3 rounds of history (skip on rounds 1 and 2). See How It Works step 10 for rationale.
Oscillation detection (round ≥ suppression_threshold): If the weighted score increases (not just stays the same) for a single round, escalate immediately as a regression. Report: "Round N score (X) is higher than Round N-1 score (Y). The fix cycle introduced new issues. Escalating." No judge needed.
Regression with checkpoint (any escalation path on code artifacts): When the gate escalates with Verdict: ESCALATED | STAGNATION | SUSTAINED_REGRESSION | ARCHITECTURAL, glob for all pre-qg-fix-round-* checkpoints in the checkpoint skill's store. Surface the full list in escalation output:
Pre-fix checkpoints from this gate run (most-recent first):
- round 9: <hash> (score 4, before round-9 fix)
- round 8: <hash> (score 3, before round-8 fix)
- round 7: <hash> (score 5, before round-7 fix)
...
The user can identify the inflection point (e.g., round-3 fix made things worse) by score trajectory and choose any checkpoint to restore. Options offered: (a) restore to a chosen checkpoint and retry with different fix strategy, (b) continue with current state, (c) escalate to parent orchestrator.
If no checkpoints exist (checkpoint skill unavailable), escalate without the restore option.
Multi-Model Consensus (when available)
When the consensus_query MCP tool is available and consensus mode verdict is enabled:
-
Instead of dispatching a single judge via Task tool (subagent_type: crucible-qg-judge), call
consensus_query(mode: "verdict") with:
- prompt: the stagnation judge prompt from
stagnation-judge-prompt.md
- context: round N findings, round N-1 findings, latest fix journal entry,
prior comparison files (same inputs as the single-model judge)
- metadata: { artifact_type, round_number, score_progression }
-
Read the consensus response:
- If
status: "complete" or status: "partial":
- Use the
synthesis verdict (PROGRESS/STAGNATION/DIMINISHING_RETURNS)
- If the verdict is STAGNATION or DIMINISHING_RETURNS and disagreements
exist, include the dissent summary in the escalation message:
"Stagnation detected (consensus: N/M models agree, dissent: [summary])."
- If
status: "unavailable":
- Fall back to the single-judge dispatch (
subagent_type: crucible-qg-judge, see
Judge Dispatch → Dispatch method)
-
The comparison file (round-N-comparison.md) includes the consensus
metadata: models queried, models responded, agreement level, and any
dissenting verdicts.
Persistence Check (Component 4 / #265)
The orchestrator dispatches a persistence checker between a non-clean red-team round's receipt and the stagnation judge's dispatch, conditional on cross-channel corroboration triggers. The stagnation judge itself is UNCHANGED — its 4-data-input set (plus the prompt itself) and procedure are preserved verbatim. The persistence signal is applied as an orchestrator-layer verdict-level promotion AFTER the judge returns (see Verdict-Level Promotion below).
Counter selection. For chunked gates, "round N" and "round N+1" in this section refer to LOCAL round numbers within the in-progress chunk (consistent with suppression_threshold, look-harder, tail-rubric, consensus cadence, and the stagnation judge). Cross-chunk persistence checking is NOT performed — each chunk's persistence-checker fires only against the prior LOCAL round of the same chunk. The marker / convergence-log encoding for persistence rounds uses the canonical <chunk_id>:<local_round> grammar per INV-A15.
Trigger (INV-A10). The persistence checker fires on every non-clean red-team round N+1 where ALL of:
- Round N's fix-journal entry's
### Verifier Assessment sub-section contains ≥1 finding with verdict Unresolved (the verifier's per-finding vocabulary is exactly Resolved | Unresolved per fix-verifier-prompt.md). Partial-Unresolved (some Resolved, some Unresolved) fires it; full-Resolved SKIPS it (symmetric leverage — converging runs bypass the mechanism entirely); full-Unresolved fires it.
- Both
round-N-findings.md AND round-(N+1)-findings.md exist on disk.
Verifier-error rounds (where the round-N fix-verifier dispatch failed or its ### Verifier Assessment sub-section is malformed/absent) implicitly skip the persistence checker per fail-open semantics — the ≥1 Unresolved gate is vacuous, so the trigger does not fire. The F1 promotion is also skipped (vacuous gating on condition (d) below).
Dispatch. When the trigger fires, dispatch the persistence checker as a fresh Task with subagent_type: crucible-qg-verifier (reused — both the fix verifier and the persistence checker are Sonnet mechanical structural checks; the agent def pins Sonnet, agents/crucible-qg-verifier.md; do not pass a call-level model:) and persistence-checker-prompt.md as the prompt. The persistence checker emits a JSON correspondence object, not an Evidence Receipt — the shared crucible-qg-verifier def is deliberately return-format-neutral so it does not conflict with that (the persistence checker is not a receipt-bearing role — its JSON is consumed directly by the orchestrator, written to round-(N+1)-persistence.md per the flow below, and is NOT run through the receipt linter; this is a pre-existing exemption that #352 does not change). Inputs supplied verbatim by the orchestrator:
round-N-findings.md (prior round's findings)
round-(N+1)-findings.md (current round's findings)
- The round-N fix-journal entry — full entry, including both the
## Round N Fix agent-authored sub-section AND the ### Verifier Assessment verifier-authored sub-section. Both sub-sections live in the remediation path and never leak to the red-team.
The persistence checker performs a structural diff — not an adversarial review. It produces only correspondence judgments between round-(N+1) findings and the round-N Unresolved set. Output is a JSON object (see persistence-checker-prompt.md for schema) written to round-(N+1)-persistence.md BEFORE the stagnation judge dispatches on round N+1.
Failure modes (fail-open). If the persistence checker fails (Task error, malformed output): record status: error in round-(N+1)-persistence.md, treat persistent_finding_count: 0, and proceed to standard stagnation judge dispatch. The orchestrator does NOT retry a failed persistence-checker dispatch within the same round. Error dispatches DO count toward PersistentCheckCount (the dispatch happened); only the resulting persistent_finding_count is treated as 0.
Data flow guarantees (anti-anchoring preservation, INV-A11). Persistence-checker output flows ONLY to the orchestrator (read path between judge dispatch and verdict marker write). It NEVER flows back into the red-team prompt on subsequent rounds. It NEVER flows into the stagnation judge's input set. The persistence checker itself sees only the three inputs above; it never receives prior-round content beyond round N, the orchestrator's state machine, or the artifact bytes.
Judge Dispatch (silent-seed at round ≥ max(1, suppression_threshold - 3) when suppression_threshold ≥ 6; normal escalation at round ≥ suppression_threshold)
Rounds 1 through max(0, suppression_threshold - 4): Skip judge dispatch entirely. Loop again regardless of score trajectory. The max(0, ...) clamp handles short thresholds: for suppression_threshold ≤ 4 the upper bound clamps to 0 (no rounds are skipped — judge dispatches normally starting at the threshold). For threshold 10: rounds 1-6 skipped. For threshold 3: no rounds skipped; judge runs from round 3 onward.