Skip to content

feat(coding-agent): whole-tree idle eviction - #588

Merged
snimu merged 6 commits into
feat/inspector-deletionfrom
feat/whole-tree-eviction
Aug 5, 2026
Merged

snimu merged 6 commits into
feat/inspector-deletionfrom
feat/whole-tree-eviction

Conversation

@snimu

@snimu snimu commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Worker processes used to stay alive forever, even when every session in them had been idle for hours. This PR makes the supervisor notice and stop them.

A background sweep checks each worker. If every session in it has been idle past a threshold, and nothing is attached, and no timers (heartbeats/cron jobs) are pinned to it, the worker process is stopped. Nothing is lost: the sessions become ordinary inactive entries, and attaching to one — or sending it a message — starts a fresh worker and picks up where it left off.

The threshold is a new idleEvictionMinutes setting (default 90; off disables it), which you can change in /settings.

The check errs on the side of caution: if a worker's state is at all unclear (busy session, running subagent, missing timestamps, mid-startup or mid-shutdown), it's left alone.


This is part 1–7 of a stack (each PR builds on the one before it):

  1. feat(coding-agent): lazy child session hydration #583 — don't load finished subagents until someone needs them
  2. feat(coding-agent): persist parent edges and derived depth for all sessions #584 — record each session's parent and depth on disk
  3. feat(coding-agent): /rlm-max-depth session command #585 — new /rlm-max-depth command to control how deep agents can nest
  4. feat(coding-agent): recursive scoped agents view #586 — browse the agent tree in the agents view
  5. feat(coding-agent): delete child-agent inspector; one subagent summary line #587 — replace the child-agent inspector with one summary line
  6. feat(coding-agent): whole-tree idle eviction #588 — shut down worker processes whose sessions have all gone idle
  7. feat(coding-agent): intra-tree child session passivation #589 — quietly unload idle child sessions inside a running worker

Tests: the full suite at the top of the stack fails only on tests that already fail on the base branch. Each branch in the stack also passes checks and its own tests independently.

Note

Add whole-tree idle eviction to the coding agent daemon supervisor

  • Adds canEvictWorker in session-action-store.ts to evaluate eviction eligibility: a worker is evictable only when ready, connected, unowned, not updating, and all sessions have been idle beyond the configured threshold with no attachments, heartbeats, or cron jobs.
  • Adds idleEvictionMinutes to SettingsManager (default 90 minutes; 'off' disables eviction) and exposes it in the interactive settings UI as an 'Idle worker eviction' option.
  • The supervisor periodically runs runIdleEvictionSweep, which reloads settings, evaluates candidates via canEvictWorker, and stops eligible workers. Sweep interval is bounded between 1 and 5 minutes.
  • Adds mutation fencing in the supervisor so mutating commands wait for any in-flight eviction sweep to complete before proceeding.
  • Extends SessionSummary with hasRegisteredHeartbeat, hasRegisteredCronJob, and lastActivityAt fields (protocol schema bumped to revision 12).
  • Agent messages targeting a selector not locally resident are now forwarded to the supervisor, which resolves the saved session via the catalog, wakes the worker if needed, and delivers the message.
  • Risk: existing clients consuming session list responses must handle the new schema revision 12 fields.

Macroscope summarized b184c50.


Note

Medium Risk
Changes supervisor worker lifecycle, session protocol schema, and cross-worker messaging wake paths; mistakes could stop workers prematurely or mishandle a2a delivery, though policy is conservative and heavily tested.

Overview
Introduces whole-tree idle worker eviction: a global idleEvictionMinutes setting (default 90, off to disable) in settings and /settings, plus pure canEvictWorker policy that only stops a worker when every session is past the threshold with no attachments, active heartbeats, or registered cron jobs.

The daemon supervisor runs periodic sweeps (interval derived from the threshold), reloads settings each pass, fences mutating commands during eviction, and stops eligible workers while logging evictions. Session list rows gain hasRegisteredHeartbeat, hasRegisteredCronJob, and lastActivityAt (from message timestamps); daemon schema revision 12 documents these fields. Paused heartbeats no longer pin residency.

Agent-to-agent messaging improves for non-resident targets: the supervisor can catalog-resolve a selector (including exact names, with ambiguity errors when a name collides with an id prefix), wake a saved session through the existing create path, and deliver the message; workers validate messages before remote fallback and do not retry supervisor rejections.

Also refreshes generated model catalog entries (pricing/limits and a few provider models).

Reviewed by Cursor Bugbot for commit b184c50. Bugbot is set up for automated code reviews on this repo. Configure here.

Comment thread packages/coding-agent/src/modes/daemon/daemon-mode.ts
Comment thread packages/coding-agent/src/modes/daemon/daemon-supervisor.ts Outdated
Comment thread packages/coding-agent/src/modes/daemon/daemon-session-list.ts
snimu added a commit that referenced this pull request Aug 3, 2026
…eep snapshot atomicity

- Finding 22: async snapshot creation exposed clients before attach initialization; publish client attachment only after the snapshot resolves. origin: PR #587
- Finding 23: catch-up validity was checked only before async snapshot creation; revalidate session and client attachment after the await. origin: PR #587
- Finding 25: deterministic ambiguous selectors entered the 30-second remote-send retry loop; fail fast for Ambiguous errors. origin: PR #588
- Finding 28: active rows ignored stable file-keyed schedule pins during active-id rebinding; consult both id- and file-keyed heartbeat/cron sets. origin: PR #588
- Finding 29: passivation sampled activity before an asynchronous passive-registry walk; complete the walk before atomically sampling summary and admission markers. origin: PR #589
snimu added a commit that referenced this pull request Aug 3, 2026
…n, summary refresh, pulse, and settings cycling

- Finding 18: stale saved-catalog readiness could destructively drop scope during refresh; reset readiness when refresh starts. origin: PR #586
- Finding 19: resolved scope roots were lost across view remounts, swallowing early Left navigation; persist and restore the root summary. origin: PR #586
- Finding 20: post-attach crashes left backSession pointing at the prior session; assign the opened summary before interactive mode runs. origin: PR #586
- Findings 21 and 24: startup seeding could leave subagent counts stale and background subagents drove an unused render pulse; refresh every seed and pulse only while streaming. origin: PR #587
- Finding 27: custom idle-eviction values cycled directly to off; include custom numeric values in the sorted cycle. origin: PR #588
snimu added a commit that referenced this pull request Aug 3, 2026
…eep snapshot atomicity

- Finding 22: async snapshot creation exposed clients before attach initialization; publish client attachment only after the snapshot resolves. origin: PR #587
- Finding 23: catch-up validity was checked only before async snapshot creation; revalidate session and client attachment after the await. origin: PR #587
- Finding 25: deterministic ambiguous selectors entered the 30-second remote-send retry loop; fail fast for Ambiguous errors. origin: PR #588
- Finding 28: active rows ignored stable file-keyed schedule pins during active-id rebinding; consult both id- and file-keyed heartbeat/cron sets. origin: PR #588
- Finding 29: passivation sampled activity before an asynchronous passive-registry walk; complete the walk before atomically sampling summary and admission markers. origin: PR #589
@snimu
snimu force-pushed the feat/whole-tree-eviction branch from aa98c9a to 19a2be1 Compare August 3, 2026 12:05
snimu added a commit that referenced this pull request Aug 3, 2026
…n, summary refresh, pulse, and settings cycling

- Finding 18: stale saved-catalog readiness could destructively drop scope during refresh; reset readiness when refresh starts. origin: PR #586
- Finding 19: resolved scope roots were lost across view remounts, swallowing early Left navigation; persist and restore the root summary. origin: PR #586
- Finding 20: post-attach crashes left backSession pointing at the prior session; assign the opened summary before interactive mode runs. origin: PR #586
- Findings 21 and 24: startup seeding could leave subagent counts stale and background subagents drove an unused render pulse; refresh every seed and pulse only while streaming. origin: PR #587
- Finding 27: custom idle-eviction values cycled directly to off; include custom numeric values in the sorted cycle. origin: PR #588

@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 2 potential issues.

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 19a2be1. Configure here.

Comment thread packages/coding-agent/src/modes/daemon/daemon-mode.ts
@snimu
snimu force-pushed the feat/whole-tree-eviction branch from 19a2be1 to 4a062fa Compare August 4, 2026 00:44
snimu added a commit that referenced this pull request Aug 4, 2026
…eep snapshot atomicity

- Finding 22: async snapshot creation exposed clients before attach initialization; publish client attachment only after the snapshot resolves. origin: PR #587
- Finding 23: catch-up validity was checked only before async snapshot creation; revalidate session and client attachment after the await. origin: PR #587
- Finding 25: deterministic ambiguous selectors entered the 30-second remote-send retry loop; fail fast for Ambiguous errors. origin: PR #588
- Finding 28: active rows ignored stable file-keyed schedule pins during active-id rebinding; consult both id- and file-keyed heartbeat/cron sets. origin: PR #588
- Finding 29: passivation sampled activity before an asynchronous passive-registry walk; complete the walk before atomically sampling summary and admission markers. origin: PR #589
snimu added a commit that referenced this pull request Aug 4, 2026
…n, summary refresh, pulse, and settings cycling

- Finding 18: stale saved-catalog readiness could destructively drop scope during refresh; reset readiness when refresh starts. origin: PR #586
- Finding 19: resolved scope roots were lost across view remounts, swallowing early Left navigation; persist and restore the root summary. origin: PR #586
- Finding 20: post-attach crashes left backSession pointing at the prior session; assign the opened summary before interactive mode runs. origin: PR #586
- Findings 21 and 24: startup seeding could leave subagent counts stale and background subagents drove an unused render pulse; refresh every seed and pulse only while streaming. origin: PR #587
- Finding 27: custom idle-eviction values cycled directly to off; include custom numeric values in the sorted cycle. origin: PR #588
Comment thread packages/coding-agent/src/modes/daemon/daemon-supervisor.ts Outdated
@snimu
snimu force-pushed the feat/whole-tree-eviction branch from 4a062fa to cc8b7d0 Compare August 4, 2026 06:10
snimu added a commit that referenced this pull request Aug 4, 2026
…eep snapshot atomicity

- Finding 22: async snapshot creation exposed clients before attach initialization; publish client attachment only after the snapshot resolves. origin: PR #587
- Finding 23: catch-up validity was checked only before async snapshot creation; revalidate session and client attachment after the await. origin: PR #587
- Finding 25: deterministic ambiguous selectors entered the 30-second remote-send retry loop; fail fast for Ambiguous errors. origin: PR #588
- Finding 28: active rows ignored stable file-keyed schedule pins during active-id rebinding; consult both id- and file-keyed heartbeat/cron sets. origin: PR #588
- Finding 29: passivation sampled activity before an asynchronous passive-registry walk; complete the walk before atomically sampling summary and admission markers. origin: PR #589
snimu added a commit that referenced this pull request Aug 4, 2026
…n, summary refresh, pulse, and settings cycling

- Finding 18: stale saved-catalog readiness could destructively drop scope during refresh; reset readiness when refresh starts. origin: PR #586
- Finding 19: resolved scope roots were lost across view remounts, swallowing early Left navigation; persist and restore the root summary. origin: PR #586
- Finding 20: post-attach crashes left backSession pointing at the prior session; assign the opened summary before interactive mode runs. origin: PR #586
- Findings 21 and 24: startup seeding could leave subagent counts stale and background subagents drove an unused render pulse; refresh every seed and pulse only while streaming. origin: PR #587
- Finding 27: custom idle-eviction values cycled directly to off; include custom numeric values in the sorted cycle. origin: PR #588
@snimu
snimu force-pushed the feat/whole-tree-eviction branch from cc8b7d0 to 262aabc Compare August 4, 2026 07:42
snimu added a commit that referenced this pull request Aug 4, 2026
…eep snapshot atomicity

- Finding 22: async snapshot creation exposed clients before attach initialization; publish client attachment only after the snapshot resolves. origin: PR #587
- Finding 23: catch-up validity was checked only before async snapshot creation; revalidate session and client attachment after the await. origin: PR #587
- Finding 25: deterministic ambiguous selectors entered the 30-second remote-send retry loop; fail fast for Ambiguous errors. origin: PR #588
- Finding 28: active rows ignored stable file-keyed schedule pins during active-id rebinding; consult both id- and file-keyed heartbeat/cron sets. origin: PR #588
- Finding 29: passivation sampled activity before an asynchronous passive-registry walk; complete the walk before atomically sampling summary and admission markers. origin: PR #589
snimu added a commit that referenced this pull request Aug 4, 2026
…n, summary refresh, pulse, and settings cycling

- Finding 18: stale saved-catalog readiness could destructively drop scope during refresh; reset readiness when refresh starts. origin: PR #586
- Finding 19: resolved scope roots were lost across view remounts, swallowing early Left navigation; persist and restore the root summary. origin: PR #586
- Finding 20: post-attach crashes left backSession pointing at the prior session; assign the opened summary before interactive mode runs. origin: PR #586
- Findings 21 and 24: startup seeding could leave subagent counts stale and background subagents drove an unused render pulse; refresh every seed and pulse only while streaming. origin: PR #587
- Finding 27: custom idle-eviction values cycled directly to off; include custom numeric values in the sorted cycle. origin: PR #588
Comment thread packages/coding-agent/src/modes/daemon/daemon-supervisor.ts
snimu added a commit that referenced this pull request Aug 4, 2026
…eep snapshot atomicity

- Finding 22: async snapshot creation exposed clients before attach initialization; publish client attachment only after the snapshot resolves. origin: PR #587
- Finding 23: catch-up validity was checked only before async snapshot creation; revalidate session and client attachment after the await. origin: PR #587
- Finding 25: deterministic ambiguous selectors entered the 30-second remote-send retry loop; fail fast for Ambiguous errors. origin: PR #588
- Finding 28: active rows ignored stable file-keyed schedule pins during active-id rebinding; consult both id- and file-keyed heartbeat/cron sets. origin: PR #588
- Finding 29: passivation sampled activity before an asynchronous passive-registry walk; complete the walk before atomically sampling summary and admission markers. origin: PR #589
snimu added a commit that referenced this pull request Aug 4, 2026
…n, summary refresh, pulse, and settings cycling

- Finding 18: stale saved-catalog readiness could destructively drop scope during refresh; reset readiness when refresh starts. origin: PR #586
- Finding 19: resolved scope roots were lost across view remounts, swallowing early Left navigation; persist and restore the root summary. origin: PR #586
- Finding 20: post-attach crashes left backSession pointing at the prior session; assign the opened summary before interactive mode runs. origin: PR #586
- Findings 21 and 24: startup seeding could leave subagent counts stale and background subagents drove an unused render pulse; refresh every seed and pulse only while streaming. origin: PR #587
- Finding 27: custom idle-eviction values cycled directly to off; include custom numeric values in the sorted cycle. origin: PR #588
@snimu
snimu force-pushed the feat/whole-tree-eviction branch from 92d8c2a to cfd8ddf Compare August 4, 2026 18:46
snimu added a commit that referenced this pull request Aug 4, 2026
…eep snapshot atomicity

- Finding 22: async snapshot creation exposed clients before attach initialization; publish client attachment only after the snapshot resolves. origin: PR #587
- Finding 23: catch-up validity was checked only before async snapshot creation; revalidate session and client attachment after the await. origin: PR #587
- Finding 25: deterministic ambiguous selectors entered the 30-second remote-send retry loop; fail fast for Ambiguous errors. origin: PR #588
- Finding 28: active rows ignored stable file-keyed schedule pins during active-id rebinding; consult both id- and file-keyed heartbeat/cron sets. origin: PR #588
- Finding 29: passivation sampled activity before an asynchronous passive-registry walk; complete the walk before atomically sampling summary and admission markers. origin: PR #589
snimu added a commit that referenced this pull request Aug 4, 2026
…n, summary refresh, pulse, and settings cycling

- Finding 18: stale saved-catalog readiness could destructively drop scope during refresh; reset readiness when refresh starts. origin: PR #586
- Finding 19: resolved scope roots were lost across view remounts, swallowing early Left navigation; persist and restore the root summary. origin: PR #586
- Finding 20: post-attach crashes left backSession pointing at the prior session; assign the opened summary before interactive mode runs. origin: PR #586
- Findings 21 and 24: startup seeding could leave subagent counts stale and background subagents drove an unused render pulse; refresh every seed and pulse only while streaming. origin: PR #587
- Finding 27: custom idle-eviction values cycled directly to off; include custom numeric values in the sorted cycle. origin: PR #588
@snimu
snimu force-pushed the feat/whole-tree-eviction branch from cfd8ddf to d9241ea Compare August 4, 2026 20:25
snimu added a commit that referenced this pull request Aug 4, 2026
…eep snapshot atomicity

- Finding 22: async snapshot creation exposed clients before attach initialization; publish client attachment only after the snapshot resolves. origin: PR #587
- Finding 23: catch-up validity was checked only before async snapshot creation; revalidate session and client attachment after the await. origin: PR #587
- Finding 25: deterministic ambiguous selectors entered the 30-second remote-send retry loop; fail fast for Ambiguous errors. origin: PR #588
- Finding 28: active rows ignored stable file-keyed schedule pins during active-id rebinding; consult both id- and file-keyed heartbeat/cron sets. origin: PR #588
- Finding 29: passivation sampled activity before an asynchronous passive-registry walk; complete the walk before atomically sampling summary and admission markers. origin: PR #589
snimu added a commit that referenced this pull request Aug 4, 2026
…n, summary refresh, pulse, and settings cycling

- Finding 18: stale saved-catalog readiness could destructively drop scope during refresh; reset readiness when refresh starts. origin: PR #586
- Finding 19: resolved scope roots were lost across view remounts, swallowing early Left navigation; persist and restore the root summary. origin: PR #586
- Finding 20: post-attach crashes left backSession pointing at the prior session; assign the opened summary before interactive mode runs. origin: PR #586
- Findings 21 and 24: startup seeding could leave subagent counts stale and background subagents drove an unused render pulse; refresh every seed and pulse only while streaming. origin: PR #587
- Finding 27: custom idle-eviction values cycled directly to off; include custom numeric values in the sorted cycle. origin: PR #588
@snimu
snimu force-pushed the feat/whole-tree-eviction branch from d9241ea to 92ede8d Compare August 4, 2026 20:56
snimu added a commit that referenced this pull request Aug 4, 2026
…eep snapshot atomicity

- Finding 22: async snapshot creation exposed clients before attach initialization; publish client attachment only after the snapshot resolves. origin: PR #587
- Finding 23: catch-up validity was checked only before async snapshot creation; revalidate session and client attachment after the await. origin: PR #587
- Finding 25: deterministic ambiguous selectors entered the 30-second remote-send retry loop; fail fast for Ambiguous errors. origin: PR #588
- Finding 28: active rows ignored stable file-keyed schedule pins during active-id rebinding; consult both id- and file-keyed heartbeat/cron sets. origin: PR #588
- Finding 29: passivation sampled activity before an asynchronous passive-registry walk; complete the walk before atomically sampling summary and admission markers. origin: PR #589
snimu added a commit that referenced this pull request Aug 4, 2026
…n, summary refresh, pulse, and settings cycling

- Finding 18: stale saved-catalog readiness could destructively drop scope during refresh; reset readiness when refresh starts. origin: PR #586
- Finding 19: resolved scope roots were lost across view remounts, swallowing early Left navigation; persist and restore the root summary. origin: PR #586
- Finding 20: post-attach crashes left backSession pointing at the prior session; assign the opened summary before interactive mode runs. origin: PR #586
- Findings 21 and 24: startup seeding could leave subagent counts stale and background subagents drove an unused render pulse; refresh every seed and pulse only while streaming. origin: PR #587
- Finding 27: custom idle-eviction values cycled directly to off; include custom numeric values in the sorted cycle. origin: PR #588
Comment thread packages/coding-agent/src/modes/daemon/daemon-mode.ts
@snimu
snimu force-pushed the feat/whole-tree-eviction branch from 92ede8d to 1231a19 Compare August 4, 2026 22:35
snimu added a commit that referenced this pull request Aug 4, 2026
…eep snapshot atomicity

- Finding 22: async snapshot creation exposed clients before attach initialization; publish client attachment only after the snapshot resolves. origin: PR #587
- Finding 23: catch-up validity was checked only before async snapshot creation; revalidate session and client attachment after the await. origin: PR #587
- Finding 25: deterministic ambiguous selectors entered the 30-second remote-send retry loop; fail fast for Ambiguous errors. origin: PR #588
- Finding 28: active rows ignored stable file-keyed schedule pins during active-id rebinding; consult both id- and file-keyed heartbeat/cron sets. origin: PR #588
- Finding 29: passivation sampled activity before an asynchronous passive-registry walk; complete the walk before atomically sampling summary and admission markers. origin: PR #589
snimu added a commit that referenced this pull request Aug 4, 2026
…n, summary refresh, pulse, and settings cycling

- Finding 18: stale saved-catalog readiness could destructively drop scope during refresh; reset readiness when refresh starts. origin: PR #586
- Finding 19: resolved scope roots were lost across view remounts, swallowing early Left navigation; persist and restore the root summary. origin: PR #586
- Finding 20: post-attach crashes left backSession pointing at the prior session; assign the opened summary before interactive mode runs. origin: PR #586
- Findings 21 and 24: startup seeding could leave subagent counts stale and background subagents drove an unused render pulse; refresh every seed and pulse only while streaming. origin: PR #587
- Finding 27: custom idle-eviction values cycled directly to off; include custom numeric values in the sorted cycle. origin: PR #588
Comment thread packages/coding-agent/src/modes/daemon/daemon-supervisor.ts
snimu added 6 commits August 5, 2026 03:02
Track A PR 6 of the recursive-agent-harness plan (D5, eviction shape 1):

- supervisor idle sweep stops worker processes whose entire session tree
  has been idle past the threshold; evicted trees become ordinary inactive
  rows (non-archive stop; catalog preserved; attach and a2a wake them via
  the existing lazy-hydration paths)
- pure canEvictWorker() predicate (extends the session-action-store policy
  module): every session must be non-busy (isSessionSummaryBusy = active or
  running RLM children), detached, schedule-free, and idle past threshold;
  fail-closed on missing timestamps; rejects starting/recovering/stopping/
  owned/update-preparing workers; eviction never cascades across workers
- per-session residency metadata on summaries: lastActivityAt (max across
  all message types), heartbeat/cron pins (active heartbeats only; passive
  sessions matched by resolved session file); schema rev 10 (additive)
- a2a to an evicted/inactive session resolves through the saved catalog,
  opens a fresh worker, and delivers after binding; ambiguity errors
  propagate distinctly from unknown-target errors
- idleEvictionMinutes setting (default 90, 'off' disables) via /settings;
  supervisor re-reads settings each sweep
- Ignore finite message timestamps outside the JavaScript Date range so one corrupt persisted timestamp cannot abort session-list generation.
- Preserve valid latest-message activity when invalid timestamps are present.
- Narrow passive RLM snapshot rows to resident roots so the branch remains type-safe.
- Forward resolved same-worker agent message targets through the guarded worker delivery command.
- Fail genuinely unknown selectors without returning them to the source worker.
- Validate agent messages before resolving local or remote targets.
- Retry remote delivery only for failures before a supervisor response.
- Cover nonresident validation and deterministic supervisor rejection paths.
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