You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Plugin cannot release a stopped session: host heap stays at 70–85% until a full restart
#11855
On a long-running host (macOS desktop app, v0.2.1-rc.5, node v24.21.0) the V8 old space climbs with every session that does real work — reading files, shells, browser — and never comes back down when those sessions stop. After a few hours of normal use the composer memory ring sits at 70–85 % of the 8 GB cap (latest sample: heap ≈ 6.3 GB / 8384 MB, peak 6.4 GB); at a fresh start it is ~0.6 GB, and at that point nothing is running. The only way to get it back today is restarting the host, which kills in-flight turns and costs ~20 s.
Idle sessions are the bulk of it: their event log, tool output caches and message history stay reachable long after their last turn.
Why a plugin cannot fix this
The session's object graph is owned by whoever created it — ctx.effect(...) in dsh-session / dsh-agent — and only that creator holds the disposer. From a plugin I can watch the pressure (that's what my guard does), but nothing I can call actually releases a session:
sessionController.archiveSession() — bookkeeping only, state stays resident
workspaceRegistry.stopSessionActivity() — same
gateway close() / closeSession() and the ACP equivalent — same
Forcing a GC is not a workaround either: the shipped runtime does not run with --expose-gc, and a full GC cannot collect sessions that are still reachable; V8 also does not hand committed pages back promptly, so the number would move a little and come straight back.
Proposal
Release a stopped session's state. Something like await sessionController.release(sessionId)
that drops the agent loop, event log and caches for a session that is (a) not in a running turn and (b) has no attached view / subscriber — idempotent, and refuses with a reason otherwise instead of corrupting a live session. Optional { keepHistory: true } if the on-disk log should stay for later resume (it must stay resumable from disk either way).
Per-session attribution, even rough. Retained bytes per session (event log + attachments + known caches) exposed read-only. Right now the UI has one opaque number; with attribution a panel can say which sessions hold the memory, which is what decides whether releasing is worth it.
With those two, a plugin can offer a real "release idle sessions" button: the ring actually drops, no restart, in-flight work untouched.
Context
I maintain dsh-lifeguard, a crash-pressure guard plugin: it mirrors heap vs. the crash wall, refuses single steps that would cross it, and keeps crash forensics (32 incidents recorded so far, heap sampled every 60 s). Everything above comes from that sampler on this machine. Happy to test against a pre-release build, or to share the measurement data.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
What happens
On a long-running host (macOS desktop app, v0.2.1-rc.5, node v24.21.0) the V8 old space climbs with every session that does real work — reading files, shells, browser — and never comes back down when those sessions stop. After a few hours of normal use the composer memory ring sits at 70–85 % of the 8 GB cap (latest sample: heap ≈ 6.3 GB / 8384 MB, peak 6.4 GB); at a fresh start it is ~0.6 GB, and at that point nothing is running. The only way to get it back today is restarting the host, which kills in-flight turns and costs ~20 s.
Idle sessions are the bulk of it: their event log, tool output caches and message history stay reachable long after their last turn.
Why a plugin cannot fix this
The session's object graph is owned by whoever created it —
ctx.effect(...)indsh-session/dsh-agent— and only that creator holds the disposer. From a plugin I can watch the pressure (that's what my guard does), but nothing I can call actually releases a session:sessionController.archiveSession()— bookkeeping only, state stays residentworkspaceRegistry.stopSessionActivity()— sameclose()/closeSession()and the ACP equivalent — sameForcing a GC is not a workaround either: the shipped runtime does not run with
--expose-gc, and a full GC cannot collect sessions that are still reachable; V8 also does not hand committed pages back promptly, so the number would move a little and come straight back.Proposal
await sessionController.release(sessionId)that drops the agent loop, event log and caches for a session that is (a) not in a running turn and (b) has no attached view / subscriber — idempotent, and refuses with a reason otherwise instead of corrupting a live session. Optional
{ keepHistory: true }if the on-disk log should stay for later resume (it must stay resumable from disk either way).With those two, a plugin can offer a real "release idle sessions" button: the ring actually drops, no restart, in-flight work untouched.
Context
I maintain
dsh-lifeguard, a crash-pressure guard plugin: it mirrors heap vs. the crash wall, refuses single steps that would cross it, and keeps crash forensics (32 incidents recorded so far, heap sampled every 60 s). Everything above comes from that sampler on this machine. Happy to test against a pre-release build, or to share the measurement data.— regrevia
All reactions