Skip to content

feat(miner-governor): local create->score->self-review->decide iterate-loop orchestrator (#2333) - #5044

Merged
JSONbored merged 2 commits into
mainfrom
feat/miner-iterate-loop-orchestrator-2333
Jul 11, 2026
Merged

feat(miner-governor): local create->score->self-review->decide iterate-loop orchestrator (#2333)#5044
JSONbored merged 2 commits into
mainfrom
feat/miner-iterate-loop-orchestrator-2333

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Summary

Closes #2333. Also extends #2335's iterate-policy.ts (already merged) with a cost_ceiling_reached AbandonReason -- see below.

Adds runIterateLoop (packages/gittensory-engine/src/miner/iterate-loop.ts): the actual autonomous control flow this phase exists to build. Repeatedly invokes a CodingAgentDriver (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.ts implementing create->score->self-review->decide, consuming a CodingAgentDriver + the predicted-gate self-review target -- done.
  • A bounded max-iteration/max-cost ceiling enforced inside the loop -- done, both. maxIterations <= 0 abandons before ever invoking the driver; a fractional maxIterations truncates toward zero rather than silently permitting a partial extra iteration (see "a real bug found via dead-code reasoning" below). The optional maxTotalTurns cost ceiling sums each iteration's turnsUsed.
  • Every iteration's decision recorded via the attempt-log primitive -- done, via an injected appendAttemptLogEvent dependency, 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).
  • Default behavior on ambiguity is ABANDON, never optimistic handoff -- done. A driver run that doesn't complete successfully (including a thrown exception, which is normalized rather than left to propagate) OR a runSelfReview call that itself throws both become an "ambiguous" SelfReviewOutcome; iterate-policy.ts's own precedence then abandons.
  • Owner-reviewed tests proving the loop never hands off on a FAIL or ambiguous self-review -- done, explicit tests for every abandon reason confirm no path to outcome: "handoff" except a genuine clean predicted-gate pass.

Why iterate-policy.ts needed a small extension: #2333's own "max-cost ceiling enforced inside the loop" deliverable has no equivalent in #2335's closed AbandonReason vocabulary (which only knew about the iteration ceiling, not cost). Rather than have this loop make an ad-hoc abandon decision of its own alongside decideNextAction's, I extended the same precedence ladder with cost_ceiling_reached (checked right after the iteration ceiling) -- keeps decision-making authority in one place. costCeilingReached is optional on IterationState, 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 own for bound and iterate-policy.ts's iterationNumber >= maxIterations ceiling check disagree by less than one iteration, silently permitting one extra partial iteration beyond the caller's intent. Fixed by truncating maxIterations once at the top of the function (Math.max(0, Math.trunc(...))), with a dedicated regression test.

Validation

Measured, not assumed:

npm run build && npx tsc -p tsconfig.test.json --incremental false && node --experimental-test-coverage --test "dist-test/**/*.test.js"

(the --incremental false is load-bearing -- the root tsconfig's inherited incremental: true cache can silently no-op a tsc re-emit after rm -rf dist-test even on a 0 exit code; see the local memory note I wrote after tripping over it debugging this file's coverage.)

  • 424/424 tests pass across the whole engine package.
  • 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:
    • Every reachable branch has a dedicated test: every driver-failure mode (ok:false with/without an error message, a thrown Error, a thrown non-Error value), every self-review-failure mode (runSelfReview throwing an Error vs. a non-Error value), every abandon reason (rejection_signaled winning over an otherwise-passing review, self_review_ambiguous x2, max_iterations_reached, cost_ceiling_reached, no_progress), the multi-iteration continue-then-handoff path, optional-field threading (branchRef, labels, authorAssociation), the fractional-maxIterations truncation, and logging-failure resilience.
    • The 2 remaining gaps (8 source lines total) are both provably unreachable through the real call path, verified via code inspection rather than assumed: (1) a fallback inside blockerCodesFromContinuingOutcome that only executes if decideNextActionWithReason ever 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 the maxIterations truncation fix above. Both are marked with this codebase's standard /* v8 ignore next */ convention (matching src/api/routes.ts's precedent) for when this file is eventually exercised through the root vitest/istanbul pipeline, which already includes packages/gittensory-engine/src/** in its coverage.include and does honor that syntax. I could not get the engine package's own node --experimental-test-coverage to 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

  • 424/424 engine tests pass.
  • Every reachable branch of iterate-loop.ts has a dedicated, real (not contrived) test.
  • iterate-policy.ts and self-review-adapter.ts remain at 100% branch coverage.

…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.
@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

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

@JSONbored
JSONbored merged commit c51fe41 into main Jul 11, 2026
12 checks passed
@JSONbored
JSONbored deleted the feat/miner-iterate-loop-orchestrator-2333 branch July 11, 2026 10:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

maintainer: local create->score->self-review iterate-loop orchestrator (the control-flow core)

1 participant