Repository navigation
fix(scheduler): idle_fill does not lift a park or backoff on an issue whose input never changed (AGT-4787) - #843
Merged
Conversation
… whose input never changed (AGT-4787) One issue was dispatched 18 times in 14 hours after infra_error / operator-park outcomes that retrying cannot change. A run now records a digest of the title and description it started on (own table, read-fail-open); filterAlreadyProcessed skips the NEEDS_HUMAN resume and the RETRY_AT lift while the issue still matches it. The key is not trackerUpdatedAt or a comment count: both move on the daemon's own writes (AGT-4300 measured the same trap).
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.
Why
On 2026-10-20 one cgf-portal issue was dispatched 18 times in 14 hours and 52 issues were touched, mostly after an
infra_erroror a "parked for operator decision" — two outcomes that retrying cannot change. Nothing compared the issue as the last run saw it with the issue as it is now, soidle_filllifted every park a free slot allowed (AGT-4787).Cycle 2 of the factory-stability loop; check C4.
The design decision that mattered
The obvious key — the tracker's update time, or a comment count — is wrong. Both move on the daemon's own writes (progress comments, state transitions). AGT-4300 measured exactly that: one issue's draft was recomputed on every pass,
attempt_noreached 40. A fingerprint on those keys would change after every comment the daemon posts, so the block would never hold. The AGT-4784 description and my own first harness contract both proposed(updatedAt, commentCount); I found the trap while readingautonomousRunner.ts:3188before writing code, and corrected the contract (autoresearch/.../predicate-amendments.md).Tracker comments carry no author either (
user: undefined // TODO,linear.ts:494), and the daemon writes as the operator — so "human comments only" cannot come from authorship.What changes
src/automation/dispatchFingerprint.ts. Own table (automation_parked_inputs), theexecHold.tspattern.inputFingerprint: sha256 over title, description and comments without the daemon's signature (🧭 **[OpenSwarm] …,**[OpenSwarm]…,_via OpenSwarm_). A person can copy the signature; that is the safe direction here (an issue stays parked, a park is never lifted unearned).recordRunInput: called once where the pipeline starts, so every later park or backoff — wherever in the ledger it happens — is measured against the issue as the run saw it. Recording at each park site would have scattered the write across many paths.filterAlreadyProcessed: one new condition,inputUnchanged, on the two idle_fill resume branches (NEEDS_HUMAN, RETRY_AT). An edit to the title or description lifts them as before.runnerExecution.tsis at its 1500-line cap. The call is a dynamic import folded onto the existing setup line; no line was added.Known gap (stated, not hidden)
Tracker comments are not on a listed task (slim mode), so the call site hashes title + description only. An operator answer given only as a comment does not lift a held park; an explicit dispatch or a Todo transition does. The module already takes comments and filters the daemon's own, so wiring them in is a follow-up. This fails in the safe direction and is strictly better than the previous unbounded retry.
Verified
dispatchFingerprint.test.ts(19): signature recognition including person-lookalikes, stability, daemon comments ignored, latest-run semantics, issueId keying, persistence across handles, fail-open reads, fail-closed explicit writes.autonomousRunner.dispatchFingerprint.test.ts(5): the realfilterAlreadyProcessedwith a real ledger. Every blocking case has a control that differs only in the input. One case sets a newtrackerUpdatedAtalone and must stay blocked.trackerUpdatedAt→ 3 fail (the AGT-4300 trap);src/automation: 87 files, 1167 tests pass. typecheck, lint (12 warnings = baseline).