Skip to content

fix(coding-agent): cancel RLM subtrees through one iterative visited walk - #2027

Merged
sethkarten merged 3 commits into
mainfrom
fix/rlm-cancel-subtree-iterator
Sep 4, 2026
Merged

fix(coding-agent): cancel RLM subtrees through one iterative visited walk#2027
sethkarten merged 3 commits into
mainfrom
fix/rlm-cancel-subtree-iterator

Conversation

@snimu

@snimu snimu commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

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 _activeRlmChildRuns while 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.

  • cancelRunningRlmDescendants is 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.
  • The three other walkers that shared the hazard now use the same iterator: hasRunningRlmChildren (the hottest — it runs on every roster flush and destructive-action gate), getRlmChildSession's search, and deleteInactiveRlmSubagent's not-found fallback. Exactly one subtree enumeration remains in the session.
  • Untouched: the teardown cascade (abort() → local active-run cancellation, deliberately local dispose semantics), the destructive-action gates, and the idempotent _cancelRlmChildRun primitive.

Validation

  • New pin 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 most levels + 1 times (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.
  • The same fixture also bounds hasRunningRlmChildren's run-map iterations (O(nodes)) and checks the boolean flips to false after the cancel.
  • Existing pins unchanged and green: running-work-under-a-settled-descendant is stopped by one cancel press; live-target cancel descends into retained work; truthful return flag throughout. agent-session-recursion 114 tests, plus agents-view, daemon-mode, daemon-session-list, agent-connection-in-process, compaction suites — 478 passed; root npm run check green.

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

  • Adds _rlmSubtreeSessions, an iterative depth-first generator that follows both active-run and retained-child links while tracking visited sessions to emit each once
  • Refactors deleteInactiveRlmSubagent, hasRunningRlmChildren, getRlmChildSession, cancelRunningRlmDescendants, and cancelRlmChildRun to iterate over this shared generator instead of recursing separately through active and retained child maps
  • Fixes exponential re-walking of sessions that appear in both child maps, and fixes cancelRlmChildRun continuing past a finished retained match to cancel a live colliding sibling ID elsewhere in the subtree
  • Behavioral Change: cancelRlmChildRun now 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 parent

Macroscope 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 _activeRlmChildRuns and _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 + visited set) is the single subtree enumeration. cancelRunningRlmDescendants, cancelRlmChildRun, hasRunningRlmChildren, getRlmChildSession, and deleteInactiveRlmSubagent now use it instead of nested recursion. cancelRlmChildRun still 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.

…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
Comment thread packages/coding-agent/src/core/agent-session.ts Outdated
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
sethkarten self-requested a review September 3, 2026 22:17
@sethkarten
sethkarten merged commit 87fa499 into main Sep 4, 2026
24 checks passed
@sethkarten
sethkarten deleted the fix/rlm-cancel-subtree-iterator branch September 4, 2026 04:18
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