Skip to content

refactor(coding-agent): wake RLM quiescence on activity changes instead of polling - #1859

Merged
xeophon merged 6 commits into
mainfrom
snimu/rlm-activity-change-waiter
Aug 29, 2026
Merged

refactor(coding-agent): wake RLM quiescence on activity changes instead of polling#1859
xeophon merged 6 commits into
mainfrom
snimu/rlm-activity-change-waiter

Conversation

@snimu

@snimu snimu commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Note: this PR also contains the full change from #1844 (serialized post-compaction continuation: one settlement owner, commit-fence re-checks, and paused-continuation preservation replacing the racy fixed delay) - that PR is closed and merges here as one unit.

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-delay setTimeout(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 isSessionActive component 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.

waitForRlmQuiescence no longer spins on setTimeout(0) when session-level work is active; it blocks on a new _waitForSessionActivityChange hook that shares the existing checkpoint waiter set. _notifySessionInputCheckpointChange is 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. _runScheduledPostCompactionContinue runs immediately and loops until it can safely call agent.continue(), explicitly waiting for agent idle, retries, refine, an in-flight _compactionOperation, queued-work pauses, and session-input pump idle. A continueAfterSessionInput flag on the continuation settlement controls whether an empty “resume after compaction” should still call continue() once queued session input has drained.

waitForIdle / checkpoint waiting now register and clean up waiters correctly and can Promise.race against 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

  • Introduces AgentSession._waitForSessionActivityChange as a one-shot waiter that resolves on session activity or abort, replacing setTimeout(0) busy-loop yielding in waitForRlmQuiescence
  • Removes fixed-delay timer scheduling from post-compaction continuations; _schedulePostCompactionContinue now launches _runScheduledPostCompactionContinue directly, which loops waiting for idle, retry, refine, queued-work, and compaction completion
  • Adds continueAfterSessionInput flag to PostCompactionContinuationSettlement so continuations can optionally wait for pending session input before calling agent.continue()
  • Notifies session-input checkpoint waiters from runUserBash, refine-application paths, branch summary, retry resolution, and compaction completion so quiescence waiters wake promptly
  • Risk: waitForRlmQuiescence no longer polls on a macrotask interval; any missed _notifySessionInputCheckpointChange call site will cause quiescence to hang until the next activity notification. Reviewers should verify all session-input-producing paths in AgentSession notify checkpoint waiters in their finally blocks.

Macroscope summarized 5bcedd4.

Note: trivial textual overlap with #1844 (both add cleanup lines to the same finally block in _runAutoCompaction; both lines should survive). Whichever PR merges second will be refreshed against main.

snimu added 3 commits August 27, 2026 14:04
…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).
snimu added 2 commits August 27, 2026 15:38
…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

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ 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.

Comment thread packages/coding-agent/src/core/agent-session.ts
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
xeophon merged commit dab03c0 into main Aug 29, 2026
24 checks passed
@xeophon
xeophon deleted the snimu/rlm-activity-change-waiter branch August 29, 2026 08:54
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants