code-yeongyu / code-yeongyu/senpi
bug(codemode): eval cells, tool-call queue, and display buffers are retained for the session lifetime (unbounded memory growth)
- Dominant language
- TypeScript
- Stars
- 429
- Forks
- 98
- Avg merge
- 5h 3m
- Merged PRs (30d)
- 526
Description
## Summary
Three unbounded-retention defects in packages/senpi-codemode make every long-lived session's JS heap grow without limit:
1. src/tool/detached-cell-manager.ts — #cells entries are never removed once a cell settles. The only #cells.delete is at line 84 (same-id reuse), and dispose() (lines 186-194) stops detached cells but never clears the map. Every eval cell's input, live result, terminal result, and onKill closure (capturing the whole CellExecution) is retained for the session lifetime.
2. src/kernels/js/context-manager.ts:216 — every tool-call kernel message is pushed to #pendingToolCalls when no waiter is registered. The only consumer is nextToolCall() (line 83), which has no caller on the normal production path (git grep: only tests and the subprocess-kernel wrapper). Full tool arguments (write/edit bodies, task prompts) accumulate forever.
3. src/tool/image.ts — #displayImages (line 95) and the JSON display list grow without any byte or count cap; compaction accounting (compaction.ts:408-411) counts message content but not these details, so compaction never relieves the heap.
## Context
Root-caused during a profiling investigation of a long-lived macOS host (M4 Pro, 64 GB) running ~28 concurrent senpi/omo sessions for several days. Symptoms: load average ~150 with only ~50% CPU utilization (threads blocked on swap I/O), 20 GB swap in use, idle sessions each burning 2-8% CPU sustained, and idle session heaps growing to multiple GB (one 4-day-idle session: 648 MB resident JSC heap + 1.4 GB swapped, 3.3 GB peak footprint). All file:line references are against main (b19491745). Raw profiling evidence held locally.
## Expected (ideal state)
Settled cells are removed from the registry (terminal snapshots remain available via the notification/artifact path, or a small bounded LRU); the tool-call fallback queue is bounded or absent on paths that cannot consume it; display buffers have byte/count caps with oldest-first eviction and are included in compaction accounting.
## Acceptance criteria
- Unit tests prove: after N settled eval cells the registry size stays bounded; the tool-call queue does not grow on the normal path; display buffers evict beyond the cap.
- A soak running repeated eval cells shows heap delta plateau instead of linear growth.
## Related
- #1461 (compaction/session memory growth — same theme, different surface)
Contributor guide
Research direction
Read packages/senpi-codemode/src/tool/detached-cell-manager.ts, src/kernels/js/context-manager.ts, src/tool/image.ts, and the compaction accounting at compaction.ts:408-411. Add unit tests for bounded settled-cell registries, the normal tool-call path, and display-buffer eviction, then run a repeated-evaluation soak to confirm heap growth plateaus.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- performance, tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100