feat(miner-governor): local create->score->self-review->decide iterate-loop orchestrator (#2333) - #5044
Merged
Merged
Conversation
…on reason #2333's own deliverable calls for "a bounded max-iteration/max-cost ceiling enforced INSIDE the loop itself" -- iterate-policy.ts (#2335) already enforces the iteration ceiling but has no notion of cost. Rather than have the loop mechanics (#2333) make its own ad-hoc abandon decision alongside decideNextAction's, extend the same closed AbandonReason vocabulary and precedence ladder with cost_ceiling_reached, keeping decision-making authority in one place. costCeilingReached is optional on IterationState (defaults to not-reached) so it stays backward compatible with existing fixtures. Checked right after the iteration ceiling, before the no-progress detector -- both are "hard resource ceiling" checks of the same tier.
…e-loop orchestrator Adds runIterateLoop (#2333): the actual autonomous control flow Phase 3 exists to build. Repeatedly invokes a CodingAgentDriver, self-reviews the resulting diff via the byte-identical predicted-gate target (self-review-adapter.ts, #2334), and consults the pure policy (iterate-policy.ts, #2335) to decide -- autonomously, no human in the loop at this stage -- whether to keep iterating, hand off to Phase 4 submission, or abandon. - Fail-closed on ambiguity: a driver run that doesn't complete successfully (including a thrown exception, normalized rather than left to propagate), or a self-review call that itself throws, both become an "ambiguous" SelfReviewOutcome -- iterate-policy.ts's own precedence then abandons rather than optimistically continuing or handing off. The loop never fabricates a pass from anything but a genuinely successful runSelfReview call. - Bounded inside the loop: both the iteration ceiling and the optional cumulative-cost ceiling (summed driver turnsUsed across every iteration) are enforced every iteration, not left to an external caller to remember. maxIterations <= 0 abandons before ever invoking the driver; a fractional maxIterations is truncated toward zero rather than silently permitting one extra partial iteration. - Auditable: every iteration's decision is recorded via the injected appendAttemptLogEvent dependency, mapped onto attempt-log.ts's fixed six-value vocabulary (continue -> attempt_tool_edit, handoff -> attempt_succeeded, deliberate-disengagement abandons -> aborted, genuine-failure-to-converge abandons -> failed). A logging failure never crashes the loop or alters its decision. Also extends iterate-policy.ts (#2335, already merged) with a cost_ceiling_reached AbandonReason, since #2333's own "max-cost ceiling enforced inside the loop" deliverable needs a vocabulary slot iterate-policy.ts didn't have yet -- kept in the same closed-union policy module rather than having the loop mechanics make an ad-hoc abandon decision of its own alongside decideNextAction's. Barrel-exported from the engine's public entrypoint.
Contributor
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
gittensory-ui | 9c509f4 | Commit Preview URL Branch Preview URL |
Jul 11 2026, 10:26 AM |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #2333. Also extends #2335's
iterate-policy.ts(already merged) with acost_ceiling_reachedAbandonReason-- see below.Adds
runIterateLoop(packages/gittensory-engine/src/miner/iterate-loop.ts): the actual autonomous control flow this phase exists to build. Repeatedly invokes aCodingAgentDriver(coding-agent-driver.ts), self-reviews the resulting diff against the byte-identical predicted-gate target (runSelfReview, self-review-adapter.ts / #2334), and consults the pure policy (decideNextActionWithReason, iterate-policy.ts / #2335) to decide -- autonomously, no human in the loop at this stage -- whether to keep iterating, hand off to Phase 4 submission, or abandon.Deliverables, mapped:
iterate-loop.tsimplementing create->score->self-review->decide, consuming aCodingAgentDriver+ the predicted-gate self-review target -- done.maxIterations <= 0abandons before ever invoking the driver; a fractionalmaxIterationstruncates toward zero rather than silently permitting a partial extra iteration (see "a real bug found via dead-code reasoning" below). The optionalmaxTotalTurnscost ceiling sums each iteration'sturnsUsed.appendAttemptLogEventdependency, mapped onto attempt-log.ts's fixed six-value vocabulary (continue->attempt_tool_edit,handoff->attempt_succeeded, deliberate-disengagement abandons [rejection_signaled,self_review_ambiguous] ->attempt_aborted, genuine-failure-to-converge abandons [max_iterations_reached,cost_ceiling_reached,no_progress] ->attempt_failed). A logging failure never crashes the loop or alters its decision (mirrors the governor-ledger/pretooluse-hook append-failure handling elsewhere in this package).runSelfReviewcall that itself throws both become an"ambiguous"SelfReviewOutcome; iterate-policy.ts's own precedence then abandons.outcome: "handoff"except a genuine clean predicted-gate pass.Why
iterate-policy.tsneeded a small extension: #2333's own "max-cost ceiling enforced inside the loop" deliverable has no equivalent in #2335's closedAbandonReasonvocabulary (which only knew about the iteration ceiling, not cost). Rather than have this loop make an ad-hoc abandon decision of its own alongsidedecideNextAction's, I extended the same precedence ladder withcost_ceiling_reached(checked right after the iteration ceiling) -- keeps decision-making authority in one place.costCeilingReachedis optional onIterationState, defaulting to not-reached.A real bug found via dead-code reasoning, not just coverage-chasing: while verifying that a defensive post-loop fallback was genuinely unreachable, I found it wasn't, for one specific input: a fractional
maxIterations(e.g.2.5) would let this loop's ownforbound and iterate-policy.ts'siterationNumber >= maxIterationsceiling check disagree by less than one iteration, silently permitting one extra partial iteration beyond the caller's intent. Fixed by truncatingmaxIterationsonce at the top of the function (Math.max(0, Math.trunc(...))), with a dedicated regression test.Validation
Measured, not assumed:
(the
--incremental falseis load-bearing -- the root tsconfig's inheritedincremental: truecache can silently no-op atscre-emit afterrm -rf dist-testeven on a 0 exit code; see the local memory note I wrote after tripping over it debugging this file's coverage.)iterate-policy.js: 100.00% lines / 100.00% branch / 100.00% funcs.self-review-adapter.js: 100.00% lines / 100.00% branch / 100.00% funcs.iterate-loop.js: 95.48% lines / 96.23% branch / 100.00% funcs. Not 100 -- and I want to be precise about why rather than paper over it:ok:falsewith/without an error message, a thrownError, a thrown non-Error value), every self-review-failure mode (runSelfReviewthrowing anErrorvs. a non-Error value), every abandon reason (rejection_signaledwinning over an otherwise-passing review,self_review_ambiguousx2,max_iterations_reached,cost_ceiling_reached,no_progress), the multi-iteration continue-then-handoff path, optional-field threading (branchRef,labels,authorAssociation), the fractional-maxIterationstruncation, and logging-failure resilience.blockerCodesFromContinuingOutcomethat only executes ifdecideNextActionWithReasonever returned"continue"for a non-"fail"self-review outcome, which its own precedence ladder structurally prevents (both"ambiguous"and"pass"short-circuit to abandon/handoff before the"continue"fallthrough); (2) a post-loop defensive fallback, now unreachable given themaxIterationstruncation fix above. Both are marked with this codebase's standard/* v8 ignore next */convention (matchingsrc/api/routes.ts's precedent) for when this file is eventually exercised through the root vitest/istanbul pipeline, which already includespackages/gittensory-engine/src/**in itscoverage.includeand does honor that syntax. I could not get the engine package's ownnode --experimental-test-coverageto honor any inline ignore-comment convention I tried (v8 ignore,c8 ignore,node:coverage ignore-- verified empirically, none work), so this specific tool's raw number can't reach 100.00 for genuinely-dead code the way Codecov/vitest can.Test plan
iterate-loop.tshas a dedicated, real (not contrived) test.iterate-policy.tsandself-review-adapter.tsremain at 100% branch coverage.