instar 1.3.106 → 1.3.108

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 (37) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +6 -0
  3. package/dist/commands/server.js.map +1 -1
  4. package/dist/core/SessionManager.d.ts +23 -0
  5. package/dist/core/SessionManager.d.ts.map +1 -1
  6. package/dist/core/SessionManager.js +53 -0
  7. package/dist/core/SessionManager.js.map +1 -1
  8. package/dist/core/StuckInputSentinel.d.ts.map +1 -1
  9. package/dist/core/StuckInputSentinel.js +7 -6
  10. package/dist/core/StuckInputSentinel.js.map +1 -1
  11. package/dist/core/claudeActivityIndicators.d.ts +37 -0
  12. package/dist/core/claudeActivityIndicators.d.ts.map +1 -0
  13. package/dist/core/claudeActivityIndicators.js +45 -0
  14. package/dist/core/claudeActivityIndicators.js.map +1 -0
  15. package/dist/core/cpuStarvation.d.ts +30 -0
  16. package/dist/core/cpuStarvation.d.ts.map +1 -0
  17. package/dist/core/cpuStarvation.js +44 -0
  18. package/dist/core/cpuStarvation.js.map +1 -0
  19. package/dist/lifeline/ServerSupervisor.d.ts +25 -0
  20. package/dist/lifeline/ServerSupervisor.d.ts.map +1 -1
  21. package/dist/lifeline/ServerSupervisor.js +71 -46
  22. package/dist/lifeline/ServerSupervisor.js.map +1 -1
  23. package/dist/monitoring/CompactionSentinel.d.ts +39 -1
  24. package/dist/monitoring/CompactionSentinel.d.ts.map +1 -1
  25. package/dist/monitoring/CompactionSentinel.js +45 -1
  26. package/dist/monitoring/CompactionSentinel.js.map +1 -1
  27. package/dist/server/AgentServer.d.ts.map +1 -1
  28. package/dist/server/AgentServer.js +17 -0
  29. package/dist/server/AgentServer.js.map +1 -1
  30. package/package.json +1 -1
  31. package/src/data/builtin-manifest.json +4 -4
  32. package/upgrades/1.3.107.md +72 -0
  33. package/upgrades/1.3.108.md +94 -0
  34. package/upgrades/NEXT.eli16.md +60 -49
  35. package/upgrades/side-effects/compaction-busy-session-defer.md +94 -0
  36. package/upgrades/side-effects/failure-learning-sources-wiring-and-migration-parity.md +127 -0
  37. package/upgrades/side-effects/supervisor-cpu-starvation-restart-guard.md +83 -0
@@ -0,0 +1,94 @@
1
+ # Side-Effects Review — Compaction recovery: defer re-inject while the session is actively working
2
+
3
+ **Version / slug:** `compaction-busy-session-defer`
4
+ **Date:** `2026-05-29`
5
+ **Author:** `echo`
6
+ **Spec:** `docs/specs/compaction-busy-session-defer.md` (+ `.eli16.md`)
7
+ **Convergence:** `docs/specs/reports/compaction-busy-session-defer-convergence.md` (fast-tracked — urgent fleet bug; see report)
8
+ **Second-pass reviewer:** `not required` (see §"Phase 5 trigger check")
9
+
10
+ ## Summary of the change
11
+
12
+ Fixes the user-reported false "session is restarting" loop (Justin, topic 15160):
13
+ a live, actively-working session gets its inbound message buried under stacked
14
+ compaction-recovery re-injects, so the message never reaches the session and the
15
+ user sees repeated "restarting / starting up" narration.
16
+
17
+ Root cause (verified in running code + `logs/server.log` on Echo): `CompactionSentinel`
18
+ re-injects a recovery prompt when the session's JSONL doesn't grow within the
19
+ verify window — but a long extended-think on a large context (typical right after
20
+ resuming a near-full transcript) writes nothing to the JSONL until the turn
21
+ lands, so a healthy session reads as "stuck." Caught firing 3× in ~52s against a
22
+ live session.
23
+
24
+ The change teaches the recovery surfaces to recognize a mid-turn session (the
25
+ canonical `esc to interrupt` / `tokens · esc` footer OR a live non-baseline child
26
+ process) and DEFER instead of re-injecting, bounded by `maxWorkingDefers`.
27
+
28
+ ## Files touched
29
+
30
+ - `src/core/claudeActivityIndicators.ts` (**new**) — `CLAUDE_WORKING_INDICATORS`
31
+ single source of truth + pure `paneShowsClaudeWorking`.
32
+ - `src/core/StuckInputSentinel.ts` — its `ACTIVITY_INDICATORS` now imports the
33
+ shared const (no behavior change; removes drift risk).
34
+ - `src/core/SessionManager.ts` — `paneShowsActiveWork` + `isSessionActivelyWorking`;
35
+ `verifyInjection` skips the recovery Enter while the pane is actively working.
36
+ - `src/monitoring/CompactionSentinel.ts` — `isActivelyWorking?` dep,
37
+ `maxWorkingDefers` config, `workingDefers` state, `deferForActiveWork()` +
38
+ `scheduleVerify()`, `compaction:deferred` event, `deferring` status.
39
+ - `src/commands/server.ts` — wires `isActivelyWorking` to
40
+ `sessionManager.isSessionActivelyWorking`.
41
+ - Tests: `tests/unit/CompactionSentinel.test.ts` (+6 cases),
42
+ `tests/unit/claudeActivityIndicators.test.ts` (new),
43
+ `tests/unit/session-active-work.test.ts` (new),
44
+ `tests/integration/compaction-busy-defer-wiring.test.ts` (new),
45
+ `tests/e2e/compaction-busy-defer-lifecycle.test.ts` (new).
46
+
47
+ ## Decision-point inventory
48
+
49
+ - **Inject vs defer (CompactionSentinel)** — *modify, additive*. Was "no jsonl
50
+ growth → re-inject/fail." Now: "no growth AND actively working AND defer budget
51
+ remains → wait without injecting; else unchanged." A presence check, not a
52
+ judgment. Both branches test-covered. With the dep absent or
53
+ `maxWorkingDefers: 0`, behavior is byte-for-byte the old path.
54
+ - **Recovery-Enter vs skip (verifyInjection)** — *modify, additive*. Was "marker
55
+ at prompt → fire Enter." Now: "marker at prompt AND actively working → skip
56
+ this tick, keep polling." Skipping is strictly less action; the input is
57
+ correctly queued by Claude Code and submits when the turn ends.
58
+ - **Working signal** — *read-only*. Reuses the existing footer indicators +
59
+ `hasActiveProcesses`. No new file format, no write, no new tmux command.
60
+ `isSessionActivelyWorking` is wrapped so a capture/ps failure resolves to
61
+ `false` (cannot throw into recovery).
62
+
63
+ ## Blast radius
64
+
65
+ - **No new authority, no new gate, no new detector, no new API route, no
66
+ external surface.** A defer behind an existing, already-rate-guarded recovery
67
+ primitive.
68
+ - **Hot paths unchanged.** `getTopicForSession` and the message-inject hot path
69
+ are untouched. `isSessionActivelyWorking` runs only inside the recovery
70
+ lifecycle (already off the hot path).
71
+ - **Strictly less aggressive.** The change can only PREVENT a re-inject/Enter; it
72
+ never adds one. It cannot starve a real recovery: an idle or wedged session
73
+ shows no working tell → no defer. Bounded by `maxWorkingDefers`.
74
+ - **No agent-installed files changed** — no `.claude/settings.json` hooks, no
75
+ `.instar/config.json` defaults, no CLAUDE.md template, no hook scripts, no
76
+ skills. So **no PostUpdateMigrator entry is required**: pure `src/` logic that
77
+ reaches every agent through the normal dist update.
78
+
79
+ ## Phase 5 trigger check (second-pass reviewer)
80
+
81
+ Second pass **not required**: no new authority, no destructive operation
82
+ introduced (the only behavioral deltas REMOVE actions — a re-inject and a
83
+ recovery Enter — against working sessions), no external surface, no migration.
84
+ Additive, read-only signal behind an existing rate-guarded recovery primitive,
85
+ with full three-tier coverage.
86
+
87
+ ## Verification
88
+
89
+ - Unit: `CompactionSentinel.test.ts`, `claudeActivityIndicators.test.ts`,
90
+ `session-active-work.test.ts` (+ existing `StuckInputSentinel.test.ts` still
91
+ green after the shared-const refactor) — 125 pass across the related set.
92
+ - Integration: `compaction-busy-defer-wiring.test.ts` — 4 pass.
93
+ - E2E: `compaction-busy-defer-lifecycle.test.ts` — 4 pass (incl. WIRED guard).
94
+ - `tsc --noEmit` clean.
@@ -0,0 +1,127 @@
1
+ # Side-effects review — Failure-Learning sources wiring + migration-parity assertion
2
+
3
+ ## What changed
4
+
5
+ Two structural backstops shipped together, both targeting recurring bug classes
6
+ named in the 2026-05-29 pipeline post-mortem.
7
+
8
+ 1. **Failure-Learning Loop: unimplemented-source warning + wiring-integrity test.**
9
+ `monitoring.failureLearning.sources.regression` and
10
+ `monitoring.failureLearning.sources.degradation` are config flags shipped
11
+ without implementations: setting them on did absolutely nothing. The flag
12
+ created a false sense of coverage — exactly the "specced but not wired"
13
+ class that produced PR #530 (AttributionResolver Phase 2 specced, never
14
+ wired, every burn alarm read 100% pre-attribution). A new boot-time
15
+ `console.warn` names the unimplemented sources, says they are silent
16
+ no-ops, and tells the operator to set them back off until impl ships.
17
+ A new unit-tier test asserts the CiFailurePoller and RevertDetector are
18
+ constructed iff their flags are on, and that the unimplemented-source
19
+ warning fires on the right configurations and stays quiet on the right
20
+ ones — so a future regression in the gating logic cannot silently
21
+ disable the loop.
22
+
23
+ 2. **Migration parity: a static assertion that fresh-init hook installs match
24
+ auto-update hook installs.** Per the "Migration parity skip" class
25
+ (telegram-reply.sh 403, autonomous-stop-hook broken path, the
26
+ slack-channel-context.sh divergence we found mid-PR in #542). The test
27
+ reads `installHooks()` (src/commands/init.ts) and `migrateHooks()`
28
+ (src/core/PostUpdateMigrator.ts), extracts the set of hook files each
29
+ writes, and asserts neither side has a file the other doesn't —
30
+ except for a small documented allowlist of migrator-only deferred-install
31
+ files. The allowlist's soft cap (10) trips a regression alarm if the
32
+ gap widens.
33
+
34
+ ## Why
35
+
36
+ The May 2026 fix-rate to main was ~19% (150 of 781 commits over 14 days). The
37
+ Failure-Learning Loop had captured exactly **one** event total — a manual
38
+ agent-diagnosed entry I posted on the morning of the post-mortem itself. The
39
+ loop's substrate (CiFailurePoller, RevertDetector, FailureLedger,
40
+ FailureAttributionEngine, /failures routes) was already shipped (slice 1 in
41
+ late April, slice 1b around #484), but every ingestion source was switched OFF
42
+ in `ConfigDefaults.ts`. So the meta-trace lever was unpulled.
43
+
44
+ I cannot flip a config default to a fleet-wide ON in a single PR — per-agent
45
+ opt-in is the model the spec sets. What I CAN ship is the missing structural
46
+ backstop: unimplemented sources must surface their gap visibly, and the
47
+ wiring-integrity test makes any future regression in the gating impossible to
48
+ miss. Plus the migration-parity test catches THE specific class that produced
49
+ the silent failure that initiated this whole conversation (a fresh-init agent
50
+ missing a hook the migrator installs, including telegram-topic-context.sh —
51
+ the exact file behind the 2026-05-29 incoherent-reply incident).
52
+
53
+ Echo's local config is being flipped to `sources.ci: true, sources.revert: true`
54
+ out-of-band as a dogfooding step; that's an agent-local change, not part of
55
+ this PR.
56
+
57
+ ## Risk surface
58
+
59
+ - **Boot-time `console.warn` for unimplemented sources** — only fires when the
60
+ agent has explicitly turned on `sources.regression: true` or
61
+ `sources.degradation: ["..."]`. Default-off agents see nothing. The warning
62
+ text is plain-English and tells the operator the explicit fix (set them
63
+ back off). No state mutation; no API surface change.
64
+ - **Wiring-integrity test** — pure unit test, uses an in-process mock
65
+ `SessionManager`. Doesn't construct an HTTP server, doesn't touch the
66
+ network, doesn't read git. Risk: zero.
67
+ - **Migration-parity test** — static-analysis-only test. Parses
68
+ `src/commands/init.ts` and `src/core/PostUpdateMigrator.ts` source files
69
+ with a small regex (`fs.writeFileSync(path.join(hooksDir|instarHooksDir,
70
+ '<filename>'), …)`) and compares the resulting sets. Doesn't run either
71
+ function. Risk: zero.
72
+ - **Allowlist semantics** — the test treats the allowlist as deliberate
73
+ accepted technical debt; growing it requires explicit code change + a
74
+ documented rationale per entry. The soft cap (10) protects against silent
75
+ drift.
76
+
77
+ ## Bug surfaces eliminated
78
+
79
+ - `sources.regression: true` previously silently no-op'd → now warns loudly.
80
+ - `sources.degradation: ["x"]` previously silently no-op'd → now warns loudly.
81
+ - A future PR that adds a `migrateHooks()` write without an `installHooks()`
82
+ counterpart → caught at commit time by the parity test.
83
+ - A future PR that adds an `installHooks()` write without a `migrateHooks()`
84
+ counterpart → caught at commit time by the parity test (this is the more
85
+ dangerous direction — the migrator overwrites on every cycle; a fresh-init
86
+ file the migrator doesn't maintain drifts forever).
87
+ - A future regression that disables the CiFailurePoller construction when
88
+ `sources.ci: true` → caught by wiring-integrity test.
89
+
90
+ ## Migration footprint
91
+
92
+ - No fleet migration required. The boot-warning is purely an in-process side
93
+ effect at AgentServer construction time; existing agents pick it up on the
94
+ next process restart (which auto-update will trigger anyway).
95
+ - Default config defaults are unchanged. Per-agent opt-in remains the
96
+ intended model for the sources.
97
+
98
+ ## Testing
99
+
100
+ - Unit (wiring): `tests/unit/failure-learning-sources-wiring.test.ts` —
101
+ 9 tests. Constructed iff flag set (both directions), enabled:false
102
+ inert across all source flags, unimplemented sources trigger one
103
+ consolidated warning, implemented-only configs stay quiet.
104
+ - Unit (migration parity): `tests/unit/migration-parity-hooks.test.ts` —
105
+ 5 tests. Non-empty both sides, no install-only, no migrator-only outside
106
+ allowlist, allowlist soft cap. Positive + destructive-negative verified
107
+ (fake install-only write fails; fake migrator-only-without-allowlist
108
+ fails).
109
+
110
+ ## Test count
111
+
112
+ 14 new tests across the unit tier. Existing related tests
113
+ (`CiFailurePoller.test.ts: 12`, `RevertDetector.test.ts: 9`,
114
+ `PostUpdateMigrator-time-injection.test.ts: 13`) remain green.
115
+
116
+ ## Follow-ups (NOT in this PR — surfaced explicitly so the allowlist doesn't
117
+ hide them indefinitely):
118
+
119
+ - The `INSTALL_VS_MIGRATE_KNOWN_GAPS` allowlist enumerates 6 currently-accepted
120
+ migrator-only hooks. Each entry is technical debt that should be closed by
121
+ moving the file into `installHooks()` (or removing it from the migrator if
122
+ it's no longer needed). Tracked in the allowlist comments so the soft cap
123
+ surfaces drift.
124
+ - The `regression` and `degradation` source IMPLEMENTATIONS still need to
125
+ ship. Those are separate features (the post-mortem's lever B —
126
+ real-world-state fixture tests — closely related). This PR's warning
127
+ closes the misleading-flag gap until then.
@@ -0,0 +1,83 @@
1
+ # Side-Effects Review — Supervisor CPU-starvation restart guard
2
+
3
+ **Version / slug:** `supervisor-cpu-starvation-restart-guard`
4
+ **Date:** `2026-05-29`
5
+ **Author:** `echo`
6
+ **Spec:** `docs/specs/supervisor-cpu-starvation-restart-guard.md` (+ `.eli16.md`)
7
+ **Convergence:** `docs/specs/reports/supervisor-cpu-starvation-restart-guard-convergence.md` (fast-tracked — urgent fleet bug; see report)
8
+ **Second-pass reviewer:** `not required` (see §"Phase 5 trigger check")
9
+
10
+ ## Summary of the change
11
+
12
+ Fixes the overload-driven server restart loop that drops user messages (Justin,
13
+ topic 15160: "Session restarting, message never lands"). Under CPU starvation
14
+ the live server can't answer `/health`, so `ServerSupervisor` restarted it every
15
+ ~60s — but a fresh server is starved too, so it just dropped the in-flight
16
+ message and looped. Verified in `logs/server.log`: 6 server restarts in ~35min
17
+ during a load spike (load ratio ~2× cores).
18
+
19
+ The guard makes the supervisor load-aware: while the box is CPU-starved
20
+ (`loadavg[0]/cpuCount > 1.5`, the same signal SleepWakeDetector uses), an
21
+ alive-but-unresponsive server's restart is DEFERRED (up to a 5-minute hard cap)
22
+ instead of bounced.
23
+
24
+ ## Files touched
25
+
26
+ - `src/core/cpuStarvation.ts` (**new**) — shared `cpuLoadRatio` / `isCpuStarved`
27
+ + `DEFAULT_MAX_LOAD_RATIO`.
28
+ - `src/lifeline/ServerSupervisor.ts` — extracted the two identical
29
+ health-failure branches into `evaluateUnhealthyServer()`; added
30
+ `deferRestartForCpuStarvation()`, `maxLoadRatio`, `starvationRestartThreshold`,
31
+ and an injectable `loadRatioProvider`.
32
+ - `tests/unit/cpu-starvation.test.ts` (new), `tests/unit/supervisor-cpu-starvation-defer.test.ts` (new).
33
+
34
+ ## Decision-point inventory
35
+
36
+ - **Restart vs defer (alive + unresponsive)** — *modify, additive*. Was: restart
37
+ at `processAliveThreshold` (6 failures). Now: if CPU-starved and below the hard
38
+ cap, defer; else unchanged. A pure presence check on system load, not a
39
+ judgment. All branches covered by tests driving the real method.
40
+ - **Branch extraction** — *refactor, behavior-preserving*. The two duplicated
41
+ failure paths (unhealthy `/health`, thrown check) now call one
42
+ `evaluateUnhealthyServer()`. The non-starved/dead/below-threshold behavior is
43
+ byte-for-byte the same; verified by the existing `supervisor-health-check`
44
+ test still passing + new real-method tests.
45
+ - **Load read** — *read-only*. `os.loadavg()` / `os.cpus().length` via the shared
46
+ helper; returns 0 on error (fails toward "not starved" → restart, the safe
47
+ legacy behavior). No new file, no write, no new external surface.
48
+
49
+ ## Blast radius
50
+
51
+ - **No new authority, no new gate, no new API route, no external surface.** A
52
+ defer behind the existing restart decision.
53
+ - **Strictly less aggressive.** Can only DEFER a restart it would otherwise do,
54
+ only while genuinely CPU-starved, only up to a 5-min hard cap. Dead process →
55
+ immediate restart unchanged; not-starved → restart at 6 unchanged. Cannot
56
+ strand a hung server (hard cap) and the defer self-clears on the next healthy
57
+ tick.
58
+ - **No agent-installed files changed** — pure `src/` (lifeline + a helper). No
59
+ `PostUpdateMigrator` entry required; the lifeline picks it up on its next
60
+ restart via the existing version-skew/drift coordination.
61
+
62
+ ## Pre-existing-bug note
63
+
64
+ The two health-failure branches were identical copy-paste — a latent drift risk
65
+ (a fix to one could miss the other). Extracting them into one method removes that
66
+ hazard as a side benefit.
67
+
68
+ ## Phase 5 trigger check (second-pass reviewer)
69
+
70
+ Second pass **not required**: no new authority, no new destructive operation (the
71
+ only behavioral delta REMOVES a restart while starved), no external surface, no
72
+ migration. Additive, read-only load signal behind an existing restart primitive,
73
+ with real-method unit coverage + a wiring guard.
74
+
75
+ ## Verification
76
+
77
+ - `tests/unit/cpu-starvation.test.ts` — pure helper (ratio + threshold).
78
+ - `tests/unit/supervisor-cpu-starvation-defer.test.ts` — REAL
79
+ `evaluateUnhealthyServer()`: defer-while-starved, restart-when-not,
80
+ force-restart-past-cap, dead→immediate, below-threshold→wait,
81
+ defer-not-permanent + wiring guard. 19 pass with the existing
82
+ `supervisor-health-check` suite.
83
+ - `tsc --noEmit` clean.