instar 1.2.70 → 1.2.72

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,52 @@
1
+ # Upgrade Guide — tunable topic-intent decay horizons
2
+
3
+ <!-- bump: minor -->
4
+ <!-- minor = new features, new APIs, new capabilities (backwards-compatible) -->
5
+
6
+ ## What Changed
7
+
8
+ **You can now tune how fast each kind of memory fades, from config.**
9
+
10
+ Rung 1 gave topic-intent memories different "shelf lives" — a method ("we're
11
+ testing over Telegram") fades in about a week, an audience in about a month,
12
+ facts and decisions over months. Those numbers were code constants. This ships
13
+ the tracked `cwa-decay-profile-config` follow-up: they're now overridable via
14
+ config, so we can tune them from real data without a code change.
15
+
16
+ `topicIntent.capture.decayProfiles` takes any subset of refkinds
17
+ (method/audience/goal/fact/decision) and any subset of `{graceDays, halfLifeDays}`;
18
+ anything you don't specify keeps the built-in default. Invalid values (zero,
19
+ negative, non-finite) are ignored — a bad config can never break decay; it just
20
+ falls back to the default. Set nothing and behavior is exactly as before.
21
+
22
+ Example: slow down how fast a "method" frame fades —
23
+ `{"topicIntent":{"capture":{"decayProfiles":{"method":{"halfLifeDays":21}}}}}`.
24
+
25
+ **Evidence**: 6 unit tests (defaults, partial override, invalid-ignored,
26
+ idempotent, reset, and that an override actually changes projected decay). The
27
+ rung-0/rung-1 decay math is unchanged when no override is set (existing
28
+ projection tests green). `tsc` + lint clean.
29
+
30
+ Spec: `docs/specs/topic-intent-task-context-capture.md` §3 (the
31
+ `cwa-decay-profile-config` tracked deferral, now shipped). Side-effects:
32
+ `upgrades/side-effects/cwa-decay-profile-config.md`.
33
+
34
+ ## What to Tell Your User
35
+
36
+ - **Tune memory shelf-lives**: "How fast I forget different kinds of context is
37
+ now adjustable in config — if a 'how we're working' note fades too fast or
38
+ sticks too long, we can dial it without changing code."
39
+
40
+ ## Summary of New Capabilities
41
+
42
+ | Capability | How to Use |
43
+ |-----------|-----------|
44
+ | Override topic-intent decay horizons | `topicIntent.capture.decayProfiles.<kind>.{graceDays,halfLifeDays}` in `.instar/config.json` (optional; omitted → defaults) |
45
+
46
+ ## Evidence
47
+
48
+ Not a bug fix — a tuning knob over the rung-1 decay model. Verified by 6 unit
49
+ tests including one that confirms an override actually changes projected decay
50
+ (a method ref barely decays by day 8 under a long-horizon override vs. ~halving
51
+ under the default). No-override behavior is byte-for-byte the prior defaults.
52
+ `tsc` + lint clean.
@@ -0,0 +1,23 @@
1
+ # Upgrade Notes — NEXT
2
+
3
+ ## What Changed
4
+
5
+ Fixed a P0 safety gap: the **emergency-stop** ("stop everything" / "cancel" / "halt") detector was not honored for agents that run in lifeline-owned polling mode. The MessageSentinel intercept lived only in the Telegram adapter's own poll loop (`processUpdate`), which lifeline-owned agents never run — their messages arrive through the server's `/internal/telegram-forward` route, which had no sentinel check. As a result, an emergency-stop message was delivered to the session as ordinary text and never structurally halted a running (or wedged, mid-tool-call) session.
6
+
7
+ The same emergency-stop/pause intercept is now wired into the lifeline forward path, reusing the existing kill/pause/autonomous-clear logic. It is **fail-open**: if the detector ever errors, the message is delivered normally — the safety check can never block message delivery. A wiring-integrity test now asserts that the forward route classifies before routing, so this drift cannot silently recur.
8
+
9
+ ## What to Tell Your User
10
+
11
+ Saying "stop everything" now reliably halts your agent, no matter how its messages reach it. Before this fix, agents running in the more crash-resistant background mode could miss that command and keep working. Nothing changes for normal messages — only genuine stop and pause signals are intercepted, and if the safety check ever has a hiccup your messages still get through untouched.
12
+
13
+ ## Summary of New Capabilities
14
+
15
+ - Emergency-stop and pause now fire on the lifeline message-forwarding path, not just the direct-polling path — closing a gap for the most robust class of agents.
16
+ - A regression guard (wiring-integrity test) keeps the stop-switch connected to every inbound path going forward.
17
+
18
+ ## Evidence
19
+
20
+ - **Reproduction (pre-fix):** live classify call returned `emergency-stop` for "stop everything"; a code-path trace confirmed `/internal/telegram-forward` (the live ingress for lifeline-owned agents) contained zero sentinel references, so the message was routed to the session as normal text.
21
+ - **Post-fix proof:** new integration suite `tests/integration/telegram-forward-sentinel-intercept.test.ts` — 6/6 green, covering both sides of every boundary: emergency-stop kills the session and is not routed; pause pauses and is not routed; normal messages route untouched; a throwing detector still delivers the message (fail-open); and a no-active-session emergency-stop acknowledges without routing. Plus a wiring-integrity assertion that classification precedes routing.
22
+ - **Regression:** forward-route suite green (10/10 on the rebased branch; 23/23 including the full handshake/drift/error set) — no regression. Typecheck clean.
23
+ - **Side-effects review:** `upgrades/side-effects/emergency-stop-forward-path-wiring.md`.
@@ -0,0 +1,58 @@
1
+ # Side-effects review — config-overridable decay profiles (cwa-decay-profile-config)
2
+
3
+ **Scope**: Ship the tracked `cwa-decay-profile-config` deferral from the rung-1
4
+ task-context spec (`docs/specs/topic-intent-task-context-capture.md` §3, approved):
5
+ let operators tune the per-kind decay horizons (method/audience/goal/fact/decision)
6
+ via config instead of code constants, so the short/medium/long numbers become
7
+ tunable from real data without a code change.
8
+
9
+ **Files touched**:
10
+ - `src/core/TopicIntent.ts` — split `DECAY_PROFILES` into `DEFAULT_DECAY_PROFILES`
11
+ (the baseline) + a mutable `activeDecayProfiles`; add `configureDecayProfiles(overrides)`
12
+ (existence-checked, validated, idempotent — always re-derives from defaults),
13
+ `resetDecayProfiles()` (test isolation), and `DecayProfileOverrides` type.
14
+ `decayProfileFor` now reads the active profiles. `projectConfidence` is
15
+ unchanged (still pure w.r.t. its args; it calls `decayProfileFor`).
16
+ - `src/core/types.ts` — `topicIntent.capture.decayProfiles?` config field.
17
+ - `src/commands/server.ts` — call `configureDecayProfiles(config.topicIntent?.capture?.decayProfiles)`
18
+ once at startup, right after the TopicIntentStore is constructed.
19
+ - `tests/unit/TopicIntent-decay-profile-config.test.ts` — 6 tests (defaults,
20
+ partial override, invalid-ignored, idempotent, reset, projection-reflects-override).
21
+
22
+ **Under-block**: An override only changes the numbers it specifies; everything
23
+ else keeps the default. Invalid values (non-finite / ≤ 0) are silently ignored,
24
+ so a malformed config can never break decay (the loop falls back to defaults).
25
+
26
+ **Over-block**: None. This is a tuning knob, not a gate.
27
+
28
+ **Level-of-abstraction fit**: Decay horizons are process-wide *policy*, so a
29
+ module-level configurable map (set once at startup) is the right level — not
30
+ per-call state. `projectConfidence` keeps its signature and purity-w.r.t-args;
31
+ the only behavioral input it gains is the global policy via `decayProfileFor`,
32
+ which already read a module constant. `configureDecayProfiles` always re-derives
33
+ from `DEFAULT_DECAY_PROFILES` so it's idempotent and order-independent.
34
+
35
+ **Signal vs authority**: N/A (a tuning value, no decision authority).
36
+
37
+ **Interactions**:
38
+ - `configureDecayProfiles` is called once at server startup. If never called
39
+ (e.g. tests, or a path that doesn't boot the server), the defaults are in
40
+ effect — identical to pre-change behavior. **Rung-0/rung-1 decay math is
41
+ unchanged when no override is set** (fact/decision stay 30/180; task kinds keep
42
+ their defaults), so existing projection tests are unaffected.
43
+ - `resetDecayProfiles()` exists for test isolation (module-global state); tests
44
+ call it in `afterEach`.
45
+ - Idempotent + validated → re-running migration / re-reading config is safe.
46
+
47
+ **External surfaces**: New config field `topicIntent.capture.decayProfiles`
48
+ (optional, existence-checked). New exported `configureDecayProfiles`,
49
+ `resetDecayProfiles`, `DecayProfileOverrides`. No new endpoint.
50
+
51
+ **Rollback cost**: Low. Revert `decayProfileFor` to read a const + drop the
52
+ config field + the startup call; behavior returns to fixed code-constant
53
+ profiles (today's state). No persisted state involved.
54
+
55
+ **Migration parity**: Pure config-read at startup (server-side; every agent
56
+ gets the capability on update). The field is opt-in with an existence check, so
57
+ no `ConfigDefaults` entry is required — absence → built-in defaults. No
58
+ hook/template/skill change.
@@ -0,0 +1,94 @@
1
+ # Side-Effects Review — Emergency-stop on the lifeline forward path
2
+
3
+ **Version / slug:** `emergency-stop-forward-path-wiring`
4
+ **Date:** `2026-05-24`
5
+ **Author:** `echo`
6
+ **Second-pass reviewer:** `not required (single localized, fail-open route addition; spec-driven)`
7
+
8
+ ## Summary of the change
9
+
10
+ Wires the existing `MessageSentinel` emergency-stop/pause intercept into `POST /internal/telegram-forward` (the lifeline ingress path) so that lifeline-owned-polling agents — which never run `TelegramAdapter.processUpdate()`, where the intercept currently lives — actually honor "stop everything". Before this, those agents (e.g. echo) delivered emergency-stop messages to the session as normal text and nothing halted a running/wedged session. One file touched: `src/server/routes.ts` (a ~50-line block inserted after request validation, before message logging/routing). Reuses the existing `ctx.telegram.onSentinelKillSession`/`onSentinelPauseSession` callbacks and `stopAutonomousTopic`; adds no new kill logic. Spec: `docs/specs/emergency-stop-forward-path-wiring.md`.
11
+
12
+ ## Decision-point inventory
13
+
14
+ - `MessageSentinel.classify` (existing authority) — **pass-through** — its verdict is unchanged; this change only routes the verdict to an action on a path that previously ignored it.
15
+ - `/internal/telegram-forward` routing decision — **modify** — adds an emergency-stop/pause short-circuit before the existing `onTopicMessage`/registry-inject routing. Normal messages are unaffected.
16
+ - Session kill / pause / autonomous-clear — **pass-through** — reuses `onSentinelKillSession`, `onSentinelPauseSession`, `stopAutonomousTopic` exactly as the `processUpdate` path does.
17
+
18
+ ---
19
+
20
+ ## 1. Over-block
21
+
22
+ **What legitimate inputs does this change reject that it shouldn't?**
23
+
24
+ Only messages the sentinel classifies as `emergency-stop` or `pause` are intercepted (not routed to the session). That is the intended behavior and matches the already-live `processUpdate` path. The classifier's own over-block surface is unchanged (live-tested: a long sentence merely containing "stop" → `normal`, routed normally). A false-positive emergency-stop would terminate the session — but (a) the classifier already gates this with word-count + exact/regex/LLM disambiguation, and (b) the user can simply send a new message to respawn; no data loss (resume UUID is saved by `onSentinelKillSession`). No new over-block beyond what `processUpdate` already exhibits.
25
+
26
+ ---
27
+
28
+ ## 2. Under-block
29
+
30
+ **What failure modes does this still miss?**
31
+
32
+ - A genuinely-wedged classifier (LLM provider down) → fail-open → the emergency-stop is NOT honored (message routes normally). This is the deliberate trade: never block delivery. The deterministic exact/regex patterns ("stop", "cancel", "halt") do not require the LLM, so the most common emergency phrasings still classify without a provider.
33
+ - Non-Telegram lifeline paths (none currently) would need the same treatment.
34
+ - This change does not address the still-dark `processUpdate`-only assumption for any *other* per-message safety hook; scope is the sentinel only.
35
+
36
+ ---
37
+
38
+ ## 3. Level-of-abstraction fit
39
+
40
+ **Is this at the right layer?**
41
+
42
+ Yes. The smart authority (`MessageSentinel`, LLM + deterministic patterns) already exists and already owns the decision. This change is pure routing: it ensures the authority's verdict reaches the action on the server-side ingress path that lifeline-owned agents use. It does not re-implement classification (would be the wrong layer); it consumes the existing authority's output. It places the intercept at the server route layer — the correct single choke-point for the lifeline path, mirroring where `processUpdate` sits for the adapter-poll path.
43
+
44
+ ---
45
+
46
+ ## 4. Signal vs authority compliance
47
+
48
+ **Required reference:** docs/signal-vs-authority.md
49
+
50
+ - [x] No — this change produces no new block/allow logic; it routes an existing smart-gate (`MessageSentinel`, LLM-backed with deterministic patterns) verdict to its action.
51
+
52
+ The sentinel remains the sole authority. The forward route is a consumer of its verdict, not a new detector. No brittle logic gains blocking authority. (And the action is fail-open: a classifier error degrades to normal delivery, never to a wrong block.)
53
+
54
+ ---
55
+
56
+ ## 5. Interactions
57
+
58
+ - **Shadowing:** The new block runs AFTER the version-handshake (426/400 still short-circuit first) and BEFORE message logging + `onTopicMessage` routing. It does not shadow the handshake. It intentionally shadows routing for emergency-stop/pause (that's the point) — confirmed `logInboundMessage` is skipped for intercepted messages, which is acceptable (an emergency-stop need not be logged as a conversational turn; the kill is logged to the server log).
59
+ - **Double-fire:** For lifeline-owned agents, `processUpdate` does not run, so there is no double-intercept. For adapter-poll agents, the forward route is not used, so again no double-fire. The two paths are mutually exclusive per deployment mode. No agent runs both for the same message.
60
+ - **Races:** `onSentinelKillSession` already encapsulates resume-UUID save + kill (its existing concurrency behavior is unchanged). `stopAutonomousTopic` is idempotent (best-effort, try/catch). Session resolution reads the registry file read-only.
61
+ - **Feedback loops:** None. The intercept terminates routing; it does not feed back into the sentinel.
62
+
63
+ ---
64
+
65
+ ## 6. External surfaces
66
+
67
+ - **Other agents on same machine:** none — server-local route logic.
68
+ - **Install base:** ships to every agent via the normal server build. Adapter-poll agents are unaffected (path unused); lifeline-owned agents gain the (intended) emergency-stop. No agent-installed file (hook/config/template) changes → no `PostUpdateMigrator` work.
69
+ - **External systems:** on emergency-stop/pause the route now sends one Telegram message ("Session terminated." / "Session paused." / "No active session to stop."), matching the `processUpdate` user-facing copy. No new external endpoints.
70
+ - **Persistent state:** none added. Reads `topic-session-registry.json` read-only; `onSentinelKillSession` writes the resume-UUID map exactly as today.
71
+ - **Response shape:** the route now may return `{ ok:true, sentinel:'emergency-stop'|'pause', killed|paused }` instead of `{ ok:true, forwarded:true }` for intercepted messages. The lifeline treats any 200 as delivered; the new fields are additive and ignored by existing callers.
72
+
73
+ ---
74
+
75
+ ## 7. Rollback cost
76
+
77
+ Pure code change in one route, fail-open by design. Back-out = revert the block; behavior returns to current (sentinel dark on the forward path). No persistent state, no schema, no migration, no agent-state repair. The fail-open guarantee means even a bug in the block degrades to "behaves like today" (message routes), never to blocked delivery — so the rollback urgency is low even if a defect ships.
78
+
79
+ ## Conclusion
80
+
81
+ The review produced no design changes — the fix is a faithful port of the already-tested `processUpdate` intercept to the lifeline ingress path, consuming the existing sentinel authority, fail-open. The only behavioral change is that emergency-stop/pause now fire for lifeline-owned agents, which is the intended P0 safety fix. Integration tests cover both sides of every boundary (kill/pause/normal/fail-open/no-session) plus a wiring-integrity assertion that classification precedes routing (the guard whose absence caused the original drift). Clear to ship. Live end-to-end verification (real Telegram "stop everything" terminating a real session) occurs post-deploy, since it requires the merged build running; pre-merge proof is the integration suite exercising the real route handler.
82
+
83
+ ---
84
+
85
+ ## Second-pass review (if required)
86
+
87
+ Not required — single localized, fail-open route addition with full test coverage and no new decision logic.
88
+
89
+ ---
90
+
91
+ ## Evidence pointers
92
+
93
+ - Reproduction (pre-fix): live `POST /sentinel/classify` returns `emergency-stop` for "stop everything"; code trace shows `/internal/telegram-forward` (the live path for lifeline-owned echo) had zero sentinel references → message routed as normal.
94
+ - Post-fix proof: `tests/integration/telegram-forward-sentinel-intercept.test.ts` — 6/6 green (emergency-stop kills + not-routed; pause; normal-routes; fail-open-routes; no-session; wiring-integrity classify-before-route).
@@ -0,0 +1,48 @@
1
+ # Side-effects review — `instar spec conformance` CLI (scg-cli)
2
+
3
+ **Scope**: Ship the tracked `scg-cli` deferral from the standards-conformance-gate
4
+ spec (`docs/specs/standards-conformance-gate.md`, approved/merged in #373): a
5
+ command-line entry point to the conformance gate, so a spec can be checked
6
+ against the constitution without curl. Thin client over the existing
7
+ `POST /spec/conformance-check` route.
8
+
9
+ **Files touched**:
10
+ - `src/commands/spec.ts` — NEW. `runSpecConformance({specPath,dir,port,json})`:
11
+ reads the spec file, resolves port + authToken from `loadConfig`, POSTs
12
+ `{markdown}` to the local server's conformance route, prints a formatted
13
+ rule-by-rule report (or `--json`). Server-down / 503 / missing-file are clear
14
+ errors with non-zero exit.
15
+ - `src/cli.ts` — register `instar spec conformance <path>` (a `spec` command group
16
+ + `conformance` subcommand) using the established Commander + `loadConfig` pattern.
17
+ - `tests/unit/spec-conformance-cli.test.ts` — 5 tests (posts markdown, prints
18
+ findings, clean-pass, degraded-advisory, --json, missing-file exit).
19
+
20
+ **Under-block / over-block**: None. The CLI only *reads* a spec and *prints* a
21
+ report; it has no authority (mirrors the route's signal-only nature). It cannot
22
+ block anything.
23
+
24
+ **Level-of-abstraction fit**: The CLI is a thin presentation layer over the
25
+ already-built route + reviewer; it duplicates no logic. It reuses the standard
26
+ CLI→local-server pattern (`loadConfig` → port/authToken → fetch), so the
27
+ subscription-backed intelligence provider stays server-side (the CLI never
28
+ constructs an LLM client).
29
+
30
+ **Signal vs authority**: Inherits the route's signal-only posture — prints
31
+ "possible violations (you decide)", never a pass/fail verdict that gates anything.
32
+
33
+ **Interactions**:
34
+ - Requires the local server running (it's a client). Server-down → a clear
35
+ "Is the server running?" error + exit 1, not a crash.
36
+ - Reads `config.port` (so it targets the agent's real port, e.g. 4042) with a 4040
37
+ fallback; reads `authToken` for the Bearer header.
38
+ - 60s fetch timeout (a `capable`-tier conformance check can take a while).
39
+
40
+ **External surfaces**: New CLI subcommand `instar spec conformance <path>
41
+ [--dir] [--port] [--json]`. New exported `runSpecConformance`. No new endpoint,
42
+ no config change.
43
+
44
+ **Rollback cost**: Trivial. Remove the command registration + `src/commands/spec.ts`
45
+ + the test. The route it calls is unaffected.
46
+
47
+ **Migration parity**: CLI code only (shipped in the package; every agent gets it
48
+ on update). No hook/template/config/skill change.