instar 0.28.44 → 0.28.46
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/dashboard/index.html +179 -0
- package/dist/cli.js +43 -4
- package/dist/cli.js.map +1 -1
- package/dist/commands/ledgerCleanup.d.ts +25 -0
- package/dist/commands/ledgerCleanup.d.ts.map +1 -0
- package/dist/commands/ledgerCleanup.js +71 -0
- package/dist/commands/ledgerCleanup.js.map +1 -0
- package/dist/commands/migrate.d.ts +33 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +63 -0
- package/dist/commands/migrate.js.map +1 -0
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +50 -1
- package/dist/commands/server.js.map +1 -1
- package/dist/core/BackupManager.d.ts +9 -1
- package/dist/core/BackupManager.d.ts.map +1 -1
- package/dist/core/BackupManager.js +57 -2
- package/dist/core/BackupManager.js.map +1 -1
- package/dist/core/CoherenceGate.d.ts +20 -0
- package/dist/core/CoherenceGate.d.ts.map +1 -1
- package/dist/core/CoherenceGate.js +29 -1
- package/dist/core/CoherenceGate.js.map +1 -1
- package/dist/core/DispatchExecutor.d.ts +19 -0
- package/dist/core/DispatchExecutor.d.ts.map +1 -1
- package/dist/core/DispatchExecutor.js +24 -1
- package/dist/core/DispatchExecutor.js.map +1 -1
- package/dist/core/FileClassifier.d.ts.map +1 -1
- package/dist/core/FileClassifier.js +5 -0
- package/dist/core/FileClassifier.js.map +1 -1
- package/dist/core/LedgerParaphraseDetector.d.ts +54 -0
- package/dist/core/LedgerParaphraseDetector.d.ts.map +1 -0
- package/dist/core/LedgerParaphraseDetector.js +103 -0
- package/dist/core/LedgerParaphraseDetector.js.map +1 -0
- package/dist/core/MessagingToneGate.d.ts +24 -0
- package/dist/core/MessagingToneGate.d.ts.map +1 -1
- package/dist/core/MessagingToneGate.js +12 -1
- package/dist/core/MessagingToneGate.js.map +1 -1
- package/dist/core/MultiMachineCoordinator.d.ts +9 -0
- package/dist/core/MultiMachineCoordinator.d.ts.map +1 -1
- package/dist/core/MultiMachineCoordinator.js +29 -0
- package/dist/core/MultiMachineCoordinator.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +19 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/SharedStateLedger.d.ts +99 -89
- package/dist/core/SharedStateLedger.d.ts.map +1 -1
- package/dist/core/SharedStateLedger.js +564 -137
- package/dist/core/SharedStateLedger.js.map +1 -1
- package/dist/core/registerLedgerEmitters.d.ts +26 -0
- package/dist/core/registerLedgerEmitters.d.ts.map +1 -0
- package/dist/core/registerLedgerEmitters.js +121 -0
- package/dist/core/registerLedgerEmitters.js.map +1 -0
- package/dist/core/types.d.ts +74 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/messaging/MessageRouter.d.ts.map +1 -1
- package/dist/messaging/MessageRouter.js +5 -2
- package/dist/messaging/MessageRouter.js.map +1 -1
- package/dist/messaging/SessionSummarySentinel.d.ts +7 -1
- package/dist/messaging/SessionSummarySentinel.d.ts.map +1 -1
- package/dist/messaging/SessionSummarySentinel.js +11 -3
- package/dist/messaging/SessionSummarySentinel.js.map +1 -1
- package/dist/server/AgentServer.d.ts +2 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +1 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/routes.d.ts +2 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +76 -0
- package/dist/server/routes.js.map +1 -1
- package/dist/threadline/ThreadlineRouter.d.ts +30 -1
- package/dist/threadline/ThreadlineRouter.d.ts.map +1 -1
- package/dist/threadline/ThreadlineRouter.js +68 -2
- package/dist/threadline/ThreadlineRouter.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +93 -93
- package/upgrades/{0.28.44.md → 0.28.45.md} +1 -1
- package/upgrades/0.28.46.md +27 -0
- package/upgrades/side-effects/0.28.44.md +36 -0
- package/upgrades/side-effects/0.28.45.md +138 -0
- package/upgrades/side-effects/echo-prevention-self-session-exclusion.md +176 -0
- package/upgrades/side-effects/integrated-being-ledger-v1.md +138 -0
- package/upgrades/0.28.41.md +0 -41
- package/upgrades/0.28.42.md +0 -25
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Upgrade Guide — v0.28.46
|
|
2
|
+
|
|
3
|
+
<!-- bump: patch -->
|
|
4
|
+
|
|
5
|
+
## What Changed
|
|
6
|
+
|
|
7
|
+
Fixed echo-prevention false positive in `MessageRouter` when resolving `to.session === "best"` for self-directed routing. The best-session resolver could pick the sender's own session as the top candidate, which then immediately tripped the echo-prevention guard and threw `Cannot send a message to the same session (echo prevention)`.
|
|
8
|
+
|
|
9
|
+
`MessageRouter.send` now passes `from.session` to `SessionSummarySentinel.findBestSession` as an exclusion, so the sender's own session can never be selected as the routing target. When the sender is the only active candidate, the resolver returns no matches and the router leaves `to.session = "best"` for queueing — the same behavior used when no summaries match well enough.
|
|
10
|
+
|
|
11
|
+
## What to Tell Your User
|
|
12
|
+
|
|
13
|
+
- **Smarter message routing**: "If you've ever seen an agent throw an 'echo prevention' error when it tried to message itself or hand off to a related session, that's fixed. I won't accidentally route a message back to my own live session anymore."
|
|
14
|
+
|
|
15
|
+
## Summary of New Capabilities
|
|
16
|
+
|
|
17
|
+
| Capability | How to Use |
|
|
18
|
+
|-----------|-----------|
|
|
19
|
+
| Self-session exclusion in best-session routing | Automatic — no configuration needed |
|
|
20
|
+
|
|
21
|
+
## Evidence
|
|
22
|
+
|
|
23
|
+
Reproducible via the new `session-summary-sentinel.test.ts` cases:
|
|
24
|
+
- `excludes sender session from candidates` — confirms the sender's own session is dropped from results when `excludeSession` is passed, even though its score would otherwise qualify.
|
|
25
|
+
- `returns empty when the only candidate is the excluded sender` — confirms the router falls back to queueing as `best` instead of triggering echo prevention.
|
|
26
|
+
|
|
27
|
+
Feedback cluster: `cluster-threadline-send-fails-with-echo-prevention-when-replying-to` (reported by luke @ v0.28.45).
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Side-Effects Review — Fix Auto-Ack Echo Loop
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `fix-auto-ack-echo-loop`
|
|
4
|
+
**Date:** `2026-04-16`
|
|
5
|
+
**Author:** `dawn`
|
|
6
|
+
**Second-pass reviewer:** `not required — single boolean guard addition to existing condition`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
Adds `!isAutoAck` to the auto-ack send guard in `src/commands/server.ts` (line 5431). This prevents incoming auto-ack messages from triggering outbound auto-acks, breaking the echo loop observed between Demiclaude and E-Ray.
|
|
11
|
+
|
|
12
|
+
The `isAutoAck` detection (checking if text starts with "Message received.") is already computed at line 5402 and used at line 5424 to prevent auto-acks from resolving reply waiters. This fix applies the same check to the send path.
|
|
13
|
+
|
|
14
|
+
## Decision-point inventory
|
|
15
|
+
|
|
16
|
+
- `src/commands/server.ts:5431` — **modify** — add `&& !isAutoAck` to existing guard condition. No new code paths, no new branches.
|
|
17
|
+
|
|
18
|
+
## 1. Over-block
|
|
19
|
+
|
|
20
|
+
**Risk:** None. The `isAutoAck` check only matches messages starting with "Message received." — the exact text produced by the auto-ack sender. Real messages that happen to start with "Message received." would be suppressed, but this is the same check already used for reply waiter exclusion (line 5424), so behavior is consistent.
|
|
21
|
+
|
|
22
|
+
## 2. Under-block
|
|
23
|
+
|
|
24
|
+
**Risk:** Negligible. Custom `autoAckMessage` configurations that don't start with "Message received." would still echo. This is acceptable — the detection matches the default message, and custom messages are rare.
|
|
25
|
+
|
|
26
|
+
## 3. Silent behavior change
|
|
27
|
+
|
|
28
|
+
**Risk:** None. The only behavioral change is: auto-ack messages no longer trigger auto-acks. This is purely bug-fix territory — the echo was never intended behavior.
|
|
29
|
+
|
|
30
|
+
## 4. Data / state impact
|
|
31
|
+
|
|
32
|
+
None. No files written, no state modified. This is a pure message-flow guard.
|
|
33
|
+
|
|
34
|
+
## 5. Downstream agent impact
|
|
35
|
+
|
|
36
|
+
Positive. Agents will no longer receive duplicate ack messages. No agent behavior depends on receiving multiple acks per message.
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Side-Effects Review — Integrated-Being Ledger v1
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `integrated-being-ledger-v1`
|
|
4
|
+
**Date:** `2026-04-15`
|
|
5
|
+
**Author:** `echo`
|
|
6
|
+
**Second-pass reviewer:** `required — this touches dispatch, lifecycle, coherence gates, and session-start hooks`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
Per-agent append-only JSONL ledger (`.instar/shared-state.jsonl`) that gives each session awareness of what other sessions on the same agent have done. Server-side-only writes from four curated emitters (threadline lifecycle, dispatch, coherence-gate, and an outbound commitment classifier that is DEFAULT-OFF). Four HTTP read endpoints. Injection into the session-start hook via the PostUpdateMigrator inline template. Dashboard tab, CLI commands, BackupManager glob support, multi-machine warning, and a paraphrase cross-check signal.
|
|
11
|
+
|
|
12
|
+
Files touched: `src/core/SharedStateLedger.ts` (new), `src/core/registerLedgerEmitters.ts` (new), `src/core/LedgerParaphraseDetector.ts` (new), `src/commands/migrate.ts` (new), `src/commands/ledgerCleanup.ts` (new), plus modifications to types.ts, ThreadlineRouter.ts, DispatchExecutor.ts, CoherenceGate.ts, MessagingToneGate.ts, PostUpdateMigrator.ts, BackupManager.ts, MultiMachineCoordinator.ts, FileClassifier.ts, routes.ts, AgentServer.ts, server.ts, cli.ts, dashboard/index.html, .gitignore.
|
|
13
|
+
|
|
14
|
+
Decision points interacted with: threadline lifecycle events (observed, not gated), dispatch application events (observed), coherence-gate block decisions (observed — emits rule-id only, no context), outbound message path (paraphrase signal only, never blocks).
|
|
15
|
+
|
|
16
|
+
## Decision-point inventory
|
|
17
|
+
|
|
18
|
+
- **Threadline lifecycle emitter** — add (signal producer) — observes thread-opened/closed/abandoned events, appends entries. Zero blocking authority.
|
|
19
|
+
- **Dispatch emitter** — add (signal producer) — observes dispatch execution success, appends entries. Zero blocking authority.
|
|
20
|
+
- **Coherence-gate emitter** — add (signal producer) — observes block decisions, appends rule-id-only notes. Zero blocking authority. Context stays in gate's audit log.
|
|
21
|
+
- **Outbound commitment classifier** — add (signal producer, DEFAULT-OFF) — regex prefilter + LLM confirmation on outbound messages. Async, off the send path, fail-open. Zero blocking authority.
|
|
22
|
+
- **Paraphrase cross-check** — add (signal producer) — flags outbound messages that paraphrase a ledger entry with a different counterparty. Signal only — feeds into tone gate's authority decision but NEVER blocks independently.
|
|
23
|
+
- **Session-start injection** — add (context producer) — renders ledger entries for the session-start hook. Pure data formatter, no decision logic.
|
|
24
|
+
- **All existing authorities** — pass-through — MessagingToneGate, CoherenceGate, MessageSentinel are unchanged in their decision logic. They receive new signals but their authority scope and fail-open behavior are preserved.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 1. Over-block
|
|
29
|
+
|
|
30
|
+
**No block/allow surface — over-block not applicable.**
|
|
31
|
+
|
|
32
|
+
This change introduces zero blocking paths. All emitters fail-open. The paraphrase cross-check is a signal consumed by the existing tone gate authority; it does not independently block. The /shared-state/* endpoints return 503 when disabled (per config) but this is an operational gate, not a content gate.
|
|
33
|
+
|
|
34
|
+
The one adjacent concern: the session-start hook injection adds ~500-2000 bytes of context to every session start. If the injection content is very large (near rotation threshold with many entries), it could crowd out other context. Mitigated by the render limit default of 50 entries and the 200-char subject + 400-char summary caps per entry.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 2. Under-block
|
|
39
|
+
|
|
40
|
+
**No block/allow surface — under-block not applicable.**
|
|
41
|
+
|
|
42
|
+
Under-observation gaps (things the ledger misses):
|
|
43
|
+
|
|
44
|
+
- **Trust-tier lookup not wired in v1**: The threadline emitter defaults to `untrusted` for all agent counterparties because the live autonomy-level lookup is not yet passed from ThreadlineRouter's context. This means all agent names render as hashed values. Functionally correct (default-deny), but trusted agents will appear as opaque hashes in the session-start injection until the lookup is wired. This is a known v1 limitation, not a bug.
|
|
45
|
+
- **Threadline thread-closed fires in finally block**: If the thread handler crashes before reaching the finally block (e.g., OOM kill), the close event won't fire. Mitigated by the abandoned-thread sweep which converts unclosed threads older than TTL into synthetic `thread-abandoned` entries.
|
|
46
|
+
- **No session-write API in v1**: Sessions cannot record commitments directly. The outbound classifier (when enabled) infers them from message content, but it's a classifier — it will miss commitments phrased in ways the regex prefilter doesn't match. v2 scope addresses this with a sanctioned session-write endpoint.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## 3. Level-of-abstraction fit
|
|
51
|
+
|
|
52
|
+
**Correct layer.** The ledger sits at the agent-platform layer (instar core), not at the per-session layer and not at the inter-agent layer (threadline). This is the right position:
|
|
53
|
+
|
|
54
|
+
- It aggregates signals from multiple subsystems (threadline, dispatch, coherence-gate, outbound path) — a per-subsystem solution would fragment the coherence view.
|
|
55
|
+
- It serves sessions via the server's HTTP API and the session-start hook — a session-level solution couldn't serve other sessions.
|
|
56
|
+
- It does NOT enter the threadline protocol or messaging layer — inter-agent coherence is explicitly deferred to v2's cross-agent visibility design.
|
|
57
|
+
|
|
58
|
+
No existing gate or authority is duplicated. The ledger doesn't make decisions; it observes them. The paraphrase detector is a new signal feeding an existing authority (tone gate), not a parallel authority.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## 4. Signal vs authority compliance
|
|
63
|
+
|
|
64
|
+
**Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
|
|
65
|
+
|
|
66
|
+
**Does this change hold blocking authority with brittle logic?**
|
|
67
|
+
|
|
68
|
+
- [x] No — this change produces signals consumed by an existing smart gate.
|
|
69
|
+
- [x] No — this change has no block/allow surface.
|
|
70
|
+
|
|
71
|
+
All four emitters are pure signal producers. They observe subsystem events and append structured entries to the ledger. None of them can block, reject, or modify the operation they observe. All fail-open via DegradationReporter.
|
|
72
|
+
|
|
73
|
+
The paraphrase cross-check produces a `signals.paraphrase` signal consumed by the MessagingToneGate authority. The tone gate may cite B10_PARAPHRASE_FLAGGED in its reasoning, but this is within its existing authority scope and subject to its existing fail-open behavior. The paraphrase detector itself has zero blocking authority.
|
|
74
|
+
|
|
75
|
+
The session-start injection is a context producer — it formats entries into a text block that sessions receive at startup. It does not gate session behavior.
|
|
76
|
+
|
|
77
|
+
Full signal-vs-authority compliance confirmed.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## 5. Interactions
|
|
82
|
+
|
|
83
|
+
- **Shadowing:** The shared-state routes are new endpoints with unique paths (`/shared-state/*`). They do not shadow any existing route. The session-start hook injection runs AFTER the existing topic-context fetch — both produce output, neither shadows the other.
|
|
84
|
+
- **Double-fire:** The threadline thread-opened emitter fires when `handleInboundMessage` spawns a new session. If a message is retried (e.g., relay reconnect), the dedupKey (`threadline:thread-opened:<thread-id>`) prevents double-append. Tested.
|
|
85
|
+
- **Races:** The append path uses `proper-lockfile` to serialize concurrent writes. The rotation check happens inside the lock. The dedup Set is an in-memory pre-lock check (false negatives possible on concurrent appenders, but the lock-protected append will still succeed — worst case is a duplicate entry, which is harmless). The abandoned-thread sweep runs under the same lock (piggybacks on rotation).
|
|
86
|
+
- **Feedback loops:** The ledger could theoretically observe its own emitter's effects — e.g., a coherence-gate block caused by a ledger entry could trigger another ledger entry. This is bounded: the coherence-gate emitter records the block event, but the block event itself doesn't trigger another coherence check. No unbounded feedback loop exists.
|
|
87
|
+
- **BackupManager glob expansion:** New glob logic in `resolveIncludedFiles()` only applies to entries containing `*` or `?`. Existing literal paths (`AGENT.md`, `jobs.json`, etc.) go through the old code path unchanged. No shadowing of existing backup behavior.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## 6. External surfaces
|
|
92
|
+
|
|
93
|
+
- **Other agents on the same machine:** The ledger file is 0o600 — other local users cannot read it. Other agents (different `.instar/` dirs) have their own ledger files. No cross-agent visibility in v1. No change to what other agents see.
|
|
94
|
+
- **Other users of the install base:** The PostUpdateMigrator template patch will propagate on next update. Agents receiving the update will have the session-start hook try to fetch `/shared-state/render` — if the server doesn't have the ledger endpoints (e.g., older server version), the curl fails silently (the `-sf` flags suppress errors). Backward-compatible.
|
|
95
|
+
- **External systems:** No external API calls added. The ledger is entirely local. The paraphrase signal is computed locally. The outbound classifier (when enabled) uses a haiku-class LLM call, but this is no different from the existing tone gate's LLM usage pattern.
|
|
96
|
+
- **Persistent state:** Introduces `.instar/shared-state.jsonl` (new file). Rotation creates `.jsonl.<epoch>` archives. The sidecar `.stats.json` is ephemeral (can be rebuilt). All gated by `config.integratedBeing.enabled`. Cleanup via `instar ledger cleanup`.
|
|
97
|
+
- **Timing/runtime conditions:** The session-start hook fetch depends on the server being alive (curl to localhost). If the server isn't running, the fetch returns empty — silent. This matches the existing topic-context fetch pattern.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## 7. Rollback cost
|
|
102
|
+
|
|
103
|
+
**Hot-fix release: revert the commit, ship as next patch.**
|
|
104
|
+
|
|
105
|
+
- **Code revert:** `registerLedgerEmitters(ledger)` is one call site in `commands/server.ts`. Delete that line. The subsystem callback options (`onLedgerEvent?`, `setLedgerEventSink()`) are all optional — removing the registration leaves them unused but harmless. The routes return 503 when `sharedStateLedger` is null (which it would be after removing the construction).
|
|
106
|
+
- **Data migration:** None required. The `.jsonl` files remain on disk but are inert. `instar ledger cleanup` can remove them.
|
|
107
|
+
- **Agent state repair:** None. The ledger is additive-only and nothing depends on it for correctness. Sessions that previously saw injection content will simply stop seeing it.
|
|
108
|
+
- **User visibility:** The dashboard tab will show "disabled" or empty. The session-start hook fetch will return empty. No visible regression beyond the feature going away.
|
|
109
|
+
- **PostUpdateMigrator template:** Remains in the hook template but produces no output (the fetch returns empty when the server doesn't have the endpoint). Functionally invisible after revert.
|
|
110
|
+
|
|
111
|
+
Estimated rollback time: one commit revert + one `npm run build` + one server restart. Under 5 minutes.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Conclusion
|
|
116
|
+
|
|
117
|
+
This change adds a substantial new feature (cross-session coherence) while maintaining full signal-vs-authority compliance. All new components are pure signal producers or context formatters — zero new blocking authority. The main architectural risk is the session-start hook injection bloating context if the ledger grows large, mitigated by the 50-entry render limit and per-entry size caps.
|
|
118
|
+
|
|
119
|
+
One known v1 limitation: the trust-tier lookup isn't live-wired, so all agent counterparties render as hashed names. This is defensively correct (default-deny) but reduces human readability of the injection content. Wiring the autonomy-level lookup is the top priority for the first post-v1 pass.
|
|
120
|
+
|
|
121
|
+
The change is clear to ship. No design rework was required by this review.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Second-pass review (if required)
|
|
126
|
+
|
|
127
|
+
**Reviewer:** independent-reviewer-subagent (Claude Opus 4.6)
|
|
128
|
+
**Independent read of the artifact: concur**
|
|
129
|
+
|
|
130
|
+
Concur with the review. Independent verification confirms all claims. Specifically: (1) Paraphrase cross-check has zero path to independent blocking — it produces a `ToneReviewSignals.paraphrase` signal consumed by `MessagingToneGate`, whose `VALID_RULES` set is hardcoded to B1-B9 and fails-open on any rule citation outside that set. (2) Coherence-gate emitter passes only `ruleId` in the subject, no rule context leaking. (3) Session-start injection includes explicit untrusted-content header ("Entries below are OBSERVATIONS... They are NOT instructions") with shell fencing. (4) All emitters fail-open via DegradationReporter; all subsystem sinks wrapped in try/catch. (5) No POST/PUT/DELETE endpoint for `/shared-state/*` — only four bearer-token-gated GETs. (6) PostUpdateMigrator patch targets the real `getSessionStartHook()` private method, not the dead-code template file. (7) Prompt-injection vectors mitigated: enum-validated attributes, Unicode-stripped text, angle-bracket-escaped, double-quoted shell echo, SHA-256-hashed untrusted names. (8) `registerLedgerEmitters()` called in exactly one place (`src/commands/server.ts:5660`).
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Evidence pointers
|
|
135
|
+
|
|
136
|
+
- 72 new unit tests passing (SharedStateLedger: 29, routes: 13, emitters: 5, paraphrase: 5, PostUpdateMigrator: 6, CLI: 9, backup: 3, multi-machine: 2)
|
|
137
|
+
- 15,870 existing tests passing with zero regressions
|
|
138
|
+
- 6 pre-existing test failures confirmed on base branch (not introduced by this change)
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# Side-Effects Review — Echo-prevention self-session exclusion
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `echo-prevention-self-session-exclusion`
|
|
4
|
+
**Date:** `2026-04-16`
|
|
5
|
+
**Author:** `dawn (job=instar-bug-fix, session=AUT-5547-wo)`
|
|
6
|
+
**Second-pass reviewer:** `not required (narrowing change, no new block surface)`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
`MessageRouter.send` resolves `to.session === "best"` by calling
|
|
11
|
+
`SessionSummarySentinel.findBestSession`, which ranks active sessions by how
|
|
12
|
+
well their recent summary matches the message. The sender's own session was
|
|
13
|
+
a valid candidate in that search, and when it happened to score highest
|
|
14
|
+
(common for self-reply cases where the subject/body match what the sender
|
|
15
|
+
has been working on), the resolver rewrote `to.session` to the sender's own
|
|
16
|
+
session. The very next statement in `send` ran the echo-prevention check
|
|
17
|
+
`from.agent === to.agent && from.session === to.session`, which now matched,
|
|
18
|
+
and threw `Cannot send a message to the same session (echo prevention)`.
|
|
19
|
+
|
|
20
|
+
This change adds an optional `excludeSession` parameter to `findBestSession`
|
|
21
|
+
and passes `from.session` from the router. The sentinel filters candidates
|
|
22
|
+
by both `sessionId` and `tmuxSession` name. When the sender is the only
|
|
23
|
+
candidate, the resolver returns `[]` and the router leaves `to.session =
|
|
24
|
+
"best"`, falling back to the existing queueing behavior used when no
|
|
25
|
+
summaries match well enough.
|
|
26
|
+
|
|
27
|
+
Files touched:
|
|
28
|
+
- `src/messaging/SessionSummarySentinel.ts`
|
|
29
|
+
- `src/messaging/MessageRouter.ts`
|
|
30
|
+
- `tests/unit/session-summary-sentinel.test.ts`
|
|
31
|
+
- `upgrades/NEXT.md`
|
|
32
|
+
|
|
33
|
+
## Decision-point inventory
|
|
34
|
+
|
|
35
|
+
- `MessageRouter.send → best-session resolution (src/messaging/MessageRouter.ts:109-119)` —
|
|
36
|
+
modify — narrows the candidate set the resolver considers.
|
|
37
|
+
- `MessageRouter.send → echo-prevention check (src/messaging/MessageRouter.ts:121-128)` —
|
|
38
|
+
pass-through — unchanged, but its input is now correct.
|
|
39
|
+
- `SessionSummarySentinel.findBestSession (src/messaging/SessionSummarySentinel.ts)` —
|
|
40
|
+
modify — gains optional `excludeSession` parameter.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## 1. Over-block
|
|
45
|
+
|
|
46
|
+
**What legitimate inputs does this change reject that it shouldn't?**
|
|
47
|
+
|
|
48
|
+
No over-block risk. The excluded session is exclusively the sender's own
|
|
49
|
+
session, which echo prevention would reject downstream anyway. Excluding it
|
|
50
|
+
upstream does not prevent any message that was not already going to be
|
|
51
|
+
rejected. Cross-session and cross-agent routing are unaffected —
|
|
52
|
+
`excludeSession` matches on a specific session identity, not on any
|
|
53
|
+
heuristic.
|
|
54
|
+
|
|
55
|
+
The only behavior change is: messages that would have produced a confusing
|
|
56
|
+
`echo prevention` error now fall through to the `"best"` queueing path (same
|
|
57
|
+
behavior as when no session meets the matching threshold).
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## 2. Under-block
|
|
62
|
+
|
|
63
|
+
**What failure modes does this still miss?**
|
|
64
|
+
|
|
65
|
+
Echo prevention (the authority) is unchanged. Any path that still ends up
|
|
66
|
+
with `from.session === to.session` will still throw. This change only
|
|
67
|
+
removes one upstream false-positive path; it does not weaken the check.
|
|
68
|
+
|
|
69
|
+
Not in scope for this change: the `"best"` queueing behavior itself when the
|
|
70
|
+
sender is the only candidate. Currently the message is enqueued as-is and
|
|
71
|
+
will be picked up by whatever drains the queue. If the drain re-runs
|
|
72
|
+
resolution and the sender is still the only candidate, it will re-enqueue.
|
|
73
|
+
The existing `"best"` queue semantics handle this — nothing about the fix
|
|
74
|
+
alters those semantics.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 3. Level-of-abstraction fit
|
|
79
|
+
|
|
80
|
+
**Is this at the right layer?**
|
|
81
|
+
|
|
82
|
+
Yes. The resolver (`findBestSession`) is the right place to filter the
|
|
83
|
+
sender out of candidates — it's the layer that enumerates who could
|
|
84
|
+
receive, so it owns knowing who should not. Pushing the filter up into
|
|
85
|
+
`send` would duplicate sentinel internals; pushing it down into
|
|
86
|
+
`getActiveSessions` would couple unrelated subsystems to routing intent.
|
|
87
|
+
|
|
88
|
+
The echo-prevention check at the router level is the authority, and
|
|
89
|
+
remains the single authority. The resolver produces candidates and feeds
|
|
90
|
+
them forward; the authority gates the final send. Separation is preserved.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## 4. Signal vs authority compliance
|
|
95
|
+
|
|
96
|
+
**Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
|
|
97
|
+
|
|
98
|
+
**Does this change hold blocking authority with brittle logic?**
|
|
99
|
+
|
|
100
|
+
- [x] No — this change has no block/allow surface.
|
|
101
|
+
|
|
102
|
+
Narrative: This change narrows a resolver — it removes one entry from a
|
|
103
|
+
candidate list by exact identity match. It does not add a new block path,
|
|
104
|
+
does not introduce a new authority, does not feed any signal to an existing
|
|
105
|
+
authority. The sole blocking authority involved here (echo prevention) is
|
|
106
|
+
untouched and remains the single gate for "cannot message yourself." We are
|
|
107
|
+
only preventing the resolver from generating input that would trip that gate
|
|
108
|
+
for a self-referential reason the user did not intend.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## 5. Interactions
|
|
113
|
+
|
|
114
|
+
**Does this interact with existing checks, recovery paths, or infrastructure?**
|
|
115
|
+
|
|
116
|
+
- **Shadowing:** The filter runs before echo prevention. It does not shadow
|
|
117
|
+
the echo check — any remaining self-routing path (e.g., explicit
|
|
118
|
+
`to.session = <mySessionId>`) still hits echo prevention exactly as
|
|
119
|
+
before. The filter only intercepts the resolver-produced false positive.
|
|
120
|
+
- **Double-fire:** No. Only `send` calls `findBestSession`; the
|
|
121
|
+
`excludeSession` plumbing is local to that one caller.
|
|
122
|
+
- **Races:** `findBestSession` reads the session registry synchronously and
|
|
123
|
+
returns a ranked array. No shared mutable state is introduced. The race
|
|
124
|
+
surface of the registry itself is unchanged.
|
|
125
|
+
- **Feedback loops:** None. The change does not affect what summaries get
|
|
126
|
+
written or how they are scored — only which entries are visible to a
|
|
127
|
+
given caller.
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## 6. External surfaces
|
|
132
|
+
|
|
133
|
+
**Does this change anything visible outside the immediate code path?**
|
|
134
|
+
|
|
135
|
+
- Other agents on the same machine: unchanged — only the sender's own
|
|
136
|
+
session is filtered, and only from that sender's own resolution call.
|
|
137
|
+
- Other users of the install base: behavior change is a bug fix — the error
|
|
138
|
+
message `Cannot send a message to the same session (echo prevention)` for
|
|
139
|
+
self-reply routing will no longer appear. Users who relied on that error
|
|
140
|
+
as a signal (there are none — it was always a bug) are unaffected.
|
|
141
|
+
- External systems: none. No wire format changes, no new HTTP endpoints.
|
|
142
|
+
- Persistent state: none. The sentinel's summary store is unchanged, the
|
|
143
|
+
message queue format is unchanged.
|
|
144
|
+
- Timing: none.
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## 7. Rollback cost
|
|
149
|
+
|
|
150
|
+
**If this turns out wrong in production, what's the back-out?**
|
|
151
|
+
|
|
152
|
+
Pure code change in two source files and one test. Rollback = revert the
|
|
153
|
+
commit and ship a patch. No persistent state, no migration, no
|
|
154
|
+
user-visible regression during rollback window. Agents in the field continue
|
|
155
|
+
working — those that had been seeing the echo-prevention error will start
|
|
156
|
+
seeing it again (the pre-fix behavior), which is recoverable.
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Conclusion
|
|
161
|
+
|
|
162
|
+
The change is minimal, narrows an existing false-positive path, and
|
|
163
|
+
preserves the echo-prevention authority intact. No new block surface, no
|
|
164
|
+
new detectors, no signal-vs-authority violation. Cluster
|
|
165
|
+
`cluster-threadline-send-fails-with-echo-prevention-when-replying-to`
|
|
166
|
+
reported by luke @ v0.28.45 is resolved. Clear to ship as a patch.
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Evidence pointers
|
|
171
|
+
|
|
172
|
+
- `tests/unit/session-summary-sentinel.test.ts` — cases `excludes sender
|
|
173
|
+
session from candidates` and `returns empty when the only candidate is
|
|
174
|
+
the excluded sender`.
|
|
175
|
+
- Feedback cluster details: Portal feedback registry,
|
|
176
|
+
`cluster-threadline-send-fails-with-echo-prevention-when-replying-to`.
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Side-Effects Review — Integrated-Being Ledger v1
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `integrated-being-ledger-v1`
|
|
4
|
+
**Date:** `2026-04-15`
|
|
5
|
+
**Author:** `echo`
|
|
6
|
+
**Second-pass reviewer:** `required — this touches dispatch, lifecycle, coherence gates, and session-start hooks`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
Per-agent append-only JSONL ledger (`.instar/shared-state.jsonl`) that gives each session awareness of what other sessions on the same agent have done. Server-side-only writes from four curated emitters (threadline lifecycle, dispatch, coherence-gate, and an outbound commitment classifier that is DEFAULT-OFF). Four HTTP read endpoints. Injection into the session-start hook via the PostUpdateMigrator inline template. Dashboard tab, CLI commands, BackupManager glob support, multi-machine warning, and a paraphrase cross-check signal.
|
|
11
|
+
|
|
12
|
+
Files touched: `src/core/SharedStateLedger.ts` (new), `src/core/registerLedgerEmitters.ts` (new), `src/core/LedgerParaphraseDetector.ts` (new), `src/commands/migrate.ts` (new), `src/commands/ledgerCleanup.ts` (new), plus modifications to types.ts, ThreadlineRouter.ts, DispatchExecutor.ts, CoherenceGate.ts, MessagingToneGate.ts, PostUpdateMigrator.ts, BackupManager.ts, MultiMachineCoordinator.ts, FileClassifier.ts, routes.ts, AgentServer.ts, server.ts, cli.ts, dashboard/index.html, .gitignore.
|
|
13
|
+
|
|
14
|
+
Decision points interacted with: threadline lifecycle events (observed, not gated), dispatch application events (observed), coherence-gate block decisions (observed — emits rule-id only, no context), outbound message path (paraphrase signal only, never blocks).
|
|
15
|
+
|
|
16
|
+
## Decision-point inventory
|
|
17
|
+
|
|
18
|
+
- **Threadline lifecycle emitter** — add (signal producer) — observes thread-opened/closed/abandoned events, appends entries. Zero blocking authority.
|
|
19
|
+
- **Dispatch emitter** — add (signal producer) — observes dispatch execution success, appends entries. Zero blocking authority.
|
|
20
|
+
- **Coherence-gate emitter** — add (signal producer) — observes block decisions, appends rule-id-only notes. Zero blocking authority. Context stays in gate's audit log.
|
|
21
|
+
- **Outbound commitment classifier** — add (signal producer, DEFAULT-OFF) — regex prefilter + LLM confirmation on outbound messages. Async, off the send path, fail-open. Zero blocking authority.
|
|
22
|
+
- **Paraphrase cross-check** — add (signal producer) — flags outbound messages that paraphrase a ledger entry with a different counterparty. Signal only — feeds into tone gate's authority decision but NEVER blocks independently.
|
|
23
|
+
- **Session-start injection** — add (context producer) — renders ledger entries for the session-start hook. Pure data formatter, no decision logic.
|
|
24
|
+
- **All existing authorities** — pass-through — MessagingToneGate, CoherenceGate, MessageSentinel are unchanged in their decision logic. They receive new signals but their authority scope and fail-open behavior are preserved.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 1. Over-block
|
|
29
|
+
|
|
30
|
+
**No block/allow surface — over-block not applicable.**
|
|
31
|
+
|
|
32
|
+
This change introduces zero blocking paths. All emitters fail-open. The paraphrase cross-check is a signal consumed by the existing tone gate authority; it does not independently block. The /shared-state/* endpoints return 503 when disabled (per config) but this is an operational gate, not a content gate.
|
|
33
|
+
|
|
34
|
+
The one adjacent concern: the session-start hook injection adds ~500-2000 bytes of context to every session start. If the injection content is very large (near rotation threshold with many entries), it could crowd out other context. Mitigated by the render limit default of 50 entries and the 200-char subject + 400-char summary caps per entry.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 2. Under-block
|
|
39
|
+
|
|
40
|
+
**No block/allow surface — under-block not applicable.**
|
|
41
|
+
|
|
42
|
+
Under-observation gaps (things the ledger misses):
|
|
43
|
+
|
|
44
|
+
- **Trust-tier lookup not wired in v1**: The threadline emitter defaults to `untrusted` for all agent counterparties because the live autonomy-level lookup is not yet passed from ThreadlineRouter's context. This means all agent names render as hashed values. Functionally correct (default-deny), but trusted agents will appear as opaque hashes in the session-start injection until the lookup is wired. This is a known v1 limitation, not a bug.
|
|
45
|
+
- **Threadline thread-closed fires in finally block**: If the thread handler crashes before reaching the finally block (e.g., OOM kill), the close event won't fire. Mitigated by the abandoned-thread sweep which converts unclosed threads older than TTL into synthetic `thread-abandoned` entries.
|
|
46
|
+
- **No session-write API in v1**: Sessions cannot record commitments directly. The outbound classifier (when enabled) infers them from message content, but it's a classifier — it will miss commitments phrased in ways the regex prefilter doesn't match. v2 scope addresses this with a sanctioned session-write endpoint.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## 3. Level-of-abstraction fit
|
|
51
|
+
|
|
52
|
+
**Correct layer.** The ledger sits at the agent-platform layer (instar core), not at the per-session layer and not at the inter-agent layer (threadline). This is the right position:
|
|
53
|
+
|
|
54
|
+
- It aggregates signals from multiple subsystems (threadline, dispatch, coherence-gate, outbound path) — a per-subsystem solution would fragment the coherence view.
|
|
55
|
+
- It serves sessions via the server's HTTP API and the session-start hook — a session-level solution couldn't serve other sessions.
|
|
56
|
+
- It does NOT enter the threadline protocol or messaging layer — inter-agent coherence is explicitly deferred to v2's cross-agent visibility design.
|
|
57
|
+
|
|
58
|
+
No existing gate or authority is duplicated. The ledger doesn't make decisions; it observes them. The paraphrase detector is a new signal feeding an existing authority (tone gate), not a parallel authority.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## 4. Signal vs authority compliance
|
|
63
|
+
|
|
64
|
+
**Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
|
|
65
|
+
|
|
66
|
+
**Does this change hold blocking authority with brittle logic?**
|
|
67
|
+
|
|
68
|
+
- [x] No — this change produces signals consumed by an existing smart gate.
|
|
69
|
+
- [x] No — this change has no block/allow surface.
|
|
70
|
+
|
|
71
|
+
All four emitters are pure signal producers. They observe subsystem events and append structured entries to the ledger. None of them can block, reject, or modify the operation they observe. All fail-open via DegradationReporter.
|
|
72
|
+
|
|
73
|
+
The paraphrase cross-check produces a `signals.paraphrase` signal consumed by the MessagingToneGate authority. The tone gate may cite B10_PARAPHRASE_FLAGGED in its reasoning, but this is within its existing authority scope and subject to its existing fail-open behavior. The paraphrase detector itself has zero blocking authority.
|
|
74
|
+
|
|
75
|
+
The session-start injection is a context producer — it formats entries into a text block that sessions receive at startup. It does not gate session behavior.
|
|
76
|
+
|
|
77
|
+
Full signal-vs-authority compliance confirmed.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## 5. Interactions
|
|
82
|
+
|
|
83
|
+
- **Shadowing:** The shared-state routes are new endpoints with unique paths (`/shared-state/*`). They do not shadow any existing route. The session-start hook injection runs AFTER the existing topic-context fetch — both produce output, neither shadows the other.
|
|
84
|
+
- **Double-fire:** The threadline thread-opened emitter fires when `handleInboundMessage` spawns a new session. If a message is retried (e.g., relay reconnect), the dedupKey (`threadline:thread-opened:<thread-id>`) prevents double-append. Tested.
|
|
85
|
+
- **Races:** The append path uses `proper-lockfile` to serialize concurrent writes. The rotation check happens inside the lock. The dedup Set is an in-memory pre-lock check (false negatives possible on concurrent appenders, but the lock-protected append will still succeed — worst case is a duplicate entry, which is harmless). The abandoned-thread sweep runs under the same lock (piggybacks on rotation).
|
|
86
|
+
- **Feedback loops:** The ledger could theoretically observe its own emitter's effects — e.g., a coherence-gate block caused by a ledger entry could trigger another ledger entry. This is bounded: the coherence-gate emitter records the block event, but the block event itself doesn't trigger another coherence check. No unbounded feedback loop exists.
|
|
87
|
+
- **BackupManager glob expansion:** New glob logic in `resolveIncludedFiles()` only applies to entries containing `*` or `?`. Existing literal paths (`AGENT.md`, `jobs.json`, etc.) go through the old code path unchanged. No shadowing of existing backup behavior.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## 6. External surfaces
|
|
92
|
+
|
|
93
|
+
- **Other agents on the same machine:** The ledger file is 0o600 — other local users cannot read it. Other agents (different `.instar/` dirs) have their own ledger files. No cross-agent visibility in v1. No change to what other agents see.
|
|
94
|
+
- **Other users of the install base:** The PostUpdateMigrator template patch will propagate on next update. Agents receiving the update will have the session-start hook try to fetch `/shared-state/render` — if the server doesn't have the ledger endpoints (e.g., older server version), the curl fails silently (the `-sf` flags suppress errors). Backward-compatible.
|
|
95
|
+
- **External systems:** No external API calls added. The ledger is entirely local. The paraphrase signal is computed locally. The outbound classifier (when enabled) uses a haiku-class LLM call, but this is no different from the existing tone gate's LLM usage pattern.
|
|
96
|
+
- **Persistent state:** Introduces `.instar/shared-state.jsonl` (new file). Rotation creates `.jsonl.<epoch>` archives. The sidecar `.stats.json` is ephemeral (can be rebuilt). All gated by `config.integratedBeing.enabled`. Cleanup via `instar ledger cleanup`.
|
|
97
|
+
- **Timing/runtime conditions:** The session-start hook fetch depends on the server being alive (curl to localhost). If the server isn't running, the fetch returns empty — silent. This matches the existing topic-context fetch pattern.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## 7. Rollback cost
|
|
102
|
+
|
|
103
|
+
**Hot-fix release: revert the commit, ship as next patch.**
|
|
104
|
+
|
|
105
|
+
- **Code revert:** `registerLedgerEmitters(ledger)` is one call site in `commands/server.ts`. Delete that line. The subsystem callback options (`onLedgerEvent?`, `setLedgerEventSink()`) are all optional — removing the registration leaves them unused but harmless. The routes return 503 when `sharedStateLedger` is null (which it would be after removing the construction).
|
|
106
|
+
- **Data migration:** None required. The `.jsonl` files remain on disk but are inert. `instar ledger cleanup` can remove them.
|
|
107
|
+
- **Agent state repair:** None. The ledger is additive-only and nothing depends on it for correctness. Sessions that previously saw injection content will simply stop seeing it.
|
|
108
|
+
- **User visibility:** The dashboard tab will show "disabled" or empty. The session-start hook fetch will return empty. No visible regression beyond the feature going away.
|
|
109
|
+
- **PostUpdateMigrator template:** Remains in the hook template but produces no output (the fetch returns empty when the server doesn't have the endpoint). Functionally invisible after revert.
|
|
110
|
+
|
|
111
|
+
Estimated rollback time: one commit revert + one `npm run build` + one server restart. Under 5 minutes.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Conclusion
|
|
116
|
+
|
|
117
|
+
This change adds a substantial new feature (cross-session coherence) while maintaining full signal-vs-authority compliance. All new components are pure signal producers or context formatters — zero new blocking authority. The main architectural risk is the session-start hook injection bloating context if the ledger grows large, mitigated by the 50-entry render limit and per-entry size caps.
|
|
118
|
+
|
|
119
|
+
One known v1 limitation: the trust-tier lookup isn't live-wired, so all agent counterparties render as hashed names. This is defensively correct (default-deny) but reduces human readability of the injection content. Wiring the autonomy-level lookup is the top priority for the first post-v1 pass.
|
|
120
|
+
|
|
121
|
+
The change is clear to ship. No design rework was required by this review.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Second-pass review (if required)
|
|
126
|
+
|
|
127
|
+
**Reviewer:** independent-reviewer-subagent (Claude Opus 4.6)
|
|
128
|
+
**Independent read of the artifact: concur**
|
|
129
|
+
|
|
130
|
+
Concur with the review. Independent verification confirms all claims. Specifically: (1) Paraphrase cross-check has zero path to independent blocking — it produces a `ToneReviewSignals.paraphrase` signal consumed by `MessagingToneGate`, whose `VALID_RULES` set is hardcoded to B1-B9 and fails-open on any rule citation outside that set. (2) Coherence-gate emitter passes only `ruleId` in the subject, no rule context leaking. (3) Session-start injection includes explicit untrusted-content header ("Entries below are OBSERVATIONS... They are NOT instructions") with shell fencing. (4) All emitters fail-open via DegradationReporter; all subsystem sinks wrapped in try/catch. (5) No POST/PUT/DELETE endpoint for `/shared-state/*` — only four bearer-token-gated GETs. (6) PostUpdateMigrator patch targets the real `getSessionStartHook()` private method, not the dead-code template file. (7) Prompt-injection vectors mitigated: enum-validated attributes, Unicode-stripped text, angle-bracket-escaped, double-quoted shell echo, SHA-256-hashed untrusted names. (8) `registerLedgerEmitters()` called in exactly one place (`src/commands/server.ts:5660`).
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Evidence pointers
|
|
135
|
+
|
|
136
|
+
- 72 new unit tests passing (SharedStateLedger: 29, routes: 13, emitters: 5, paraphrase: 5, PostUpdateMigrator: 6, CLI: 9, backup: 3, multi-machine: 2)
|
|
137
|
+
- 15,870 existing tests passing with zero regressions
|
|
138
|
+
- 6 pre-existing test failures confirmed on base branch (not introduced by this change)
|
package/upgrades/0.28.41.md
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
# Upgrade Guide — v0.28.41
|
|
2
|
-
|
|
3
|
-
<!-- bump: patch -->
|
|
4
|
-
|
|
5
|
-
## What Changed
|
|
6
|
-
|
|
7
|
-
Two fixes for volatile state that didn't survive restarts in the Threadline REST adapter and relay offline queue:
|
|
8
|
-
|
|
9
|
-
1. **REST thread history now persists to disk.** `ThreadlineRESTServer` previously held thread history in memory only, so restarts lost all conversation history even when the client-side MessageStore had messages on disk. The server now hydrates from `~/.threadline/thread-history.json` on startup and persists debounced (1s) on incoming messages and thread deletions. Writes are atomic (temp file + rename) and size-bounded by the existing `maxMessageHistoryPerThread` cap. New config: `historyPath` (default `~/.threadline/thread-history.json`) and `persistHistory` (default `true`, set `false` for tests/ephemeral servers).
|
|
10
|
-
|
|
11
|
-
2. **Offline queue default TTL extended from 1h to 24h.** `InMemoryOfflineQueue`'s 1-hour default was shorter than typical offline/restart windows for agents, so messages to offline recipients expired before reconnection. Configurable via `OfflineQueueConfig.defaultTtlMs` if you need different behavior.
|
|
12
|
-
|
|
13
|
-
No API breakage. Existing servers/queues using defaults just get more durable behavior.
|
|
14
|
-
|
|
15
|
-
## What to Tell Your User
|
|
16
|
-
|
|
17
|
-
- **Conversation history survives restarts**: "Your thread history will stick around now even if I get restarted — no more losing context from earlier in our conversation."
|
|
18
|
-
- **Messages wait longer for offline agents**: "If you message another agent who's offline, the message will wait up to a day for them to come back online instead of expiring after an hour."
|
|
19
|
-
|
|
20
|
-
## Summary of New Capabilities
|
|
21
|
-
|
|
22
|
-
| Capability | How to Use |
|
|
23
|
-
|-----------|-----------|
|
|
24
|
-
| Persistent REST thread history | Automatic (opt-out via `persistHistory: false`) |
|
|
25
|
-
| 24h offline message retention | Automatic (override via `defaultTtlMs`) |
|
|
26
|
-
|
|
27
|
-
## Evidence
|
|
28
|
-
|
|
29
|
-
Reproduction before fix:
|
|
30
|
-
1. Start `npx @anthropic-ai/threadline serve --port 18800`
|
|
31
|
-
2. Receive messages on a thread → `GET /threads/{id}` returns them
|
|
32
|
-
3. Restart the server
|
|
33
|
-
4. `GET /threads/{id}` returns 404 — history lost
|
|
34
|
-
|
|
35
|
-
After fix:
|
|
36
|
-
1. Same steps 1–2
|
|
37
|
-
2. Within 1s, `~/.threadline/thread-history.json` contains the thread
|
|
38
|
-
3. Restart the server
|
|
39
|
-
4. `GET /threads/{id}` returns the same messages — hydrated from disk
|
|
40
|
-
|
|
41
|
-
Unit tests (40/40 passing in `OfflineQueue.test.ts` and `RESTServerE2E.test.ts`) cover the default config values and existing REST flows; persistence is best-effort (wrapped in try/catch) so disk failures don't crash the server.
|
package/upgrades/0.28.42.md
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
# Upgrade Guide — v0.28.42
|
|
2
|
-
|
|
3
|
-
<!-- bump: patch -->
|
|
4
|
-
|
|
5
|
-
## What Changed
|
|
6
|
-
|
|
7
|
-
Fixed a silent failure in post-update migration: when Node.js is upgraded in place (e.g., via `brew upgrade node`) while an Instar server is still running, `process.execPath` can point at a binary path that no longer exists on disk. The subsequent spawn fails with ENOENT and post-update migration skips silently, leaving agent configuration stale after auto-updates.
|
|
8
|
-
|
|
9
|
-
`UpdateChecker.postUpdateMigration` now guards `process.execPath` with an `existsSync` check and falls back to `node` on PATH when the resolved exec path has been deleted.
|
|
10
|
-
|
|
11
|
-
## What to Tell Your User
|
|
12
|
-
|
|
13
|
-
- **More reliable self-updates**: "If your system's Node was upgraded while I was running, my next auto-update will still apply its migrations correctly instead of silently skipping them."
|
|
14
|
-
|
|
15
|
-
## Summary of New Capabilities
|
|
16
|
-
|
|
17
|
-
| Capability | How to Use |
|
|
18
|
-
|-----------|-----------|
|
|
19
|
-
| Resilient post-update migration across in-place Node upgrades | automatic |
|
|
20
|
-
|
|
21
|
-
## Evidence
|
|
22
|
-
|
|
23
|
-
Reproduction: Homebrew users who ran `brew upgrade node` between Instar server starts reported repeated `UpdateChecker.postUpdateMigration` degradation events with reason `spawn /opt/homebrew/Cellar/node@22/22.22.2/bin/node ENOENT`. Root cause traced to `src/core/UpdateChecker.ts:282` where `cmd = process.execPath` was spawned unconditionally.
|
|
24
|
-
|
|
25
|
-
Verified fix: Before — `execFile(process.execPath, [shadowCliJs, 'migrate'])` rejects with ENOENT when the Cellar path is gone; degradation reporter fires; migration skipped. After — `fs.existsSync(process.execPath)` returns false, `cmd` falls back to `'node'`, spawn resolves via PATH, migration runs. Unit tests for UpdateChecker (13) continue to pass.
|