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.
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +30 -1
- package/dist/commands/server.js.map +1 -1
- package/dist/core/Config.d.ts +1 -1
- package/dist/core/Config.d.ts.map +1 -1
- package/dist/core/Config.js +26 -2
- package/dist/core/Config.js.map +1 -1
- package/dist/core/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js +21 -9
- package/dist/core/SessionManager.js.map +1 -1
- package/dist/core/types.d.ts +23 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/monitoring/SessionActivitySentinel.d.ts +16 -0
- package/dist/monitoring/SessionActivitySentinel.d.ts.map +1 -1
- package/dist/monitoring/SessionActivitySentinel.js +18 -0
- package/dist/monitoring/SessionActivitySentinel.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +3 -3
- package/upgrades/1.2.29.md +24 -0
- package/upgrades/1.2.31.md +90 -0
- package/upgrades/1.2.32.md +37 -0
- package/upgrades/side-effects/feat-periodic-sentinel-scan.md +65 -0
- package/upgrades/side-effects/framework-spawn-portability.md +92 -0
|
@@ -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).
|