refactor(coding-agent): wake RLM quiescence on activity changes instead of polling - #1859
Merged
Conversation
…tion and stale runners - Publish _compactionOperation from _runAutoCompaction so the post-compaction continuation runner (and other waiters) serialize against auto/threshold/overflow/requested compaction, not just manual compact(). - Identity-guard the success path of the continuation runner so a cancelled runner cannot forget continuation messages that a replacement settlement still owns (busy/error paths already did).
…ction continue A refine() call could enter its apply phase (which disconnects the agent event listener) between the runner's top-of-loop _waitForRefineIdle() and agent.continue(), losing the continuation's messages. Recheck _refineInFlight under the commit fence like pauses/_compactionOperation; the path from that recheck to agent.continue() is synchronous, mirroring the final refine barrier used by normal turn dispatch.
… snimu/rlm-activity-change-waiter # Conflicts: # packages/coding-agent/src/core/agent-session.ts
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 9593430. Configure here.
A cancelled post-compaction continuation left its runner parked inside waitForIdle when the pump was suspended with queued actions: the wait was not raced against the settlement like its neighbors, so the runner re-registered a session-input checkpoint waiter on every notify. The leaked waiter kept hasPendingAdmissionWaiters true and blocked daemon passivation until an unrelated resume. waitForIdle's loop now takes an optional settlement: the suspended-queue park races the settlement promise, deregisters its waiter on loss, and the loop exits once the settlement is superseded. The runner uses the settlement-scoped variant; public waitForIdle is unchanged.
xeophon
approved these changes
Aug 29, 2026
PR9000
pushed a commit
to PR9000/prime-agent
that referenced
this pull request
Sep 1, 2026
…ad of polling (PrimeIntellect-ai#1859) * fix(coding-agent): serialize post-compaction continuation on run settlement * refactor(coding-agent): wake RLM quiescence on activity changes instead of polling * fix(coding-agent): guard post-compaction continue against auto-compaction and stale runners - Publish _compactionOperation from _runAutoCompaction so the post-compaction continuation runner (and other waiters) serialize against auto/threshold/overflow/requested compaction, not just manual compact(). - Identity-guard the success path of the continuation runner so a cancelled runner cannot forget continuation messages that a replacement settlement still owns (busy/error paths already did). * fix(coding-agent): recheck refine apply before dispatching post-compaction continue A refine() call could enter its apply phase (which disconnects the agent event listener) between the runner's top-of-loop _waitForRefineIdle() and agent.continue(), losing the continuation's messages. Recheck _refineInFlight under the commit fence like pauses/_compactionOperation; the path from that recheck to agent.continue() is synchronous, mirroring the final refine barrier used by normal turn dispatch. * fix(coding-agent): release the continuation runner's idle wait on cancel A cancelled post-compaction continuation left its runner parked inside waitForIdle when the pump was suspended with queued actions: the wait was not raced against the settlement like its neighbors, so the runner re-registered a session-input checkpoint waiter on every notify. The leaked waiter kept hasPendingAdmissionWaiters true and blocked daemon passivation until an unrelated resume. waitForIdle's loop now takes an optional settlement: the suspended-queue park races the settlement promise, deregisters its waiter on loss, and the loop exits once the settlement is superseded. The runner uses the settlement-scoped variant; public waitForIdle is unchanged.
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.

What was wrong
waitForRlmQuiescence— the strong idle barrier gating goal continuations and daemon idle for sessions with subagent work — detected settlement by re-checking on a zero-delaysetTimeout(0)loop: busy-spinning the event loop instead of being told when activity changes (audit: timeouts.md finding 7).The fix
The poll is replaced by an abort-aware activity-change waiter that reuses the existing session-input checkpoint waiter set (no second notification registry). Every component of the quiescence predicate now notifies on its clear path — bash, refine, retry, compaction, branch summaries, terminal actions, and (added in review) the post-compaction continuation settlement, which was the one clear path without a notify and could have hung the barrier permanently in a narrow registration window. Spurious wakes are safe in both directions: all consumers of the shared set re-check their predicates in loops. +31/−9.
How it's verified
Reviewer performed an exhaustive missed-wake audit of every
isSessionActivecomponent with per-clear-site accounting, verified bidirectional spurious-wake safety across the shared waiter set, confirmed no hot-path notifies (all fire once per operation, never per-token), and validated the goal-continuation-quiescence suite (#1610) is untouched and green (7/7). Recursion suite 116/116; full CI-style failing set matches stack base. Two-model implement/review loop, approved on second pass.Stacked on #1751 (test the whole stack at the leaf; merge base-first).
Note: intentionally no Linear ticket for this cleanup stack, so that check stays red.
Note
Medium Risk
Changes core session scheduling, compaction continuation, and RLM idle barriers; regressions could stall continuations, leak admission waiters, or mis-order resume vs queued input, though behavior is heavily covered by updated compaction, queue, and recursion tests.
Overview
Replaces busy-wait patterns in RLM quiescence and post-compaction continuation with waits on shared session activity notifications.
waitForRlmQuiescenceno longer spins onsetTimeout(0)when session-level work is active; it blocks on a new_waitForSessionActivityChangehook that shares the existing checkpoint waiter set._notifySessionInputCheckpointChangeis invoked when that work finishes (bash, refine apply, retry, compaction, branch summary, post-compaction settlement, and related paths) so waiters wake instead of polling.Post-compaction resume drops the fixed 100ms timer.
_runScheduledPostCompactionContinueruns immediately and loops until it can safely callagent.continue(), explicitly waiting for agent idle, retries, refine, an in-flight_compactionOperation, queued-work pauses, and session-input pump idle. AcontinueAfterSessionInputflag on the continuation settlement controls whether an empty “resume after compaction” should still callcontinue()once queued session input has drained.waitForIdle/ checkpoint waiting now register and clean up waiters correctly and canPromise.raceagainst a superseded post-compaction settlement so cancelled continuations do not leak waiters and block daemon passivation.Reviewed by Cursor Bugbot for commit 5bcedd4. Bugbot is set up for automated code reviews on this repo. Configure here.
Linear ticket: ENG-5667
(ticket linked above)
Note
Replace polling with event-driven waiters for RLM quiescence and post-compaction continuations
AgentSession._waitForSessionActivityChangeas a one-shot waiter that resolves on session activity or abort, replacingsetTimeout(0)busy-loop yielding inwaitForRlmQuiescence_schedulePostCompactionContinuenow launches_runScheduledPostCompactionContinuedirectly, which loops waiting for idle, retry, refine, queued-work, and compaction completioncontinueAfterSessionInputflag toPostCompactionContinuationSettlementso continuations can optionally wait for pending session input before callingagent.continue()runUserBash, refine-application paths, branch summary, retry resolution, and compaction completion so quiescence waiters wake promptlywaitForRlmQuiescenceno longer polls on a macrotask interval; any missed_notifySessionInputCheckpointChangecall site will cause quiescence to hang until the next activity notification. Reviewers should verify all session-input-producing paths inAgentSessionnotify checkpoint waiters in theirfinallyblocks.Macroscope summarized 5bcedd4.
Note: trivial textual overlap with #1844 (both add cleanup lines to the same
finallyblock in_runAutoCompaction; both lines should survive). Whichever PR merges second will be refreshed against main.