instar 1.2.82 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/dist/commands/init.js +14 -3
  2. package/dist/commands/init.js.map +1 -1
  3. package/dist/commands/server.d.ts.map +1 -1
  4. package/dist/commands/server.js +143 -1
  5. package/dist/commands/server.js.map +1 -1
  6. package/dist/config/ConfigDefaults.d.ts.map +1 -1
  7. package/dist/config/ConfigDefaults.js +23 -0
  8. package/dist/config/ConfigDefaults.js.map +1 -1
  9. package/dist/core/PostUpdateMigrator.d.ts +2 -1
  10. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  11. package/dist/core/PostUpdateMigrator.js +268 -3
  12. package/dist/core/PostUpdateMigrator.js.map +1 -1
  13. package/dist/core/SessionManager.d.ts +43 -0
  14. package/dist/core/SessionManager.d.ts.map +1 -1
  15. package/dist/core/SessionManager.js +123 -24
  16. package/dist/core/SessionManager.js.map +1 -1
  17. package/dist/core/installCodexHooks.d.ts.map +1 -1
  18. package/dist/core/installCodexHooks.js +3 -2
  19. package/dist/core/installCodexHooks.js.map +1 -1
  20. package/dist/core/types.d.ts +26 -0
  21. package/dist/core/types.d.ts.map +1 -1
  22. package/dist/core/types.js.map +1 -1
  23. package/dist/monitoring/SessionReaper.d.ts +153 -0
  24. package/dist/monitoring/SessionReaper.d.ts.map +1 -0
  25. package/dist/monitoring/SessionReaper.js +376 -0
  26. package/dist/monitoring/SessionReaper.js.map +1 -0
  27. package/dist/monitoring/TokenLedger.d.ts +12 -0
  28. package/dist/monitoring/TokenLedger.d.ts.map +1 -1
  29. package/dist/monitoring/TokenLedger.js +22 -0
  30. package/dist/monitoring/TokenLedger.js.map +1 -1
  31. package/dist/monitoring/transcriptProber.d.ts +44 -0
  32. package/dist/monitoring/transcriptProber.d.ts.map +1 -0
  33. package/dist/monitoring/transcriptProber.js +57 -0
  34. package/dist/monitoring/transcriptProber.js.map +1 -0
  35. package/dist/scaffold/templates.d.ts.map +1 -1
  36. package/dist/scaffold/templates.js +6 -0
  37. package/dist/scaffold/templates.js.map +1 -1
  38. package/dist/server/AgentServer.d.ts +3 -0
  39. package/dist/server/AgentServer.d.ts.map +1 -1
  40. package/dist/server/AgentServer.js +1 -0
  41. package/dist/server/AgentServer.js.map +1 -1
  42. package/dist/server/routes.d.ts +3 -0
  43. package/dist/server/routes.d.ts.map +1 -1
  44. package/dist/server/routes.js +20 -2
  45. package/dist/server/routes.js.map +1 -1
  46. package/dist/server/stopGate.d.ts +8 -2
  47. package/dist/server/stopGate.d.ts.map +1 -1
  48. package/dist/server/stopGate.js +42 -2
  49. package/dist/server/stopGate.js.map +1 -1
  50. package/package.json +1 -1
  51. package/playbook-scripts/build-state.py +39 -1
  52. package/scripts/analyze-release.js +16 -8
  53. package/scripts/generate-builtin-manifest.cjs +2 -1
  54. package/src/data/builtin-manifest.json +76 -67
  55. package/src/scaffold/templates.ts +6 -0
  56. package/src/templates/hooks/build-stop-hook.sh +62 -0
  57. package/src/templates/hooks/settings-template.json +10 -0
  58. package/upgrades/1.2.83.md +26 -0
  59. package/upgrades/1.3.0.md +27 -0
  60. package/upgrades/side-effects/build-stop-hook-session-scoping.md +133 -0
  61. package/upgrades/side-effects/fresh-session-stop-gate-shadow-wiring.md +35 -0
  62. package/upgrades/side-effects/session-reaper.md +42 -0
@@ -135,6 +135,16 @@
135
135
  }
136
136
  ],
137
137
  "Stop": [
138
+ {
139
+ "matcher": "",
140
+ "hooks": [
141
+ {
142
+ "type": "command",
143
+ "command": "node .instar/hooks/instar/stop-gate-router.js",
144
+ "timeout": 5000
145
+ }
146
+ ]
147
+ },
138
148
  {
139
149
  "matcher": "",
140
150
  "hooks": [
@@ -0,0 +1,26 @@
1
+ # Upgrade Guide — NEXT
2
+
3
+ <!-- bump: minor -->
4
+ <!-- Valid values: patch, minor, major -->
5
+ <!-- patch = bug fixes, refactors, test additions, doc updates -->
6
+ <!-- minor = new features, new APIs, new capabilities (backwards-compatible) -->
7
+ <!-- major = breaking changes to existing APIs or behavior -->
8
+
9
+ ## What Changed
10
+
11
+ **SessionReaper — pressure-aware cleanup of idle-but-alive sessions.** A new monitor that reaps sessions sitting idle at a ready prompt (holding memory) — but ONLY when the machine is under memory pressure, and it NEVER reaps a session that might be working. It requires *positive* proof of idleness (turn complete + at a ready prompt + screen byte-static across several checks + no running process + no transcript growth) and KEEPs on any ambiguity. Ships **OFF + dry-run by default** — the only monitor that kills on a heuristic, so it stays dark until an operator validates the dry-run log and opts in. Closes the gap behind the 2026-05-25 fleet pileup (idle sessions accumulated until the machine starved and cross-agent messaging silently failed because agents could no longer spawn).
12
+
13
+ New read-only endpoint `GET /sessions/reaper` shows the live pressure tier and, per session, the verdict + the exact gate that kept it. `SessionManager` gains a single-writer `terminateSession()` so the existing idle-kill and the reaper can never double-kill. The zombie-kill recovery veto now also defers to the socket + silence sentinels.
14
+
15
+ ## What to Tell Your User
16
+
17
+ - **Idle sessions get cleaned up under memory pressure — safely.** When your machine fills up with idle agent sessions, this sweeps them so new sessions (and incoming cross-agent messages) don't get refused. It will never reap a session that's actually working. It's off by default; ask me to turn it on after we watch its dry-run log.
18
+ - **You won't notice anything unless you enable it.** No behavior change on update.
19
+
20
+ ## Summary of New Capabilities
21
+
22
+ | Capability | How to Use |
23
+ |-----------|-----------|
24
+ | SessionReaper (idle-session cleanup under pressure) | `monitoring.sessionReaper.enabled:true` (leave `dryRun:true` first). Off by default. |
25
+ | Reaper observability | `GET /sessions/reaper` — pressure tier + per-session verdict + keptBy |
26
+ | Single-writer session termination | `SessionManager.terminateSession()` — idle-kill + reaper share one CAS kill path |
@@ -0,0 +1,27 @@
1
+ # Upgrade Guide — NEXT
2
+
3
+ <!-- bump: patch -->
4
+ <!-- Valid values: patch, minor, major -->
5
+ <!-- patch = bug fixes, refactors, test additions, doc updates -->
6
+ <!-- minor = new features, new APIs, new capabilities (backwards-compatible) -->
7
+ <!-- major = breaking changes to existing APIs or behavior -->
8
+
9
+ ## What Changed
10
+
11
+ **The `/build` stop-hook is now session-scoped — it only nags the session that actually owns the build.** Before, the hook that keeps a build from quitting half-done had no idea *which* of your concurrent sessions started the build, so it fired its "keep working" block into every session — trapping unrelated ones and, worse, spending the owning build's reinforcement budget on each misfire (when that budget hit its cap, the hook stopped protecting the real builder too).
12
+
13
+ Now `build-state.py` stamps the owning session (its tmux session name, and optionally the Claude session UUID) at build start, and the hook blocks **only** the proven owner. Every other session approve-exits without touching the owner's budget. A build with no owner stamp (legacy state) gets a conservative no-adopt: the hook goes quiet rather than guessing — it never traps a session and never claims ownership.
14
+
15
+ The hook ships via the always-overwrite path (the inline `getBuildStopHook()` twin in `PostUpdateMigrator`, kept byte-identical to `src/templates/hooks/build-stop-hook.sh` and asserted by a drift test), so every agent gets it on update.
16
+
17
+ ## What to Tell Your User
18
+
19
+ - **No action needed; this just stops cross-talk between your sessions.** If you run more than one session at once, a build in one of them will no longer pester the others or drain its own "keep going" budget. You won't notice anything unless you run concurrent sessions, in which case it gets quieter.
20
+
21
+ ## Summary of New Capabilities
22
+
23
+ | Capability | How to Use |
24
+ |-----------|-----------|
25
+ | Session-scoped build stop-hook | Automatic. `build-state.py init` stamps `owner.{tmux,session,stampedAt}`; the hook blocks only the owner. |
26
+ | Owner-stamp flags on `build-state.py init` | `--owner-session "$CLAUDE_CODE_SESSION_ID"` (precision; SKILL wiring is a fast-follow), `--owner-tmux` (override seam). Tmux name is auto-resolved by default. |
27
+ | Conservative no-adopt for un-stamped builds | Automatic. No owner stamp → hook approves without claiming ownership (never traps, never drains). |
@@ -0,0 +1,133 @@
1
+ # Side-Effects Review — Build Stop-Hook Session-Scoping
2
+
3
+ **Slug:** `build-stop-hook-session-scoping`
4
+ **Date:** `2026-05-26`
5
+ **Author:** Echo
6
+ **Spec:** `docs/specs/BUILD-STOP-HOOK-SESSION-SCOPING-SPEC.md` (converged round 1, approved by Justin via Telegram topic 13352)
7
+ **Second-pass reviewer:** independent general-purpose review agent (3 findings, all incorporated — see spec §"Review Findings Incorporated")
8
+
9
+ ## Summary of the change
10
+
11
+ The `/build` Stop hook had no notion of which session owns a build. With one
12
+ shared `build-state.json` and one hook in a checkout, a build started by session
13
+ A fired its "keep working" block into every concurrent session of the same agent
14
+ — trapping unrelated sessions and, on every misfire, incrementing the shared
15
+ `reinforcementsUsed` counter, which drains the owning build's protection budget
16
+ to zero.
17
+
18
+ This change stamps the owning session at `/build` start (`build-state.py init`
19
+ writes `owner.{tmux,session,stampedAt}`) and teaches the hook to block **only**
20
+ the proven owner. Any other session approve-exits **without** incrementing the
21
+ counter. A build with no owner stamp gets a conservative no-adopt (approve,
22
+ never claim ownership) — it never traps a session and never inverts ownership.
23
+
24
+ ## Files changed (in gate scope = behavior)
25
+
26
+ - `src/core/PostUpdateMigrator.ts` — the inline `getBuildStopHook()` (the
27
+ shipping artifact; written to `.instar/hooks/instar/build-stop-hook.sh` on every
28
+ migration via always-overwrite, and by `init.ts` via `getHookContent`). Added
29
+ the ownership block between the terminal-phase early-exit and the counter
30
+ mutation.
31
+ - `src/templates/hooks/build-stop-hook.sh` — the canonical reference template +
32
+ builtin-manifest fingerprint. Kept byte-identical to the inline twin (asserted
33
+ by a new drift test).
34
+
35
+ Out of gate scope but part of the change:
36
+ - `playbook-scripts/build-state.py` — `cmd_init` stamps `owner`; new
37
+ `--owner-session` / `--owner-tmux` flags; `resolve_owner_tmux()` helper.
38
+ - `tests/unit/build-stop-hook-session-scoping.test.ts` (new, 12 tests),
39
+ `tests/unit/PostUpdateMigrator-buildStopHook.test.ts` (+1 drift test).
40
+ - `docs/specs/*`, `upgrades/NEXT.md` (docs / release note).
41
+
42
+ ## Decision-point inventory
43
+
44
+ - **Added**: hook ownership gate — between terminal-phase exit and counter
45
+ mutation. Decides block (owner) vs approve-no-increment (non-owner / unknown /
46
+ un-stamped). This is the new decision boundary.
47
+ - **Added**: `build-state.py` owner stamp at init (records identity; no runtime
48
+ decision, pure data).
49
+ - **Unchanged**: no-state-file exit, terminal-phase exit, and the
50
+ counter/reinforcement block logic itself (the owner path falls through to the
51
+ exact pre-existing code).
52
+
53
+ ## Over-block / under-block analysis
54
+
55
+ - **Over-block risk (trapping a non-owner):** eliminated. A non-owner returns
56
+ `approve` before reaching the counter. The only block path requires a positive
57
+ owner match (tmux or session). Tested: non-owner tmux, non-owner session,
58
+ identity-unknown, and legacy/un-stamped all return approve.
59
+ - **Under-block risk (owner not protected):** bounded and acceptable. The owner
60
+ is protected whenever `owner.tmux` matches the live tmux (the load-bearing
61
+ path, proven live) or `owner.session` matches stdin `session_id`. The only
62
+ under-protection case is an **un-stamped** build (legacy state, or an
63
+ environment where stamping didn't run) — by deliberate design the hook goes
64
+ quiet there rather than guess. Forfeiting protection for a stale build is the
65
+ correct trade vs. trapping the wrong session (spec §"Why conservative-no-adopt").
66
+ - **Bootstrap inversion (the rejected alternative):** an earlier draft let the
67
+ first session to Stop adopt ownership. The independent review showed this
68
+ inverts ownership in the real incident pattern (busy owner never stops first).
69
+ Removed entirely; replaced with conservative no-adopt. Tested: un-stamped state
70
+ yields approve with `owner` NOT written.
71
+
72
+ ## Level-of-abstraction fit
73
+
74
+ The fix lives at the same layer as the bug: the Stop hook and the state writer.
75
+ It mirrors the already-shipped autonomous stop-hook's session-scoping ladder
76
+ (tmux-name primary, session-UUID backstop, fail-open) without merging the two
77
+ (explicit non-goal — bash hooks don't share code cleanly; premature abstraction
78
+ avoided). No new module, no new service.
79
+
80
+ ## Signal-vs-authority compliance
81
+
82
+ The hook is a low-context filter making a binary ownership decision from
83
+ locally-verifiable identifiers (tmux `#S`, stdin `session_id`). It does not
84
+ arrogate higher-level judgment — it only declines to block a session it cannot
85
+ prove it owns. Conservative-by-construction: every ambiguous case resolves to
86
+ `approve` (release), never to `block` (trap). It emits no user-facing messages.
87
+
88
+ ## Interactions
89
+
90
+ - **Reinforcement counter:** the owner path is byte-for-byte the prior logic, so
91
+ graduated protection (3/5/10) is unchanged for the owner. Non-owners no longer
92
+ touch the counter at all.
93
+ - **Restart reconcile:** writes `owner.session` ONLY on a confirmed tmux-owner
94
+ match with a rotated UUID. Gated strictly behind the tmux match — a non-owner
95
+ can never clobber `owner.session` (tested explicitly).
96
+ - **stdin consumption:** the hook now reads stdin (`cat`). Stop hooks deliver and
97
+ close stdin in production (the autonomous hook relies on this), so no hang.
98
+ Even with no stdin/session, tmux-scoping alone is sufficient (proven live).
99
+ - **Worktree topology:** ownership is keyed on the cwd-independent tmux name, so
100
+ it is correct whether the owner launched at the main root and `cd`'d into a
101
+ worktree or launched rooted inside the worktree. The old, fragile `$PWD`-based
102
+ stopgap is NOT carried forward.
103
+ - **Migration parity:** inline twin is always-overwritten → every agent gets the
104
+ new hook on update. `build-state.py` rides the repo checkout (the only place
105
+ the bug occurs — see spec §Migration Parity 2). Drift test prevents
106
+ template/inline divergence.
107
+
108
+ ## Rollback cost
109
+
110
+ Low and clean. Revert the two src files (and optionally build-state.py); the
111
+ always-overwrite migration restores the prior hook on next update. The added
112
+ `owner` block in state is additive JSON ignored by the old hook — no destructive
113
+ schema migration. No data loss path.
114
+
115
+ ## Tracked deferral
116
+
117
+ The SKILL change to pass `--owner-session "$CLAUDE_CODE_SESSION_ID"` (session-UUID
118
+ precision; + its dedicated PostUpdateMigrator migration per Migration Parity §5)
119
+ is a deliberate fast-follow per the approved phasing (Justin approved one-PR-now +
120
+ tiny-follow-up). The flag is already plumbed and tested in `build-state.py`; only
121
+ the SKILL invocation + migration remain. This is tracked, not orphaned.
122
+
123
+ ## Verification
124
+
125
+ - 3-tier behavior tests (12) drive the **real shipping hook** (from
126
+ `getHookContent`) against the **real `build-state.py`** with real stdin/tmux
127
+ seams — covering owner-block, non-owner-no-drain, repeated-non-owner,
128
+ session-only owner, identity-unknown fail-open, legacy no-adopt, restart
129
+ reconcile, anti-clobber, terminal-phase. Plus build-state stamp tests (3) and
130
+ the template/inline drift test (1).
131
+ - Live test-as-self: ran the shipping hook with **real** `tmux display-message`
132
+ resolution (no seam) in this session (`echo-build-stop-hook-session-scoping`) —
133
+ confirmed owner→block, non-owner→approve with zero counter drain.
@@ -0,0 +1,35 @@
1
+ # Side-effects review — fresh-session stop-gate shadow wiring
2
+
3
+ **Scope**: Complete the conservative first rollout for the fresh-session stop-gate: wire the existing server authority/database, install a Stop-hook router, and default to observe-only shadow mode when the gate is healthy.
4
+
5
+ **Files touched**:
6
+ - `src/commands/server.ts` — constructs `StopGateDb` and `UnjustifiedStopGate`, persists mode state, passes both into `AgentServer`.
7
+ - `src/server/stopGate.ts` — persists mode flips to `server-data/stop-gate-mode.json`.
8
+ - `src/server/routes.ts` — records SessionStart rows in `StopGateDb` when hook events arrive.
9
+ - `src/core/PostUpdateMigrator.ts` — installs `stop-gate-router.js` and patches `.claude/settings.json` Stop hooks.
10
+ - `src/commands/init.ts` and `src/templates/hooks/settings-template.json` — include the router on fresh installs.
11
+ - `src/core/installCodexHooks.ts` — mirrors the Stop router into Codex hook config.
12
+ - Focused tests cover route mode persistence, hook behavior, Codex registration, and update migration.
13
+
14
+ **Under-block**: Intentional for this PR. Default mode is `shadow` only when the authority and SQLite log initialize successfully; otherwise the gate is `off`. In shadow mode the hook submits evaluations but always lets the agent exit. Enforcement is reserved for a later explicit operator flip.
15
+
16
+ **Over-block**: Minimal. The router only emits `{decision:"block"}` when the server is already in `enforce` mode and the server authority returns `continue` with a reminder. All network errors, malformed hook payloads, missing config, hot-path failures, compaction-in-flight, kill-switch, and degraded initialization paths fail open.
17
+
18
+ **Signal vs authority**: Compliant. The hook collects evidence metadata and simple signals, but never decides whether a Stop is unjustified. The server-side `UnjustifiedStopGate` remains the sole authority for `continue`; the hook is a transport/router.
19
+
20
+ **External surfaces**:
21
+ - New installed hook file: `.instar/hooks/instar/stop-gate-router.js`.
22
+ - Existing routes become live for real Stop events: `GET /internal/stop-gate/hot-path` and `POST /internal/stop-gate/evaluate`.
23
+ - New persisted mode file: `server-data/stop-gate-mode.json`.
24
+ - Existing SQLite event log: `server-data/stop-gate.db`.
25
+
26
+ **Migration parity**:
27
+ - Post-update migration writes the router and patches existing Claude settings.
28
+ - Fresh `instar init` installs the router template.
29
+ - Codex hook installer places the router first in the Stop chain before the existing review trio.
30
+
31
+ **Rollback cost**: Revert this change set. Existing `stop-gate-mode.json` and `stop-gate.db` files can remain on disk; without the router/server wiring they are inert. Emergency runtime rollback without code revert: `instar gate mode off` or set the kill-switch.
32
+
33
+ **Tests**:
34
+ - `npm test -- --run tests/unit/routes-stopGate.test.ts tests/unit/stop-gate-router-hook.test.ts tests/unit/installCodexHooks.test.ts tests/unit/PostUpdateMigrator-codexHooks.test.ts`
35
+ - `npx tsc --noEmit`
@@ -0,0 +1,42 @@
1
+ # Side-Effects Review — SessionReaper
2
+
3
+ Spec: `docs/specs/SESSION-REAPER-SPEC.md` (v2 CONVERGED + ratified). Build branch `build/session-reaper`.
4
+
5
+ ## What changes for a deployed agent
6
+
7
+ - A new monitor (`SessionReaper`) is constructed and started at server boot. **Default OFF + dry-run** (`monitoring.sessionReaper.enabled:false, dryRun:true`), so deployed agents get **no behavior change** until an operator opts in. New config block arrives via the standard `ConfigDefaults`/`applyDefaults` migration; operator-set values are never overwritten.
8
+ - New read-only endpoint `GET /sessions/reaper` (503 when unwired, 200 snapshot otherwise).
9
+ - `SessionManager` gains `terminateSession()` (single-writer CAS), `isRelayLeaseActive()`, and `markReaping/clearReaping/isReaping`. The existing idle-kill now funnels through `terminateSession` and skips reaping-leased sessions; `killSession` shares the CAS guard and now sets `endedReason` (its event emissions are unchanged — still no `sessionComplete`).
10
+ - The zombie-kill recovery veto (`activeRecoveryChecker`) is recomposed to include the socket + silence sentinels (previously compaction + rate-limit only) — a strict superset; nothing is dropped.
11
+
12
+ ## Over/under-block analysis (the hard requirement)
13
+
14
+ The reaper must never reap a working session. Safety rests on positive evidence, not absence of activity:
15
+ - **Under-block (fails to reap a genuinely idle session):** acceptable — the existing 15m/4h idle-kill still runs; the reaper is additive pressure relief.
16
+ - **Over-block (reaps a working session):** the failure that matters. Mitigations: (1) requires a *positive* turn-complete idle-prompt signal; (2) render-stasis — pane byte-identical across all confirm ticks; (3) process + transcript must be quiet, and any *unresolvable* signal (no `claudeSessionId`, Codex/missing/rotated transcript, uninspectable process) forces KEEP, never "quiet"; (4) hysteresis; (5) two-phase reap with a final-grace re-check that aborts on any frame change; (6) Normal pressure tier reaps nothing; (7) bounded per-tick/per-hour budget; (8) auto-disable to dry-run on any ambiguous/failed reap; (9) ships OFF + dry-run.
17
+
18
+ ## Level-of-abstraction / signal-vs-authority
19
+
20
+ Signals carry confidence and only *recommend*; kill authority sits behind the budget + dry-run + single-writer `terminateSession` CAS + auto-disable. The reaper computes a verdict; it does not own an unbounded kill.
21
+
22
+ ## Interactions
23
+
24
+ - Composes with (does not fight) existing watchdogs: gate G defers to any recovery-in-flight (now incl. socket/silence); disjoint from OrphanProcessReaper (untracked procs) and SessionWatchdog (active-but-stuck); shares the single-writer kill path with the idle-kill so no double-kill / double-event.
25
+ - Pressure source is freemem-tiered for v1 (advisory; macOS under-reports). Crucially, an over-eager pressure tier can only reap a *genuinely-idle* session sooner — it cannot cause a working session to be reaped, because the classifier protects working sessions independent of tier.
26
+
27
+ ## Rollback
28
+
29
+ Set `monitoring.sessionReaper.enabled:false` (the default) — fully inert. No data migration; `endedReason` is additive/optional. Revert the branch to remove code; no persisted state needs cleanup beyond an optional `state/session-reaper.json` (absent unless restart-durability is later wired).
30
+
31
+ ## Tests
32
+
33
+ 3-tier: unit (transcript prober, terminateSession CAS, classifier incl. every false-reap vector, config/migration), integration (`/sessions/reaper` + dry-run), e2e (feature-alive + dangerous cases). Wiring-integrity guards the construct→start→pass chain. Live test-as-self on a real in-flight build + a real Codex session precedes merge.
34
+
35
+ ## Phase-3 review fixes (post multi-agent code review)
36
+
37
+ Independent review confirmed NO blocker to the hard requirement (cannot reap a working session) and surfaced safety-net hardening, all applied:
38
+ - **Reaping-lease leak:** when a matured reap is budget/tier-gated, the reaping lease is now released — previously it could permanently disable the fast idle-kill for that session.
39
+ - **Protected-list wiring:** gate A now reads `SessionManager.getProtectedSessions()` (the resolved list including the `<project>-server` default) rather than the raw config field, preventing spurious auto-disable when the server session goes idle.
40
+ - **Robustness:** `tick()` and `snapshot()` treat a throwing protect-signal as KEEP — never reap on a failed evaluation, and the `/sessions/reaper` route never 500s.
41
+ - **`killSession` contract preserved:** unconditional pane kill retained (only the in-flight guard added; no terminal-status early-return).
42
+ - **Known v1 gap (documented, not a false-reap vector):** the optional `mainProcessActive` CPU/IO-delta signal is not wired in v1; render-stasis is the real-time liveness channel that covers in-process work. Promoting `mainProcessActive` is a tracked enhancement, validated during the dry-run rollout.