instar 1.2.30 → 1.2.32

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.
@@ -0,0 +1,65 @@
1
+ # Side-Effects Review — feat(memory): periodic SessionActivitySentinel scan
2
+
3
+ **Branch:** `echo/phase-0b-periodic-scan`
4
+ **Origin:** Phase 0b of the topic-intent-layer thread (Telegram 9976). Last foundation piece before Phase 1.
5
+
6
+ ## Summary
7
+
8
+ Wires the periodic `SessionActivitySentinel.scan()` (every 30 min default) as a `setInterval` in `server.ts`, gated to the awake machine. Previously the sentinel only fired on `sessionComplete`, so long-running sessions never got mid-flight digestion and the Phase 0d entity extraction never ran for them.
9
+
10
+ ## Files touched
11
+
12
+ - `src/core/types.ts` — added optional `MonitoringConfig.episodicSentinel` ({ enabled?, scanIntervalMinutes? }).
13
+ - `src/monitoring/SessionActivitySentinel.ts` — added pure helper `resolveSentinelScanIntervalMs`.
14
+ - `src/commands/server.ts` — wired the periodic scan interval after the sessionComplete handler; imports the new helper.
15
+ - `tests/unit/sentinel-scan-interval.test.ts` — 6 tests on the resolver.
16
+ - `docs/specs/periodic-sentinel-scan.md` + `.eli16.md` — spec + ELI16.
17
+ - `upgrades/NEXT.md` — release note.
18
+
19
+ ## Over-block check
20
+
21
+ The scan only runs when `resolveSentinelScanIntervalMs` returns non-null (enabled !== false) AND (no coordinator OR the machine is awake). It does NOT run on standby machines. It does NOT add an immediate startup scan (first fire is one interval out), so restart loops don't trigger repeated digestion. No new behaviour in the disabled path — `enabled: false` returns null and no interval is created.
22
+
23
+ ## Under-block check
24
+
25
+ Could a long session still be missed? Only if: the periodic scan is disabled by config AND the session never reaches sessionComplete. That's an explicit operator opt-out, not a silent gap. The default (on, 30 min) covers the case the spec targets.
26
+
27
+ scan() itself enforces dormant-skip and minimum-activity internally, so an idle session that the timer visits costs nothing — no over-digestion of quiet sessions.
28
+
29
+ ## Level-of-abstraction fit
30
+
31
+ The interval policy (default, floor, disabled) is a pure function on the sentinel module — testable in isolation, no server bootstrap needed. The actual `setInterval` lives in server.ts next to the existing sessionComplete wire and the other periodic timers (relayPrune, resumeHeartbeat), matching the established in-process-timer pattern. The sentinel class is unchanged — it already had scan(); we just call it on a cadence.
32
+
33
+ ## Signal-vs-authority compliance
34
+
35
+ Not a gate — this is a scheduler wire, not a filter. The closest principle is the awake-machine gating: the coordinator (authority on which machine is active) decides whether this machine scans. The timer defers to `coordinator.isAwake` rather than making its own liveness call.
36
+
37
+ ## Interactions
38
+
39
+ - **sessionComplete synthesis**: unchanged. The periodic scan and the completion synthesis are independent; scan()'s idempotency (hash-keyed digests) means a periodic scan immediately before sessionComplete won't double-digest the same activity window.
40
+ - **scan() idempotency / dormant-skip / min-activity**: relied upon, unchanged. These guards are what make a 30-min cadence safe.
41
+ - **SemanticMemory entity extraction (Phase 0d)**: this scan is what drives it mid-session. The two compose: scan → digestUnit → materializeEntities. No new coupling; the periodic scan just calls the same digestActivity path the sessionComplete handler already used.
42
+ - **Multi-machine coordinator**: awake-gating added; mirrors `config.scheduler.enabled && coordinator.isAwake`. Standby machines skip.
43
+ - **Scheduler / jobs.json**: untouched. This is an in-process timer, not a cron job, so it doesn't interact with quota-tracked job scheduling. (Note: the LLM spend of periodic digestion is NOT currently quota-gated the way scheduled jobs are — see follow-up.)
44
+
45
+ ## Telemetry / observability
46
+
47
+ - A `console.log` fires on startup naming the cadence ("periodic scan every 30min").
48
+ - Each scan that creates digests logs the count.
49
+ - Failures log via console.error, never throw.
50
+ - `SemanticMemory.stats()` entity/edge growth is the downstream signal that the pipeline is working end-to-end.
51
+
52
+ ## Rollback
53
+
54
+ Single-commit revert removes the interval, the config field, and the helper. Reverts to sessionComplete-only — the pre-change behaviour. No data/config migration (the field is optional and absent by default).
55
+
56
+ ## Cross-framework portability (v1.0+)
57
+
58
+ Framework-agnostic: the scan reads tmux capture + Telegram JSONL and digests via `sharedIntelligence`. No INSTAR_FRAMEWORK branching. Same cadence on Claude and Codex.
59
+
60
+ ## Follow-ups (tracked, not orphaned)
61
+
62
+ 1. **Quota-awareness for periodic digestion LLM spend.** Scheduled jobs route through QuotaTracker; this in-process timer does not. On a quota-constrained agent, periodic digestion competes with foreground work for the same budget. A future enhancement could check `quotaTracker.canRunJob` before each scan. Lower priority — digestion uses the cheap `fast` tier and dormant-skip keeps volume low.
63
+ 2. **Live runtime verification.** The spec's acceptance includes a manual live check (set 5-min cadence, send messages, confirm a digest appears mid-session). Requires a live multi-message session; noted for the next live-agent session.
64
+
65
+ Both tracked as initiatives, intentionally out of scope here.
@@ -0,0 +1,92 @@
1
+ # Side-effects review — framework-spawn portability (codex agents spawn Codex)
2
+
3
+ **Scope**: A Codex-only agent ("codey") spawned a Claude Code session when
4
+ messaged on Telegram. Two compounding bugs: (1) `spawnInteractiveSession`
5
+ hardcoded `'claude-code'` and never read config/env; (2) the runtime's
6
+ `resolveConfiguredFramework` read `sessions.framework` + `INSTAR_FRAMEWORK`,
7
+ neither of which the wizard sets — the wizard persists `enabledFrameworks`.
8
+ So the runtime always resolved claude-code for wizard-installed Codex agents.
9
+
10
+ **Files touched**:
11
+ - `src/core/types.ts` — add optional `framework?: 'claude-code' | 'codex-cli'`
12
+ to `SessionManagerConfig` (the resolved runtime framework).
13
+ - `src/core/Config.ts` — `resolveConfiguredFramework` takes a third arg
14
+ `enabledFrameworks`; new precedence configValue → env → enabledFrameworks[0]
15
+ → 'claude-code'; explicit `claude`/`claude-code` env branch added; `loadConfig`
16
+ passes `fileConfig.enabledFrameworks` and stores the result on
17
+ `sessions.framework` (the SessionManagerConfig field).
18
+ - `src/core/SessionManager.ts` — `spawnInteractiveSession` replaces the
19
+ hardcoded default with `resolveInteractiveFramework({ perCall, configFramework:
20
+ this.config.framework, envFramework })`; `spawnSession` changes
21
+ `configFramework: undefined` → `this.config.framework`.
22
+ - `tests/unit/framework-spawn-portability.test.ts` — 10 tests: precedence of
23
+ resolveConfiguredFramework (incl. enabledFrameworks fallback + explicit-claude
24
+ env), source-grep that both spawn paths read config.framework, and Config.load
25
+ wiring (enabledFrameworks → config.framework).
26
+
27
+ **Under-block**: None. The fix makes a previously-ignored config field
28
+ authoritative. There is no path where a framework that *should* spawn is now
29
+ suppressed — per-call override and explicit config/env all still win.
30
+
31
+ **Over-block**: None. A codex-cli agent will now correctly REFUSE to silently
32
+ fall back to Claude. If a caller genuinely wants a one-off Claude session on a
33
+ Codex agent, the per-call `options.framework` override still does that — it sits
34
+ at the top of the precedence chain. So the "block" here is exactly the intended
35
+ behavior (honor the install choice), with a documented escape hatch.
36
+
37
+ **Level-of-abstraction fit**: Framework resolution stays in one place
38
+ (`resolveConfiguredFramework` / `resolveInteractiveFramework`). The spawn paths
39
+ do not re-implement precedence; they pass their three inputs and take the
40
+ answer. `enabledFrameworks` (install choice) is reduced to a single resolved
41
+ `config.framework` at load time, so SessionManager never has to know about the
42
+ array shape. Correct layering: array-of-enabled is an install concept; the
43
+ runtime sees one resolved framework.
44
+
45
+ **Signal vs authority**: Compliant. `enabledFrameworks`, env, and per-call
46
+ options are all SIGNALS fed into the resolver. The resolver
47
+ (`resolveConfiguredFramework`) is the single AUTHORITY that picks one framework.
48
+ No spawn-path makes its own framework decision anymore — both defer to the
49
+ resolved value.
50
+
51
+ **Interactions**:
52
+ - `spawnSession` (scheduled jobs) and `spawnInteractiveSession` (messages) now
53
+ resolve identically. Before, only spawnSession read env; the interactive path
54
+ was the one that hit the bug. They are now symmetric.
55
+ - `frameworkBinaryPaths[framework] ?? claudePath` fallback is unchanged — if a
56
+ codex-cli framework has no binary path mapped, it still falls back to
57
+ claudePath. That fallback is a *binary-path* concern, separate from framework
58
+ *selection*; out of scope here. For correctly-installed Codex agents the codex
59
+ binary path is populated by `detectCodexPath()` in loadConfig.
60
+ - No change to `checkFrameworkPrerequisite` or the parity sentinel — they read
61
+ `enabledFrameworks` directly and are unaffected by the new derived field.
62
+
63
+ **External surfaces**: None. No new API endpoint, no new config field the user
64
+ writes (the new `framework` is derived, not user-authored), no CLI change. The
65
+ `enabledFrameworks` field already existed and was already written by the wizard.
66
+
67
+ **Migration / existing agents**: No migration needed. The fix is a config-LOAD
68
+ derivation. Deployed Codex agents already have `enabledFrameworks: ['codex-cli']`
69
+ on disk (their install wrote it). The moment they update and the server reloads
70
+ config, `config.framework` resolves to codex-cli and both spawn paths honor it.
71
+ Verified by the Config.load wiring test.
72
+
73
+ **Rollback cost**: Trivial. Revert three source files + the test. The derived
74
+ field is optional; nothing persists it to disk, so a revert leaves no orphaned
75
+ state.
76
+
77
+ **Tests**: 10/10 new tests pass; 11/11 existing `frameworkPrerequisite.test.ts`
78
+ pass (no regression in the shared resolver). `npx tsc --noEmit` clean.
79
+ `npm run lint` clean.
80
+
81
+ **Decision-point inventory**:
82
+ 1. Derive `config.framework` at load (vs. teach every spawn path to read
83
+ `enabledFrameworks` itself) — single resolution point, single authority,
84
+ keeps SessionManager ignorant of the array shape. Chosen.
85
+ 2. `enabledFrameworks[0]` slots BELOW env (vs. above) — an explicit
86
+ `INSTAR_FRAMEWORK` env at boot is a deliberate per-boot override and should
87
+ beat the persisted install default. Matches the existing env-over-default
88
+ intent.
89
+ 3. No init-time write of `sessions.framework` (vs. backfilling it) — the
90
+ load-time derivation already fixes existing agents with zero migration; a
91
+ redundant on-disk write is deferred as unnecessary (noted out-of-scope in the
92
+ spec).