# Idle language-server reaper class — plan

Status: ACTIVE (owner-approved 2026-08-14; implementing)
Source request (owner, 2026-08-14): "identify memory leaks and recommend what to do" → after live diagnosis (RAM 53/61G, swap 48/77G, load 75) owner said "i do need the sessions even when idle sometimes, but not the lsp" and "dont we already have a reaper in overdeck? enhance existing instead of building a new one".

## Problem (measured 2026-08-14)

- 36 `tsserver.js` (13.2 GB) + intelephense (3 GB) = ~16 GB resident, one LSP chain per agent session, accumulating because the sessions stay alive for days.
- Existing reaper (`agent-reaper.py`, 5 candidate classes) cannot see them: LSP cmdlines are not agent-class, and live sessions make them non-orphans — so `is_agent_class`/`evaluate_test_orphan`/`evaluate` never classify them.
- Root contribution to swap thrash (load 75, 48 GB swap in use at peak).

## Design — class 6 "idle language server" in the existing reaper

Modeled on the orphaned-sidecar class (silent auto-reap, journaled, `REAPER_LSP` flag, gated by the `ENFORCE` master switch):

- Candidates: cmdline tokens `tsserver.js | typescript-language-server | intelephense` (extendable via `REAPER_LSP_TOKENS`).
- Owner walk (`lsp_owner_kind`): ancestor chain classified:
  - agent session (claude/cursor-agent/opencode/codex) → reapable when idle (owner pid exempts the invoking reaper session — never reaps its own tree);
  - interactive (`tty_nr != 0`) or GUI editor client (code/codium/cursor/zed/sublime/electron/IDE family) → NEVER reaped;
  - chain reaches init/user-systemd (session dead, LSP orphaned) → reapable when idle;
  - unresolvable → fail-safe NOT_CANDIDATE.
- Idle = process age ≥ `REAPER_LSP_IDLE_MIN_S` (default 10 min) AND last `CPU_SAMPLES_REQUIRED` history samples ≤ `REAPER_LSP_IDLE_CPU_MAX` (default 5%).
- Enforcement: `run_reap_lsps` under `REAPER_LSP=1` (default) + `ENFORCE=1`; kill via existing `reap_tree` (SIGTERM → SIGKILL), journaled silently like sidecars (LSP respawns on demand; no decision for the owner).
- Live ENFORCE=0 drift: `~/.claude/run/agent-reaper.log` proves `auto_unblock`/`reaped` actions ran under ENFORCE=1 through Aug 6-7; unit now carries `Environment=ENFORCE=0` (checked-in default) — restoring `ENFORCE=1` in the unit as part of this change (evidence-backed, activates the previously-live silent classes + the new LSP class).

## Acceptance criteria

1. `agent-reaper.py --self-test` green with new IDLE_LSP cases (idle-agent-owned reaped; busy/young/human-owned/editor-owned/exempt never).
2. `tests/agent-reaper-enforce.test.py` green; asserts ENFORCE=0 (unset/0/nonnumeric) signals nothing incl. new path; ENFORCE=1 reaches it; `REAPER_LSP=0` suppresses only the LSP path.
3. Deployed live (`packaging/deploy-local.sh`); installed `~/.claude/bin/agent-reaper.py` entrypoint proven with a planted fake idle LSP (age/CPU overrides, short run): reaped + journaled; busy/editor-owned fixtures untouched.
4. Unit flip: installed `agent-reaper.service` carries `ENFORCE=1`; timer active.
5. Landed on main; receipt + INDEX updated.

## Files

- `modules/workstation/claude/bin/_agent_reaper_lib.py` — consts, `is_lsp`, `lsp_owner_kind`, `list_lsp_candidate_pids`, `evaluate_idle_lsp`, `verify_lsp_before_kill`.
- `modules/workstation/claude/bin/agent-reaper.py` — class 6 in docstring + scan + `run_reap_lsps` + self-test.
- `modules/workstation/claude/tests/agent-reaper-enforce.test.py` — new verdict + signal-order expectations + `REAPER_LSP=0` case.
- `modules/workstation/systemd/user/agent-reaper.service` — `ENFORCE=1`.

## Constraints / safety

- Never kill by tty/editor evidence, never touch sessions (user requirement), never signal without ENFORCE=1 (test-enforced), never reap the invoking session's own LSP, unknown owner → skip. LSPs are caches: respawn on demand (~1-2 s), no state loss.
- No change to classes 1-5 logic; class 6 is additive. ENFORCE flip re-enables previously-live behavior (proven in log).

## Execution + receipt

- [x] Diagnose (13-16 GB LSP bloat; reaper blind to it; ENFORCE drift evidenced).
- [ ] Worktree claimed; implementation; self-test + enforce test green in worktree.
- [ ] Deploy live; planted-LSP proof via installed entrypoint.
- [ ] Land (ship.sh), update receipt + INDEX.

Next executable action: claim worktree, read lib lines 295-373 (list_candidate_pids/take_live_snapshot/update_history), implement class 6.
