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.
Files changed (83) hide show
  1. package/dashboard/index.html +179 -0
  2. package/dist/cli.js +43 -4
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/ledgerCleanup.d.ts +25 -0
  5. package/dist/commands/ledgerCleanup.d.ts.map +1 -0
  6. package/dist/commands/ledgerCleanup.js +71 -0
  7. package/dist/commands/ledgerCleanup.js.map +1 -0
  8. package/dist/commands/migrate.d.ts +33 -0
  9. package/dist/commands/migrate.d.ts.map +1 -0
  10. package/dist/commands/migrate.js +63 -0
  11. package/dist/commands/migrate.js.map +1 -0
  12. package/dist/commands/server.d.ts.map +1 -1
  13. package/dist/commands/server.js +50 -1
  14. package/dist/commands/server.js.map +1 -1
  15. package/dist/core/BackupManager.d.ts +9 -1
  16. package/dist/core/BackupManager.d.ts.map +1 -1
  17. package/dist/core/BackupManager.js +57 -2
  18. package/dist/core/BackupManager.js.map +1 -1
  19. package/dist/core/CoherenceGate.d.ts +20 -0
  20. package/dist/core/CoherenceGate.d.ts.map +1 -1
  21. package/dist/core/CoherenceGate.js +29 -1
  22. package/dist/core/CoherenceGate.js.map +1 -1
  23. package/dist/core/DispatchExecutor.d.ts +19 -0
  24. package/dist/core/DispatchExecutor.d.ts.map +1 -1
  25. package/dist/core/DispatchExecutor.js +24 -1
  26. package/dist/core/DispatchExecutor.js.map +1 -1
  27. package/dist/core/FileClassifier.d.ts.map +1 -1
  28. package/dist/core/FileClassifier.js +5 -0
  29. package/dist/core/FileClassifier.js.map +1 -1
  30. package/dist/core/LedgerParaphraseDetector.d.ts +54 -0
  31. package/dist/core/LedgerParaphraseDetector.d.ts.map +1 -0
  32. package/dist/core/LedgerParaphraseDetector.js +103 -0
  33. package/dist/core/LedgerParaphraseDetector.js.map +1 -0
  34. package/dist/core/MessagingToneGate.d.ts +24 -0
  35. package/dist/core/MessagingToneGate.d.ts.map +1 -1
  36. package/dist/core/MessagingToneGate.js +12 -1
  37. package/dist/core/MessagingToneGate.js.map +1 -1
  38. package/dist/core/MultiMachineCoordinator.d.ts +9 -0
  39. package/dist/core/MultiMachineCoordinator.d.ts.map +1 -1
  40. package/dist/core/MultiMachineCoordinator.js +29 -0
  41. package/dist/core/MultiMachineCoordinator.js.map +1 -1
  42. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  43. package/dist/core/PostUpdateMigrator.js +19 -0
  44. package/dist/core/PostUpdateMigrator.js.map +1 -1
  45. package/dist/core/SharedStateLedger.d.ts +99 -89
  46. package/dist/core/SharedStateLedger.d.ts.map +1 -1
  47. package/dist/core/SharedStateLedger.js +564 -137
  48. package/dist/core/SharedStateLedger.js.map +1 -1
  49. package/dist/core/registerLedgerEmitters.d.ts +26 -0
  50. package/dist/core/registerLedgerEmitters.d.ts.map +1 -0
  51. package/dist/core/registerLedgerEmitters.js +121 -0
  52. package/dist/core/registerLedgerEmitters.js.map +1 -0
  53. package/dist/core/types.d.ts +74 -0
  54. package/dist/core/types.d.ts.map +1 -1
  55. package/dist/messaging/MessageRouter.d.ts.map +1 -1
  56. package/dist/messaging/MessageRouter.js +5 -2
  57. package/dist/messaging/MessageRouter.js.map +1 -1
  58. package/dist/messaging/SessionSummarySentinel.d.ts +7 -1
  59. package/dist/messaging/SessionSummarySentinel.d.ts.map +1 -1
  60. package/dist/messaging/SessionSummarySentinel.js +11 -3
  61. package/dist/messaging/SessionSummarySentinel.js.map +1 -1
  62. package/dist/server/AgentServer.d.ts +2 -0
  63. package/dist/server/AgentServer.d.ts.map +1 -1
  64. package/dist/server/AgentServer.js +1 -0
  65. package/dist/server/AgentServer.js.map +1 -1
  66. package/dist/server/routes.d.ts +2 -0
  67. package/dist/server/routes.d.ts.map +1 -1
  68. package/dist/server/routes.js +76 -0
  69. package/dist/server/routes.js.map +1 -1
  70. package/dist/threadline/ThreadlineRouter.d.ts +30 -1
  71. package/dist/threadline/ThreadlineRouter.d.ts.map +1 -1
  72. package/dist/threadline/ThreadlineRouter.js +68 -2
  73. package/dist/threadline/ThreadlineRouter.js.map +1 -1
  74. package/package.json +1 -1
  75. package/src/data/builtin-manifest.json +93 -93
  76. package/upgrades/{0.28.44.md → 0.28.45.md} +1 -1
  77. package/upgrades/0.28.46.md +27 -0
  78. package/upgrades/side-effects/0.28.44.md +36 -0
  79. package/upgrades/side-effects/0.28.45.md +138 -0
  80. package/upgrades/side-effects/echo-prevention-self-session-exclusion.md +176 -0
  81. package/upgrades/side-effects/integrated-being-ledger-v1.md +138 -0
  82. package/upgrades/0.28.41.md +0 -41
  83. package/upgrades/0.28.42.md +0 -25
@@ -1,4 +1,4 @@
1
- # Upgrade Guide — v0.28.44
1
+ # Upgrade Guide — vNEXT
2
2
 
3
3
  <!-- bump: patch -->
4
4
 
@@ -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)
@@ -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.
@@ -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.