fix(coding-agent): cancel RLM subtrees through one iterative visited walk - #2027
Merged
Conversation
…walk A finished RLM child lives in BOTH parent maps until passivation: its run stays in _activeRlmChildRuns while its session is retained in _rlmChildSessions. The cancel walk descended through both recursively, so every done intermediate doubled the traversal - 2^k subtree walks on a chain of finished intermediates, enough to freeze the worker around depth 25 - and the recursion was depth-bound besides. One private iterative subtree iterator now owns the traversal: explicit stack, visited set, per node the union of active-run sessions and retained sessions, each session yielded once. cancelRunningRlmDescendants is a flat loop cancelling local runs at each visited session (two recursive loops deleted); cancelRlmChildRun's miss path walks the same iterator checking both maps per session (three recursive search constructs deleted). The teardown cascade, hasLiveWork gates, and the idempotent _cancelRlmChildRun primitive are untouched. RES-1265
hasRunningRlmChildren shared the cancel walk's exponential dual- membership hazard - and runs far more often, on every roster flush and destructive-action gate. It, getRlmChildSession's search, and deleteInactiveRlmSubagent's not-found fallback now all walk _rlmSubtreeSessions(), leaving exactly one subtree enumeration in the session. The counting pin also bounds hasRunningRlmChildren's map iterations on the same 20-level dual-membership chain and checks the boolean flips after the cancel. Changelog fragment added; comments cut to invariant one-liners. RES-1265
Child node ids are only mkdir-unique among siblings (sub- plus eight random hex chars), so the same id can exist in two subtrees. The iterative rewrite returned early when a matched run or retained child had nothing to cancel, making a colliding live run elsewhere unreachable; the recursive code it replaced fell through and kept searching. A fruitless match now continues the walk - same false result when the id is truly finished, correct cancellation when it collides. RES-1265
sethkarten
self-requested a review
September 3, 2026 22:17
sethkarten
approved these changes
Sep 4, 2026
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.
Cancelling an RLM subtree that contains resident finished intermediates re-walks whole subtrees exponentially. A finished child lives in BOTH of its parent's tracking maps until passivation — its run stays in
_activeRlmChildRunswhile its session is retained in_rlmChildSessions— and the cancel walk descended through both recursively, so every done intermediate doubled the traversal: 2^k subtree walks on a chain of k finished intermediates. Around depth 25–30 a single cancel press freezes the worker; the recursion was also depth-bound. The cancel primitive is idempotent, so the blowup burned time rather than corrupting state — but the time is what the user feels.Fix
One private iterative iterator now owns the subtree traversal (
_rlmSubtreeSessions): explicit stack,visited: Set<AgentSession>, per node the union of active-run sessions and retained sessions, each session yielded exactly once.cancelRunningRlmDescendantsis a flat loop over the iterator, cancelling local running/queued runs at each visited session — the two recursive descent loops are deleted.cancelRlmChildRun's miss path walks the same iterator, checking each session's two maps for the target id and applying the existing local-hit logic in that session's context — the three recursive search constructs are deleted.hasRunningRlmChildren(the hottest — it runs on every roster flush and destructive-action gate),getRlmChildSession's search, anddeleteInactiveRlmSubagent's not-found fallback. Exactly one subtree enumeration remains in the session.abort()→ local active-run cancellation, deliberately local dispose semantics), the destructive-action gates, and the idempotent_cancelRlmChildRunprimitive.Validation
cancels a deep dual-membership chain in one visit per session: a 20-level chain of real sessions, each finished intermediate present in BOTH parent maps, plus a running leaf. Asserts the leaf ends cancelled with its abort invoked AND the cancel primitive is invoked at mostlevels + 1times (one visit per session). Mutant checks: removing the visited set makes the pin fail with 2,097,151 invocations (= 2²¹−1) vs the bound of 21 — the exact exponential; removing the descend makes it fail with the leaf still running.hasRunningRlmChildren's run-map iterations (O(nodes)) and checks the boolean flips to false after the cancel.npm run checkgreen.Net src LOC
+72/−93 in
agent-session.ts(one ~18-line iterator replaces nine recursive constructs across four walkers) plus a one-line changelog fragment; pure traversal restructuring, no behavioral surface change beyond the complexity fix.Linear: RES-1265 https://linear.app/primeintellect/issue/RES-1265
Note
Replace recursive RLM subtree traversals with iterative visited walk in
AgentSession_rlmSubtreeSessions, an iterative depth-first generator that follows both active-run and retained-child links while tracking visited sessions to emit each oncedeleteInactiveRlmSubagent,hasRunningRlmChildren,getRlmChildSession,cancelRunningRlmDescendants, andcancelRlmChildRunto iterate over this shared generator instead of recursing separately through active and retained child mapscancelRlmChildRuncontinuing past a finished retained match to cancel a live colliding sibling ID elsewhere in the subtreecancelRlmChildRunnow searches all subtree sessions for a matching run rather than stopping at the first match; a finished retained child with the target ID no longer blocks cancellation of a running child with the same ID in another parentMacroscope summarized 74c8b0a.
Note
Medium Risk
Changes core RLM cancel, running-state checks, and subagent deletion traversal; mistakes could miss descendants or cancel the wrong owner, though behavior is heavily regression-tested.
Overview
Fixes worker freezes when stopping or deleting agents whose RLM tree has finished intermediate subagents. Those sessions were reachable from both
_activeRlmChildRunsand_rlmChildSessions, so recursive cancel/lookup walks re-entered the same subtrees exponentially (roughly 2^depth) instead of once per session.A new
_rlmSubtreeSessions()iterator (stack +visitedset) is the single subtree enumeration.cancelRunningRlmDescendants,cancelRlmChildRun,hasRunningRlmChildren,getRlmChildSession, anddeleteInactiveRlmSubagentnow use it instead of nested recursion.cancelRlmChildRunstill walks past a retained-only id match so colliding sibling child ids can reach a live run elsewhere; delete/cancel logic runs in the owning session’s context when a match is found.Regression tests cover colliding ids and a 20-level dual-membership chain with bounded cancel/iteration counts.
Reviewed by Cursor Bugbot for commit 74c8b0a. Bugbot is set up for automated code reviews on this repo. Configure here.