The reaper sits OUTSIDE the LCM's per-session serial loop. On every tick it:
1. Fires lcm.TickEscalations(now) — the duration-based escalation heartbeat
a non-polling LCM cannot wake itself to drive.
2. Asks lcm.RunningSessions for the snapshot of sessions whose runtime axis is
alive, then calls runtime.IsAlive(handle) per session via a RuntimeRegistry
that dispatches by RuntimeHandle.RuntimeName (so a single reaper covers
tmux + zellij side by side).
3. Reports any non-alive result back as a fact via ApplyRuntimeObservation —
dead -> RuntimeProbeDead, probe error -> RuntimeProbeFailed (never
collapsed to alive: failed probe ≠ dead, but it ≠ alive either). Steady-
state alive is skipped so we don't churn the LCM with no-op load/diff work.
The reaper REPORTS facts; the LCM owns DECIDE (anti-flap Detecting quarantine,
terminal-session rules). The reaper never writes.
Open-question resolution: add RunningSessions(ctx) to ports.LifecycleManager
(option a). The Manager implements it via an injectable session lister
(Manager.WithSessionLister) so the LCM itself does not require a new
LifecycleStore method — Tom's store contract is untouched, daemon wiring (lane
#10) will inject the production lister at startup.
Scope: reaper goroutine + the minimum LCM seam. No activity ingest, no FS
watcher, no daemon wiring, no new schema fields, no store changes.