Depends on #166.
Problem
The PR review cap resets on every relaunch, so it is not a budget. An issue that burned three review rounds and landed in needs_human draws three more on the next run, with less context than the run that spent the first three, and this repeats indefinitely.
processIssue zeroes both halves of the state: settled: [] at :4821 and metrics at :4846. The process_pr path at :4871 then calls reviewAndMerge, whose loop starts at iter = 1 against MAX_PR_REVIEW_ITERATIONS (:4607).
Change
Seed ctx.metrics and ctx.settled from the durable block the dependency issue writes, and enforce the seeded budget: a resume arriving with the budget already spent goes straight to needs_human without running a review round.
Give that its own gate_findings disposition. "Resumed into an exhausted budget" is a different outcome from "spent three rounds here," and collapsing them into carried-unresolved hides the thing worth seeing. Add a Verification Gaps line so the batch PR explains why the issue was left alone rather than appearing to have been skipped for no reason.
Three constraints, each verified
:4609 is an assignment, not an increment. ctx.metrics.pr_review_iters = iter, with iter a fresh local starting at 1, clobbers any seeded value on the resumed run's first pass. The counter has to become a real accumulator before seeding it means anything.
Only adjudicated iterations count. reviewAndMerge bails at :4656 (if (!spec || !code) return fail(...)) before the tally at :4669, so the rule "increment where recordGateOutcome is called" already distinguishes a real adjudicated round from an agent death, with no new judgment required. Without this, a transient outage or API failure permanently consumes budget on a PR that was never oscillating, and resume gets worse at the job it exists for.
Note that this changes the meaning of computeFriction's pr_review_iters numerator (:2437) from rounds-entered to rounds-adjudicated. That is the intended meaning, but it is a behaviour change to an existing metric and belongs in the PR description.
Scope the carry honestly. pr_review_iters carries. approach_iters and plan_iters carry, but note in the PR that this is a no-op on the process_pr path, which jumps straight to reviewAndMerge and never runs those gates: it matters only on a resume_point: 'implement' resume. quality_iters does not carry, because it is a run-wide aggregate rather than a per-scope budget. test_iters does not carry, because the test loop is oracle-grounded and a reset budget there is not the same hazard as a reset judge budget.
Acceptance criteria
- A second run against the same open PR resumes with the spent count intact and draws only the remaining rounds.
- A resume arriving with the budget exhausted returns
needs_human with zero review stages run, its own disposition recorded, and one Verification Gaps line.
- An agent death during review does not consume budget. Add a test for this specifically.
- A dead or failed preflight read does not silently grant a fresh budget; it is logged and distinguishable.
ctx.settled is populated on a resume and renders into both review prompts at :4619 and :4639.
- A first run, with no prior block, is byte-identical to today.
node --test stays green. Extend tests/pr-review-gate.test.js, tests/friction.test.js, tests/gate-findings.test.js.
Documentation target
Document in CHANGELOG.md and in docs/architecture/gate-hygiene.md, adding its row to docs/architecture/index.md's file map if not already present.
Do NOT edit docs/architecture/pipeline.md, metrics.md, or failure-semantics.md. Their moved prose is hash-frozen by tests/architecture-provenance.test.js, and since the tech-docs stage runs at :4707, after the last test-loop execution, a frozen-prose edit surfaces only as a red CI check on the batch PR. New pages are authored text and are explicitly allowed; see docs/architecture/CLAUDE.md.
Depends on #166.
Problem
The PR review cap resets on every relaunch, so it is not a budget. An issue that burned three review rounds and landed in
needs_humandraws three more on the next run, with less context than the run that spent the first three, and this repeats indefinitely.processIssuezeroes both halves of the state:settled: []at:4821andmetricsat:4846. Theprocess_prpath at:4871then callsreviewAndMerge, whose loop starts atiter = 1againstMAX_PR_REVIEW_ITERATIONS(:4607).Change
Seed
ctx.metricsandctx.settledfrom the durable block the dependency issue writes, and enforce the seeded budget: a resume arriving with the budget already spent goes straight toneeds_humanwithout running a review round.Give that its own
gate_findingsdisposition. "Resumed into an exhausted budget" is a different outcome from "spent three rounds here," and collapsing them intocarried-unresolvedhides the thing worth seeing. Add a Verification Gaps line so the batch PR explains why the issue was left alone rather than appearing to have been skipped for no reason.Three constraints, each verified
:4609is an assignment, not an increment.ctx.metrics.pr_review_iters = iter, withitera fresh local starting at 1, clobbers any seeded value on the resumed run's first pass. The counter has to become a real accumulator before seeding it means anything.Only adjudicated iterations count.
reviewAndMergebails at:4656(if (!spec || !code) return fail(...)) before the tally at:4669, so the rule "increment whererecordGateOutcomeis called" already distinguishes a real adjudicated round from an agent death, with no new judgment required. Without this, a transient outage or API failure permanently consumes budget on a PR that was never oscillating, and resume gets worse at the job it exists for.Note that this changes the meaning of
computeFriction'spr_review_itersnumerator (:2437) from rounds-entered to rounds-adjudicated. That is the intended meaning, but it is a behaviour change to an existing metric and belongs in the PR description.Scope the carry honestly.
pr_review_iterscarries.approach_itersandplan_iterscarry, but note in the PR that this is a no-op on theprocess_prpath, which jumps straight toreviewAndMergeand never runs those gates: it matters only on aresume_point: 'implement'resume.quality_itersdoes not carry, because it is a run-wide aggregate rather than a per-scope budget.test_itersdoes not carry, because the test loop is oracle-grounded and a reset budget there is not the same hazard as a reset judge budget.Acceptance criteria
needs_humanwith zero review stages run, its own disposition recorded, and one Verification Gaps line.ctx.settledis populated on a resume and renders into both review prompts at:4619and:4639.node --teststays green. Extendtests/pr-review-gate.test.js,tests/friction.test.js,tests/gate-findings.test.js.Documentation target
Document in
CHANGELOG.mdand indocs/architecture/gate-hygiene.md, adding its row todocs/architecture/index.md's file map if not already present.Do NOT edit
docs/architecture/pipeline.md,metrics.md, orfailure-semantics.md. Their moved prose is hash-frozen bytests/architecture-provenance.test.js, and since the tech-docs stage runs at:4707, after the last test-loop execution, a frozen-prose edit surfaces only as a red CI check on the batch PR. New pages are authored text and are explicitly allowed; seedocs/architecture/CLAUDE.md.