let-them-talk 5.2.5 → 5.4.0

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 (166) hide show
  1. package/CHANGELOG.md +3 -1
  2. package/README.md +158 -592
  3. package/SECURITY.md +3 -3
  4. package/USAGE.md +151 -0
  5. package/agent-contracts.js +447 -0
  6. package/api-agents.js +760 -0
  7. package/autonomy/decision-v2.js +380 -0
  8. package/autonomy/watchdog-policy.js +572 -0
  9. package/cli.js +454 -298
  10. package/conversation-templates/autonomous-feature.json +83 -22
  11. package/conversation-templates/code-review.json +69 -21
  12. package/conversation-templates/debug-squad.json +69 -21
  13. package/conversation-templates/feature-build.json +69 -21
  14. package/conversation-templates/research-write.json +69 -21
  15. package/dashboard.html +3148 -174
  16. package/dashboard.js +823 -786
  17. package/data-dir.js +58 -0
  18. package/docs/architecture/branch-semantics.md +157 -0
  19. package/docs/architecture/canonical-event-schema.md +88 -0
  20. package/docs/architecture/markdown-workspace.md +183 -0
  21. package/docs/architecture/runtime-contract.md +459 -0
  22. package/docs/architecture/runtime-migration-hardening.md +64 -0
  23. package/events/hooks.js +154 -0
  24. package/events/log.js +457 -0
  25. package/events/replay.js +33 -0
  26. package/events/schema.js +432 -0
  27. package/managed-team-integration.js +261 -0
  28. package/office/agents.js +704 -597
  29. package/office/animation.js +1 -1
  30. package/office/assets/arcade-cabinet.js +141 -0
  31. package/office/assets/archway.js +77 -0
  32. package/office/assets/bar-counter.js +91 -0
  33. package/office/assets/bar-stool.js +71 -0
  34. package/office/assets/beanbag.js +64 -0
  35. package/office/assets/bench.js +99 -0
  36. package/office/assets/bollard.js +87 -0
  37. package/office/assets/cactus.js +100 -0
  38. package/office/assets/carpet-tile.js +46 -0
  39. package/office/assets/chair.js +123 -0
  40. package/office/assets/chandelier.js +107 -0
  41. package/office/assets/coffee-machine.js +95 -0
  42. package/office/assets/coffee-table.js +81 -0
  43. package/office/assets/column.js +95 -0
  44. package/office/assets/desk-lamp.js +102 -0
  45. package/office/assets/desk.js +76 -0
  46. package/office/assets/dining-table.js +105 -0
  47. package/office/assets/door.js +70 -0
  48. package/office/assets/dual-monitor.js +72 -0
  49. package/office/assets/fence.js +76 -0
  50. package/office/assets/filing-cabinet.js +111 -0
  51. package/office/assets/floor-lamp.js +69 -0
  52. package/office/assets/floor-tile.js +54 -0
  53. package/office/assets/flower-pot.js +76 -0
  54. package/office/assets/foosball.js +95 -0
  55. package/office/assets/fridge.js +99 -0
  56. package/office/assets/gaming-chair.js +154 -0
  57. package/office/assets/gaming-desk.js +105 -0
  58. package/office/assets/glass-door.js +72 -0
  59. package/office/assets/glass-wall.js +64 -0
  60. package/office/assets/half-wall.js +49 -0
  61. package/office/assets/hanging-plant.js +112 -0
  62. package/office/assets/index.js +151 -0
  63. package/office/assets/indoor-tree.js +90 -0
  64. package/office/assets/l-sofa.js +153 -0
  65. package/office/assets/marble-floor.js +64 -0
  66. package/office/assets/materials.js +40 -0
  67. package/office/assets/meeting-table.js +88 -0
  68. package/office/assets/microwave.js +94 -0
  69. package/office/assets/monitor.js +67 -0
  70. package/office/assets/neon-strip.js +73 -0
  71. package/office/assets/painting.js +84 -0
  72. package/office/assets/palm-tree.js +108 -0
  73. package/office/assets/pc-tower.js +91 -0
  74. package/office/assets/pendant-light.js +67 -0
  75. package/office/assets/ping-pong.js +114 -0
  76. package/office/assets/plant.js +72 -0
  77. package/office/assets/planter-box.js +95 -0
  78. package/office/assets/pool-table.js +94 -0
  79. package/office/assets/printer.js +113 -0
  80. package/office/assets/reception-desk.js +133 -0
  81. package/office/assets/rug.js +78 -0
  82. package/office/assets/sculpture.js +85 -0
  83. package/office/assets/server-rack.js +98 -0
  84. package/office/assets/sink.js +109 -0
  85. package/office/assets/sofa.js +106 -0
  86. package/office/assets/speaker.js +83 -0
  87. package/office/assets/spotlight.js +83 -0
  88. package/office/assets/street-lamp.js +97 -0
  89. package/office/assets/trash-can.js +83 -0
  90. package/office/assets/treadmill.js +126 -0
  91. package/office/assets/trophy.js +89 -0
  92. package/office/assets/tv-screen.js +79 -0
  93. package/office/assets/vase.js +84 -0
  94. package/office/assets/wall-clock.js +84 -0
  95. package/office/assets/wall.js +53 -0
  96. package/office/assets/water-cooler.js +146 -0
  97. package/office/assets/whiteboard.js +115 -0
  98. package/office/assets.js +3 -431
  99. package/office/builder.js +791 -355
  100. package/office/campus-env.js +1012 -1119
  101. package/office/environment.js +2 -0
  102. package/office/gallery.js +997 -0
  103. package/office/index.js +165 -61
  104. package/office/navigation.js +173 -152
  105. package/office/player.js +178 -68
  106. package/office/robot-character.js +272 -0
  107. package/office/spectator-camera.js +33 -10
  108. package/office/state.js +2 -0
  109. package/office/world-save.js +35 -4
  110. package/package.json +57 -3
  111. package/providers/comfyui.js +383 -0
  112. package/providers/dalle.js +79 -0
  113. package/providers/gemini.js +181 -0
  114. package/providers/ollama.js +184 -0
  115. package/providers/replicate.js +115 -0
  116. package/providers/zai.js +183 -0
  117. package/runtime-descriptor.js +270 -0
  118. package/scripts/check-agent-contract-advisory.js +132 -0
  119. package/scripts/check-api-agent-parity.js +277 -0
  120. package/scripts/check-autonomy-v2-decision.js +207 -0
  121. package/scripts/check-autonomy-v2-execution.js +588 -0
  122. package/scripts/check-autonomy-v2-watchdog.js +224 -0
  123. package/scripts/check-branch-fork-snapshot.js +337 -0
  124. package/scripts/check-branch-isolation.js +787 -0
  125. package/scripts/check-branch-semantics.js +139 -0
  126. package/scripts/check-dashboard-control-plane.js +1304 -0
  127. package/scripts/check-docs-onboarding.js +490 -0
  128. package/scripts/check-event-schema.js +276 -0
  129. package/scripts/check-evidence-completion.js +239 -0
  130. package/scripts/check-invariants.js +992 -0
  131. package/scripts/check-lifecycle-hooks.js +525 -0
  132. package/scripts/check-managed-team-integration.js +166 -0
  133. package/scripts/check-markdown-workspace-export.js +548 -0
  134. package/scripts/check-markdown-workspace-safety.js +347 -0
  135. package/scripts/check-markdown-workspace.js +136 -0
  136. package/scripts/check-message-replay.js +429 -0
  137. package/scripts/check-migration-hardening.js +300 -0
  138. package/scripts/check-performance-indexing.js +272 -0
  139. package/scripts/check-provider-capabilities.js +316 -0
  140. package/scripts/check-runtime-contract.js +109 -0
  141. package/scripts/check-session-aware-context.js +172 -0
  142. package/scripts/check-session-lifecycle.js +210 -0
  143. package/scripts/export-markdown-workspace.js +84 -0
  144. package/scripts/fixtures/message-replay/clean.jsonl +2 -0
  145. package/scripts/fixtures/message-replay/corrupt-correction-payload.jsonl +1 -0
  146. package/scripts/fixtures/message-replay/corrupt-jsonl.jsonl +1 -0
  147. package/scripts/fixtures/message-replay/corrupt-payload.jsonl +1 -0
  148. package/scripts/fixtures/message-replay/out-of-order.jsonl +2 -0
  149. package/scripts/migrate-legacy-to-canonical.js +201 -0
  150. package/scripts/run-verification-suite.js +242 -0
  151. package/scripts/sync-packaged-docs.js +69 -0
  152. package/server.js +9546 -7214
  153. package/state/agents.js +161 -0
  154. package/state/canonical.js +3068 -0
  155. package/state/dashboard-queries.js +441 -0
  156. package/state/evidence.js +56 -0
  157. package/state/io.js +69 -0
  158. package/state/markdown-workspace.js +951 -0
  159. package/state/messages.js +669 -0
  160. package/state/sessions.js +683 -0
  161. package/state/tasks-workflows.js +92 -0
  162. package/templates/debate.json +2 -2
  163. package/templates/managed.json +4 -4
  164. package/templates/pair.json +2 -2
  165. package/templates/review.json +2 -2
  166. package/templates/team.json +3 -3
@@ -0,0 +1,459 @@
1
+ <!-- Generated from ../docs/architecture/runtime-contract.md by scripts/sync-packaged-docs.js for published package consumers. -->
2
+
3
+ # LetThemTalk Runtime Contract
4
+
5
+ Status: normative for Phase 1 runtime work
6
+ Audience: implementation team, validator authors, dashboard/CLI/API-agent maintainers
7
+ Last updated: 2026-04-17
8
+
9
+ ## Guide-level overview
10
+
11
+ LetThemTalk is moving from “many processes rewriting shared files” to “one broker owns canonical state, everyone else sends commands and reads projections.”
12
+
13
+ - One broker lease holder is the only canonical writer for a project runtime.
14
+ - Canonical truth is append-only events; JSON/JSONL state files become rebuildable projections/materialized views.
15
+ - A branch is a full-context namespace, not just a different message/history file.
16
+ - A session is scoped to an agent on a branch and can be resumed with its outstanding work and evidence context.
17
+ - Completion is only authoritative when the broker records evidence for it.
18
+ - Unknown or newer on-disk formats fail closed; they are never silently rewritten.
19
+
20
+ Normative language in this document uses MUST, MUST NOT, SHOULD, and MAY in the RFC-style sense.
21
+
22
+ ## Summary
23
+
24
+ This document freezes the target runtime architecture for LetThemTalk.
25
+
26
+ The authoritative model is a single-writer local broker with append-only canonical event streams and rebuildable projections. Dashboard routes, CLI helpers, API agents, and any future tooling are broker clients, not peer writers. Branches fork full branch-local context. Sessions are durable and branch-scoped. Completion, advancement, and similar “done” transitions require evidence. Storage evolution is explicit, versioned, and fail-closed on unknown formats.
27
+
28
+ This contract is intentionally implementation-driving. Later tasks may change code structure, module boundaries, or transport details, but they MUST NOT reopen the authority, event, branch, session, evidence, or versioning decisions frozen here.
29
+
30
+ ## Context / Motivation
31
+
32
+ The current runtime is concentrated in `server.js`, but canonical writes are not exclusive to that path.
33
+
34
+ Concrete current drift that this contract closes:
35
+
36
+ - `dashboard.js` directly rewrites `tasks.json` and directly edits/deletes `messages.jsonl` and `history.jsonl`.
37
+ - `cli.js` directly appends messages into `messages.jsonl` and `history.jsonl`.
38
+ - `api-agents.js` directly mutates `agents.json`, `profiles.json`, and also appends messages/history.
39
+ - Historically, `server.js` treated branches as alternate `messages.jsonl` / `history.jsonl` files only; the shipped runtime now extends branch-local scope across work, governance, session, evidence, and workspace surfaces.
40
+ - `server.js` has event-like side effects such as `fireEvent(...)`, but those notifications are not canonical lifecycle records.
41
+ - Session recovery today is ad hoc (`register` recovery payloads, heartbeat/recovery files, recent messages) rather than a first-class runtime contract.
42
+
43
+ The result is authority drift, ambiguous source-of-truth rules, weak replay/recovery behavior, and branch semantics that are too narrow for later autonomy and verification work.
44
+
45
+ ## Goals / Non-goals
46
+
47
+ ### Goals
48
+
49
+ - Freeze one canonical writer model for all shared runtime state.
50
+ - Preserve a filesystem-native local runtime rather than introducing a network service or remote dependency.
51
+ - Make canonical history append-only and reconstructable.
52
+ - Make branch semantics full-context for agent-visible reasoning state.
53
+ - Make session lifecycle explicit and resumable.
54
+ - Make completion and workflow advancement evidence-backed.
55
+ - Freeze versioning, migration, and unknown-format handling so later tasks can implement without re-debating compatibility policy.
56
+
57
+ ### Non-goals
58
+
59
+ - This document does not choose a specific broker IPC transport; stdio handoff, local HTTP, pipe/socket IPC, or an extracted in-process broker module are implementation choices as long as the single-writer contract is preserved.
60
+ - This document does not redesign dashboard UX.
61
+ - This document does not require a database migration; the runtime remains filesystem-native.
62
+ - This document does not define merge/cherry-pick semantics between branches.
63
+ - This document does not force all dashboard-owned operator/UI state into broker governance in Phase 1.
64
+
65
+ ## Authority boundaries
66
+
67
+ ### 1. Canonical writer rule
68
+
69
+ For a given project runtime directory, there MUST be exactly one active broker lease holder allowed to mutate canonical runtime state.
70
+
71
+ - Today, the future broker authority is anchored in `server.js` because that file already owns most runtime semantics.
72
+ - Later refactors MAY extract broker code into dedicated modules or a helper process, but there is still only one canonical writer per runtime.
73
+ - Dashboard routes, CLI commands, API agents, scripts, and future tools MUST call broker commands/APIs and MUST NOT write canonical files directly.
74
+
75
+ ### 2. Current canonical reality vs frozen target
76
+
77
+ The table below names the current source-of-truth files and freezes their target status under the new contract.
78
+
79
+ | Domain | Current canonical today | Frozen target status | Transitional or invalid writer paths |
80
+ | --- | --- | --- | --- |
81
+ | Message delivery and history | `.agent-bridge/messages.jsonl`, `.agent-bridge/history.jsonl` | Branch-local projections derived from canonical message events | `dashboard.js` message edit/delete rewrite paths, `cli.js::cliMsg`, `api-agents.js` direct append path |
82
+ | Agent registry and liveness | `.agent-bridge/agents.json` plus heartbeat overlay files | Global projection derived from runtime-global `agent.*` and `session.*` events | `api-agents.js::_registerInAgentsJson`, `_unregisterFromAgentsJson`, direct heartbeat updates |
83
+ | Profiles | `.agent-bridge/profiles.json` | Global projection derived from `profile.*` events | `api-agents.js` direct profile writes |
84
+ | Tasks | `.agent-bridge/tasks.json` | Branch-local projection derived from `task.*` events | `dashboard.js::apiUpdateTask` and any future non-broker task write |
85
+ | Workflows | `.agent-bridge/workflows.json` | Branch-local projection derived from `workflow.*` events | Any non-broker workflow mutation |
86
+ | Branch registry | `.agent-bridge/branches.json` and branch-specific message/history files | Global branch registry projection plus per-branch canonical event streams | Current message-only branch isolation is transitional and insufficient |
87
+ | Workspace / agent memory | `.agent-bridge/workspaces/{agent}.json` on `main` plus `branch-<branch>-workspaces/{agent}.json` elsewhere | Branch-local per-agent projection derived from `workspace.*` events | Any direct file write by clients |
88
+ | Decisions, KB, reviews, dependencies, votes, rules, progress | `.agent-bridge/decisions.json`, `kb.json`, `reviews.json`, `dependencies.json`, `votes.json`, `rules.json`, `progress.json` on `main`, plus `branch-<branch>-*.json` projections elsewhere | Branch-local projections derived from canonical events in the branch stream | Any dashboard/CLI/API-agent direct write or raw cross-branch read |
89
+ | File locks | `.agent-bridge/locks.json` | Runtime-global projection derived from `lock.*` events | Any non-broker direct lock mutation |
90
+ | Acknowledgements and compressed history | `.agent-bridge/acks.json`, `compressed.json` | Derived projections/operational artifacts, not canonical truth | Any code may rebuild them through broker-owned projection code only |
91
+ | Conversation mode / managed-floor state / branch conversation metadata | Currently shared in `.agent-bridge/config.json` | Branch-local projections derived from `conversation.*` events | Shared global config for branch-local conversation semantics is transitional |
92
+
93
+ ### 3. Explicitly non-canonical state
94
+
95
+ The following remain outside canonical branch replay unless a future contract explicitly pulls them in:
96
+
97
+ - dashboard/operator-only state such as project lists and world/office layout state,
98
+ - LAN/auth/dashboard process settings,
99
+ - `api-agents.json` and similar dashboard-owned provider configuration,
100
+ - lock files, temp files, migration backups, and other operational files.
101
+
102
+ These files MAY exist and MAY be written by their owning process, but they MUST NOT be treated as authoritative branch state.
103
+
104
+ ## Storage model
105
+
106
+ ### 1. Canonical storage principle
107
+
108
+ Canonical state is append-only event data. Projections/materialized state are caches that can be rebuilt from canonical events plus explicitly versioned snapshot checkpoints.
109
+
110
+ The broker MUST NOT treat mutable JSON state files as the source of truth after the migration cutover.
111
+
112
+ ### 2. Target runtime layout
113
+
114
+ The implementation MUST converge on a layout equivalent to the following:
115
+
116
+ ```text
117
+ .agent-bridge/
118
+ runtime/
119
+ manifest.json # runtime/storage version manifest
120
+ broker.lock # operational single-writer lease, not canonical data
121
+ events.jsonl # runtime-global canonical events
122
+ projections/
123
+ agents.json
124
+ profiles.json
125
+ locks.json
126
+ branch-index.json
127
+ sessions-index.json
128
+ branches/
129
+ main/
130
+ events.jsonl # branch-local canonical events
131
+ projections/
132
+ messages.jsonl
133
+ history.jsonl
134
+ tasks.json
135
+ workflows.json
136
+ workspaces/
137
+ decisions.json
138
+ kb.json
139
+ reviews.json
140
+ dependencies.json
141
+ votes.json
142
+ rules.json
143
+ progress.json
144
+ evidence.json
145
+ conversation.json
146
+ sessions.json
147
+ snapshots/
148
+ latest.json
149
+ <branch>/
150
+ events.jsonl
151
+ projections/
152
+ snapshots/
153
+ ```
154
+
155
+ Equivalent filenames MAY be used temporarily during migration, but the scope split above is normative:
156
+
157
+ - runtime-global events and projections live under `runtime/`,
158
+ - full branch-local state lives under `runtime/branches/<branch>/...`,
159
+ - snapshots are rebuild checkpoints, never the ultimate source of truth.
160
+
161
+ ### 3. Compatibility projections
162
+
163
+ During migration, the broker MAY continue materializing legacy filenames such as `tasks.json`, `workflows.json`, `messages.jsonl`, and `history.jsonl` so existing readers keep working.
164
+
165
+ However:
166
+
167
+ - those legacy files are projections once event-sourcing is enabled,
168
+ - direct writes to those files become contract violations even if the filenames still exist,
169
+ - deleting and rebuilding those projections MUST be safe,
170
+ - if a compatibility projection exists but its canonical event stream is missing, rebuild and rollback MUST fail explicitly instead of treating the projection as authoritative.
171
+
172
+ ### 4. Append-only rule
173
+
174
+ Canonical event streams MUST only support append. They MUST NOT be edited in place, compacted by rewriting history, or selectively deleted.
175
+
176
+ Implications:
177
+
178
+ - message edits become `message.corrected` or equivalent events,
179
+ - message deletion/redaction becomes `message.redacted` or equivalent tombstone events,
180
+ - task/workflow status corrections become compensating events,
181
+ - rollback is modeled as new events or whole-runtime backup restore during migration, never history surgery.
182
+
183
+ ## Event / command model
184
+
185
+ ### 1. Command handling
186
+
187
+ Clients submit commands to the broker. The broker validates, authorizes, resolves scope, appends canonical events, and then updates projections.
188
+
189
+ Command processing order is normative:
190
+
191
+ 1. validate command envelope,
192
+ 2. resolve runtime-global vs branch-local scope,
193
+ 3. check session and branch authority,
194
+ 4. append canonical event(s),
195
+ 5. update projections and snapshots,
196
+ 6. emit notifications/read-model updates.
197
+
198
+ If step 4 does not happen, the command did not commit.
199
+
200
+ ### 2. Required command envelope
201
+
202
+ Every mutating broker command MUST include, explicitly or by broker-populated context:
203
+
204
+ - `command_id`
205
+ - `type`
206
+ - `issued_at`
207
+ - `actor_agent`
208
+ - `session_id`
209
+ - `branch_id` for branch-local commands
210
+ - `causation_id` when the command is responding to another event/command
211
+ - `correlation_id` for multi-step flows
212
+ - `payload`
213
+
214
+ Commands that depend on current projection state SHOULD include an expected version/sequence guard so stale clients fail explicitly instead of silently overwriting newer state.
215
+
216
+ ### 3. Required event envelope
217
+
218
+ Every canonical event MUST include:
219
+
220
+ - `event_id`
221
+ - `stream` (`runtime` or `branch`)
222
+ - `branch_id` when applicable
223
+ - `seq` (monotonic within that stream)
224
+ - `type`
225
+ - `occurred_at`
226
+ - `schema_version`
227
+ - `actor_agent`
228
+ - `session_id` when applicable
229
+ - `command_id`
230
+ - `causation_id`
231
+ - `correlation_id`
232
+ - `payload`
233
+
234
+ Unknown fields in events MUST be preserved. Canonical events are write-once records.
235
+
236
+ ### 4. Required event families
237
+
238
+ The architecture MUST support canonical events for at least these domains:
239
+
240
+ - `agent.*`, `profile.*`, `lock.*`, `migration.*`, `branch.*` in the runtime-global stream
241
+ - `session.*`, `conversation.*`, `message.*`, `task.*`, `workflow.*`, `workspace.*`, `decision.*`, `kb.*`, `review.*`, `dependency.*`, `vote.*`, `rule.*`, `progress.*`, `evidence.*` in branch-local streams
242
+
243
+ Synthetic helper notifications such as the current `fireEvent(...)` system messages are projections/side effects only. They are not canonical lifecycle truth.
244
+
245
+ ### 5. Evidence-backed completion semantics
246
+
247
+ Any command that produces a terminal or advancement claim for work MUST carry evidence or be rejected.
248
+
249
+ At minimum, an evidence payload MUST include:
250
+
251
+ - `summary`
252
+ - `verification`
253
+ - `files_changed`
254
+ - `confidence`
255
+ - `recorded_at`
256
+ - `recorded_by_session`
257
+
258
+ Completion is authoritative only when the broker records an `evidence.*` event and the corresponding `task.*` / `workflow.*` completion event references that evidence record.
259
+
260
+ Consequences:
261
+
262
+ - a direct status flip to `done` without evidence is invalid,
263
+ - `dashboard.js::apiUpdateTask` style mutations are incompatible with the target contract,
264
+ - `verify_and_advance`-style flows become the model for completion, not a special case.
265
+
266
+ Historical legacy completions imported during migration that lack structured evidence MUST be marked as migrated legacy completions with an evidence-gap flag. They MUST NOT be silently upgraded into first-class evidence-backed completions.
267
+
268
+ ## Branching / isolation semantics
269
+
270
+ ### 1. Full-context branch rule
271
+
272
+ A LetThemTalk branch is a full branch-local runtime namespace.
273
+
274
+ Forking a branch MUST fork all branch-local agent-visible context, not only message/history files.
275
+
276
+ Branch-local state includes:
277
+
278
+ - messages and history,
279
+ - derived delivery/read state such as consumed offsets, acknowledgements, read receipts, compressed history, and non-general channel projections,
280
+ - tasks and workflows,
281
+ - workspace/memory state,
282
+ - decisions and KB,
283
+ - reviews, dependencies, progress, votes, rules,
284
+ - conversation mode, channels, manager/floor state, and similar conversation metadata,
285
+ - branch-local sessions and evidence records.
286
+
287
+ Task 4A freezes the implementation-driving detail for this section in `docs/architecture/branch-semantics.md`, including the two-bucket scope model, fork snapshot behavior, branch-local read/write resolution, and the current leak-priority order. That reference elaborates this section but MUST NOT contradict it.
288
+
289
+ ### 2. Runtime-global exceptions
290
+
291
+ The following remain runtime-global because they describe the shared local runtime or shared working tree rather than branch reasoning state:
292
+
293
+ - agent registration/liveness,
294
+ - profiles,
295
+ - file locks,
296
+ - runtime manifest and migration metadata,
297
+ - explicitly non-canonical dashboard/operator state.
298
+
299
+ Locks are global because the underlying working tree is shared. Lock events MUST still record branch and session metadata so the UI can explain where a lock originated.
300
+
301
+ ### 3. Fork semantics
302
+
303
+ When branch `B` is forked from branch `A` at event sequence `N`:
304
+
305
+ - the runtime-global stream records `branch.created`,
306
+ - branch `B` receives a seed snapshot/checkpoint representing the full branch-local state of `A` at `N`,
307
+ - branch `B` starts its own append-only branch event stream,
308
+ - subsequent events in `B` MUST NOT mutate `A`, and subsequent events in `A` MUST NOT mutate `B`.
309
+
310
+ Implementation may optimize seed creation, but the observable semantics above are fixed. The snapshot semantics for derived delivery state and live-session handling are further frozen in `docs/architecture/branch-semantics.md`.
311
+
312
+ ### 4. Switch semantics
313
+
314
+ Switching branch changes the entire branch-local read/write view at once. It MUST NOT switch only message history while keeping tasks, workflows, or knowledge shared.
315
+
316
+ The current `getMessagesFile(...)` / `getHistoryFile(...)`-only branch isolation is explicitly insufficient and MUST be removed during migration.
317
+
318
+ ### 5. Session scope / resumption semantics
319
+
320
+ A session is scoped to:
321
+
322
+ - one logical agent identity,
323
+ - one branch,
324
+ - one continuous execution interval between `session.started` and a terminal or interrupted state.
325
+
326
+ Rules:
327
+
328
+ - Re-registering the same logical agent on the same branch MUST resume the most recent resumable session when possible.
329
+ - Re-registering the same logical agent on a different branch MUST create or resume a different branch-scoped session; branch-local context MUST NOT be silently carried across branches.
330
+ - Branch switch MUST suspend the old branch session and create/resume the target branch session.
331
+ - A resumable session MUST surface outstanding tasks, pending workflow steps, recent conversation context, workspace state pointers, and recent evidence/completion claims relevant to that branch.
332
+ - Session identity MUST be included in all branch-local canonical events.
333
+
334
+ Required session states are:
335
+
336
+ - `active`
337
+ - `interrupted`
338
+ - `completed`
339
+ - `failed`
340
+ - `abandoned`
341
+
342
+ If an agent process disappears without a terminal session event, the broker MUST synthesize `session.interrupted` or `session.abandoned` according to timeout policy; it MUST NOT pretend the session completed successfully.
343
+
344
+ ## Versioning / migration / compatibility
345
+
346
+ ### 1. Manifest
347
+
348
+ The runtime MUST publish a version manifest at startup and after every migration.
349
+
350
+ `manifest.json` MUST include at least:
351
+
352
+ - `runtime_contract_version` (semantic version for this architecture contract)
353
+ - `storage_format_version` (integer or integer-major semantic for on-disk compatibility)
354
+ - `min_reader_format_version`
355
+ - `min_writer_format_version`
356
+ - `migrations_applied`
357
+ - `created_at`
358
+ - `last_migrated_at`
359
+
360
+ ### 2. Version rules
361
+
362
+ - Incompatible on-disk changes require a storage format major bump.
363
+ - Additive event payload fields MAY be minor changes if old readers can safely preserve and ignore them.
364
+ - Removing or reinterpreting event meaning is a breaking change and requires a major bump.
365
+ - Projections MAY evolve more frequently than canonical streams, but projection rebuild from the canonical stream MUST stay deterministic for a given storage format version.
366
+
367
+ ### 3. Unknown-format behavior
368
+
369
+ Fail closed is mandatory.
370
+
371
+ - If runtime code sees a storage format newer than it supports, it MUST refuse canonical writes.
372
+ - It MAY expose a minimal unsupported-format diagnostic, but it MUST NOT attempt best-effort mutation.
373
+ - If a projection builder encounters an unknown event type or schema version in an otherwise supported stream, it MUST preserve the raw event and mark the affected projection as stale/unsupported rather than dropping or rewriting the event.
374
+
375
+ ### 4. Migration policy
376
+
377
+ Migrations MUST be explicit, idempotent, and evidenceable.
378
+
379
+ The required migration order is:
380
+
381
+ 1. create manifest and broker lease support,
382
+ 2. introduce canonical event streams,
383
+ 3. backfill/import legacy state into canonical events,
384
+ 4. rebuild projections from canonical events,
385
+ 5. switch dashboard/CLI/API-agent callers to broker-only commands,
386
+ 6. reject/remove direct-write legacy paths,
387
+ 7. retain compatibility projections only as long as necessary.
388
+
389
+ Every migration MUST record:
390
+
391
+ - source format version,
392
+ - target format version,
393
+ - backup location,
394
+ - start and finish timestamps,
395
+ - success/failure status.
396
+
397
+ Migration hardening is not complete until deterministic validation proves both canonical-first rebuild and explicit rejection of legacy-only rollback assumptions. Task 13C freezes that validator-facing slice in `docs/architecture/runtime-migration-hardening.md`.
398
+
399
+ ## Failure, rollback, and recovery
400
+
401
+ ### 1. Crash and partial failure rules
402
+
403
+ - If event append fails, the command fails and no state transition occurred.
404
+ - If event append succeeds but projection update fails, the event remains canonical and the affected projection becomes stale until rebuild succeeds.
405
+ - Stale projections MUST be detectable; silent fallback to corrupted or partially rebuilt projections is forbidden.
406
+
407
+ ### 2. Recovery model
408
+
409
+ - Projections and snapshots MUST be rebuildable from canonical events.
410
+ - Session recovery MUST use canonical session/evidence/task/workflow data, not heuristics over whatever files happen to exist.
411
+ - Dead-agent lock cleanup is a compensating runtime action, not a silent deletion of canonical history.
412
+
413
+ ### 3. Rollback policy
414
+
415
+ Operational rollback uses compensating events or branch restore, not in-place canonical history edits.
416
+
417
+ - Incorrect task completion -> append corrective event.
418
+ - Incorrect message removal -> append redaction reversal/correction event if supported.
419
+ - Broken projection -> rebuild projection.
420
+ - Failed migration -> restore the pre-migration backup and record the failed migration in the manifest/history.
421
+ - Missing canonical stream with surviving compatibility projections -> fail explicit rebuild/rollback checks instead of promoting the projection back to authority.
422
+
423
+ Canonical event logs MUST NOT be rewritten as a normal rollback mechanism.
424
+
425
+ ## Verification expectations
426
+
427
+ Later implementation tasks are not complete until they can produce evidence that the runtime obeys this contract.
428
+
429
+ At minimum, verification MUST prove:
430
+
431
+ 1. only the broker lease holder can mutate canonical event streams,
432
+ 2. dashboard, CLI, and API-agent flows route through broker commands instead of direct canonical writes,
433
+ 3. message edit/delete behavior is append-only at the canonical layer,
434
+ 4. branch fork/switch isolates all branch-local state, not only messages/history,
435
+ 5. session resumption is branch-scoped and surfaces outstanding work,
436
+ 6. completion without evidence is rejected,
437
+ 7. unknown/newer storage formats refuse writes,
438
+ 8. projections can be rebuilt from canonical events after deletion or corruption,
439
+ 9. compatibility projections are rejected as rollback authority when the corresponding canonical stream is missing.
440
+
441
+ The future validator and invariant checks MAY evolve, but they MUST test against these behaviors rather than weaker file-existence checks.
442
+
443
+ ## Alternatives / drawbacks
444
+
445
+ - A single local broker is stricter than the current shared-file free-for-all and introduces IPC/forwarding work.
446
+ - Append-only events use more disk than in-place JSON rewrites.
447
+ - Full-context branch snapshots duplicate data or require snapshot optimization.
448
+ - Runtime-global locks plus branch-local task state create a deliberate two-scope model that implementers must keep straight.
449
+ - Event import of legacy data with evidence gaps means some historical records will remain explicitly “legacy” rather than perfectly normalized.
450
+
451
+ These drawbacks are accepted because they buy deterministic authority, replay, recovery, and verification.
452
+
453
+ ## Open questions / future evolution
454
+
455
+ The following are intentionally left open because they do not block Phase 1 implementation of this contract:
456
+
457
+ - whether branches later support merge/cherry-pick semantics or remain isolation-only,
458
+ - whether any currently non-canonical dashboard/operator state should eventually move under broker governance,
459
+ - retention/compaction policy for old snapshots and projections once rebuild tooling exists.
@@ -0,0 +1,64 @@
1
+ <!-- Generated from ../docs/architecture/runtime-migration-hardening.md by scripts/sync-packaged-docs.js for published package consumers. -->
2
+
3
+ # LetThemTalk Runtime Migration Hardening Reference
4
+
5
+ Status: normative Task 13C hardening slice
6
+ Normative parent: `docs/architecture/runtime-contract.md`
7
+ Branch semantics companion: `docs/architecture/branch-semantics.md`
8
+ Current-code anchors: `state/canonical.js`, `events/log.js`, `scripts/check-migration-hardening.js`
9
+ Last updated: 2026-04-16
10
+
11
+ This page freezes the architecture-facing migration, rollback, and hardening rules that Task 13 needs before the broader Task 14 docs refresh. It stays on runtime authority, compatibility projections, and deterministic validation. It does not broaden into public or operator guidance.
12
+
13
+ ## Cutover invariants
14
+
15
+ - Canonical rollback and rebuild inputs are the append-only event streams under `.agent-bridge/runtime/` plus explicit migration backups.
16
+ - Legacy filenames such as `messages.jsonl`, `history.jsonl`, `tasks.json`, and `workflows.json` are compatibility projections during migration. They are not rollback authority.
17
+ - If a compatibility projection exists without its canonical event stream, rebuild and rollback checks MUST fail explicitly instead of silently promoting the projection back to authority.
18
+ - Unknown or newer storage formats remain fail-closed. Unsupported runtimes may emit diagnostics, but they MUST NOT resume canonical writes.
19
+ - Migration cutover is not complete while any required collaboration surface still depends on a message-only branch switch or any other partial branch fallback. `docs/architecture/branch-semantics.md` remains normative for that scope.
20
+
21
+ ## Rollback and recovery rules
22
+
23
+ - Projection corruption or projection deletion, rebuild from canonical events.
24
+ - Incorrect runtime state after cutover, append compensating canonical events when the domain supports them.
25
+ - Failed migration, restore the pre-migration backup and keep a recorded failed migration outcome.
26
+ - Canonical event logs MUST NOT be rewritten as a normal rollback mechanism.
27
+ - Legacy compatibility projections MUST NOT be used to recreate canonical history after cutover.
28
+
29
+ ## Stale transitional assumptions that stay invalid
30
+
31
+ | Stale assumption | Hardening rule | Current guard or validator |
32
+ | --- | --- | --- |
33
+ | Legacy `messages.jsonl` or `history.jsonl` can stand in for a missing canonical branch event log during rebuild | Invalid. A missing canonical branch stream with surviving legacy projections is a fail-closed condition. | `state/canonical.js::rebuildMessageProjections()` plus `scripts/check-migration-hardening.js` |
34
+ | Compatibility projections can become rollback authority because they still use old filenames | Invalid. Old filenames remain projections only. | `docs/architecture/runtime-contract.md` and this reference |
35
+ | Rollback can rewrite append-only canonical history in place | Invalid. Rollback uses compensating events, projection rebuild, or pre-migration backup restore. | `docs/architecture/runtime-contract.md` and `scripts/check-migration-hardening.js` |
36
+ | Message-only branch switching is enough to claim migration cutover is hardened | Invalid. Full branch-local state isolation stays required. | `docs/architecture/branch-semantics.md` and `scripts/check-branch-isolation.js` |
37
+ | Unsupported storage formats may keep writing canonically if a projection looks readable | Invalid. Unknown or newer formats fail closed. | `docs/architecture/runtime-contract.md` and `scripts/check-migration-hardening.js` |
38
+
39
+ ## Guarded runtime slice in current code
40
+
41
+ Task 13C hardens the first migrated runtime slice where the code already has canonical branch events and compatibility projections:
42
+
43
+ - branch-local `message.sent` events under `.agent-bridge/runtime/branches/<branch>/events.jsonl`,
44
+ - compatibility message projections under `messages.jsonl`, `history.jsonl`, and their branch-prefixed variants,
45
+ - deterministic rebuild through `createCanonicalState().rebuildMessageProjections(...)`,
46
+ - explicit failure when a rebuild sees surviving compatibility projections but no canonical branch event stream.
47
+
48
+ This is intentionally narrow. It does not claim that the full manifest-driven storage migration is finished. It freezes the rule, the first fail-closed guard, and the validator surface that later migration work must extend instead of weakening.
49
+
50
+ ## Validation path
51
+
52
+ Healthy validation:
53
+
54
+ ```bash
55
+ node scripts/check-migration-hardening.js
56
+ ```
57
+
58
+ Expected-failure legacy-path simulation:
59
+
60
+ ```bash
61
+ node scripts/check-migration-hardening.js --scenario legacy-projection-without-canonical-log
62
+ ```
63
+
64
+ The second command exits `1` by design. It proves the runtime does not treat surviving compatibility projections as rollback authority when the canonical branch event stream is missing.
@@ -0,0 +1,154 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ const { EVENT_STREAMS } = require('./schema');
5
+
6
+ function defaultSanitizeBranchName(branchName) {
7
+ if (!branchName || branchName === 'main') return 'main';
8
+ if (!/^[a-zA-Z0-9_-]{1,64}$/.test(branchName)) {
9
+ throw new Error('Invalid branch name');
10
+ }
11
+ return branchName;
12
+ }
13
+
14
+ function cloneJsonValue(value) {
15
+ return value == null ? value : JSON.parse(JSON.stringify(value));
16
+ }
17
+
18
+ function readJsonlObjects(filePath) {
19
+ if (!filePath || !fs.existsSync(filePath)) return [];
20
+
21
+ const raw = fs.readFileSync(filePath, 'utf8');
22
+ if (!raw.trim()) return [];
23
+
24
+ return raw
25
+ .split(/\r?\n/)
26
+ .filter(Boolean)
27
+ .map((line) => JSON.parse(line));
28
+ }
29
+
30
+ function createCanonicalHookState(options = {}) {
31
+ const {
32
+ dataDir,
33
+ withLock,
34
+ sanitizeBranchName = defaultSanitizeBranchName,
35
+ now = () => new Date().toISOString(),
36
+ } = options;
37
+
38
+ function runWithLock(filePath, fn) {
39
+ if (typeof withLock === 'function') {
40
+ return withLock(filePath, fn);
41
+ }
42
+ return fn();
43
+ }
44
+
45
+ function getRuntimeHooksFile() {
46
+ return path.join(dataDir, 'runtime', 'hooks.jsonl');
47
+ }
48
+
49
+ function getBranchHooksFile(branchName = 'main') {
50
+ return path.join(dataDir, 'runtime', 'branches', sanitizeBranchName(branchName), 'hooks.jsonl');
51
+ }
52
+
53
+ function getHooksFile(stream, branchId) {
54
+ if (stream === EVENT_STREAMS.RUNTIME) {
55
+ return getRuntimeHooksFile();
56
+ }
57
+
58
+ if (stream === EVENT_STREAMS.BRANCH) {
59
+ return getBranchHooksFile(branchId || 'main');
60
+ }
61
+
62
+ throw new Error(`Unsupported hook stream: ${String(stream)}`);
63
+ }
64
+
65
+ function createHookRecord(event) {
66
+ return {
67
+ hook_id: `hook_${event.event_id}`,
68
+ topic: event.type,
69
+ stream: event.stream,
70
+ branch_id: event.branch_id,
71
+ event_id: event.event_id,
72
+ event_seq: event.seq,
73
+ occurred_at: event.occurred_at,
74
+ published_at: now(),
75
+ actor_agent: event.actor_agent,
76
+ session_id: event.session_id,
77
+ command_id: event.command_id,
78
+ causation_id: event.causation_id,
79
+ correlation_id: event.correlation_id,
80
+ payload: cloneJsonValue(event.payload),
81
+ };
82
+ }
83
+
84
+ function projectCommittedEvent(event) {
85
+ if (!event || typeof event !== 'object' || !event.type || !event.stream) return null;
86
+
87
+ const hookFile = getHooksFile(event.stream, event.branch_id);
88
+ const hook = createHookRecord(event);
89
+
90
+ return runWithLock(hookFile, () => {
91
+ fs.mkdirSync(path.dirname(hookFile), { recursive: true });
92
+ fs.appendFileSync(hookFile, JSON.stringify(hook) + '\n');
93
+ return hook;
94
+ });
95
+ }
96
+
97
+ function readHooks(params = {}) {
98
+ const stream = params.stream || EVENT_STREAMS.BRANCH;
99
+ const branchId = stream === EVENT_STREAMS.BRANCH
100
+ ? sanitizeBranchName(params.branchId || params.branch_id || 'main')
101
+ : null;
102
+ const hookFile = getHooksFile(stream, branchId);
103
+ let hooks = readJsonlObjects(hookFile);
104
+
105
+ if (params.topic) {
106
+ hooks = hooks.filter((hook) => hook.topic === params.topic);
107
+ }
108
+
109
+ if (Array.isArray(params.topics) && params.topics.length > 0) {
110
+ const topicSet = new Set(params.topics);
111
+ hooks = hooks.filter((hook) => topicSet.has(hook.topic));
112
+ }
113
+
114
+ if (Number.isInteger(params.afterEventSeq) && params.afterEventSeq >= 0) {
115
+ hooks = hooks.filter((hook) => hook.event_seq > params.afterEventSeq);
116
+ }
117
+
118
+ if (params.eventId) {
119
+ hooks = hooks.filter((hook) => hook.event_id === params.eventId);
120
+ }
121
+
122
+ if (Number.isInteger(params.limit) && params.limit > 0) {
123
+ hooks = hooks.slice(-params.limit);
124
+ }
125
+
126
+ return hooks;
127
+ }
128
+
129
+ function readBranchHooks(branchName = 'main', options = {}) {
130
+ return readHooks({
131
+ ...options,
132
+ stream: EVENT_STREAMS.BRANCH,
133
+ branchId: branchName,
134
+ });
135
+ }
136
+
137
+ function readRuntimeHooks(options = {}) {
138
+ return readHooks({
139
+ ...options,
140
+ stream: EVENT_STREAMS.RUNTIME,
141
+ });
142
+ }
143
+
144
+ return {
145
+ getRuntimeHooksFile,
146
+ getBranchHooksFile,
147
+ projectCommittedEvent,
148
+ readHooks,
149
+ readBranchHooks,
150
+ readRuntimeHooks,
151
+ };
152
+ }
153
+
154
+ module.exports = { createCanonicalHookState };