mlx-bun 0.2.0 → 0.3.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 (364) hide show
  1. package/README.md +71 -588
  2. package/docs/README.md +91 -0
  3. package/docs/{design → archive/investigations}/audio-input-plan.md +4 -4
  4. package/docs/{design → archive/investigations}/diffusion-gemma-port.md +3 -3
  5. package/docs/{design → archive/investigations}/faithful-l1-consolidation.md +4 -4
  6. package/docs/{design → archive/investigations}/minicpm5-decode-megakernel.md +11 -11
  7. package/docs/{design → archive/investigations}/optimization_plan.md +1 -1
  8. package/docs/{design → archive/investigations}/pre-colibri-stabilization.md +5 -5
  9. package/docs/{design → archive/investigations}/the-dreaming-handoff.md +2 -2
  10. package/docs/design/batching.md +569 -0
  11. package/docs/design/decode-speed-program.md +3178 -169
  12. package/docs/design/dreaming-nightly-pipeline.md +675 -282
  13. package/docs/design/generic-model-support.md +579 -334
  14. package/docs/design/kv-cache.md +680 -0
  15. package/docs/design/orpo-training.md +673 -1239
  16. package/docs/design/speculative-decoding.md +1627 -0
  17. package/docs/design/turboquant.md +2102 -0
  18. package/docs/design/unified-engine-frontier-plan.md +1831 -727
  19. package/docs/design/web-chat-redesign.md +666 -1083
  20. package/docs/planning/PRODUCT_ROADMAP.md +34 -1
  21. package/docs/planning/release-notes-v0.3.0.md +35 -0
  22. package/docs/planning/video-series.md +2 -2
  23. package/docs/reference/benchmarks.md +1162 -0
  24. package/docs/reference/cli.md +394 -159
  25. package/docs/reference/distribution.md +321 -182
  26. package/docs/reference/environment.md +348 -0
  27. package/docs/reference/glossary.md +39 -0
  28. package/docs/reference/library-api.md +566 -168
  29. package/docs/reference/memory.md +227 -64
  30. package/docs/reference/models.md +104 -6
  31. package/docs/reference/server-api.md +886 -425
  32. package/docs/reference/server-config.md +638 -330
  33. package/docs/reference/training.md +393 -254
  34. package/docs/reference/troubleshooting.md +166 -0
  35. package/package.json +9 -4
  36. package/src/audio/decode.ts +2 -2
  37. package/src/audio/features.ts +2 -2
  38. package/src/backends/mlx/autoregressive.ts +108 -0
  39. package/src/backends/mlx/batch-group.ts +1646 -0
  40. package/src/backends/mlx/diffusion.ts +39 -0
  41. package/src/backends/mlx/draft-target.ts +47 -0
  42. package/src/backends/mlx/evaluation.ts +105 -0
  43. package/src/backends/mlx/fill-append.ts +45 -0
  44. package/src/backends/mlx/gateway-binding.ts +71 -0
  45. package/src/backends/mlx/graph.ts +38 -0
  46. package/src/backends/mlx/memory-client.ts +189 -0
  47. package/src/backends/mlx/memory-guard.ts +40 -0
  48. package/src/backends/mlx/methods.ts +90 -0
  49. package/src/backends/mlx/model-prompt.ts +164 -0
  50. package/src/backends/mlx/model-serving.ts +79 -0
  51. package/src/backends/mlx/prefill.ts +33 -0
  52. package/src/backends/mlx/request-policy.ts +20 -0
  53. package/src/backends/mlx/rollback.ts +51 -0
  54. package/src/backends/mlx/serial-executor.ts +273 -0
  55. package/src/backends/mlx/speculative.ts +114 -0
  56. package/src/backends/mlx/state-views.ts +26 -0
  57. package/src/backends/mlx/text-engine.ts +73 -0
  58. package/src/cli.ts +58 -25
  59. package/src/client.ts +66 -0
  60. package/src/config.ts +21 -4
  61. package/src/contracts/completion.ts +16 -0
  62. package/src/contracts/execution.ts +49 -0
  63. package/src/contracts/generation.ts +103 -0
  64. package/src/contracts/host.ts +7 -0
  65. package/src/contracts/jobs.ts +66 -0
  66. package/src/contracts/pi-provider.ts +30 -0
  67. package/src/contracts/pi.ts +259 -0
  68. package/src/contracts/resources.ts +18 -0
  69. package/src/contracts/scheduling.ts +27 -0
  70. package/src/contracts/tsconfig.json +14 -0
  71. package/src/diffusion/diffusion-generate.ts +218 -153
  72. package/src/embed.ts +1 -1
  73. package/src/engine/admission.ts +59 -0
  74. package/src/engine/cancellation.ts +38 -0
  75. package/src/engine/engine.ts +56 -0
  76. package/src/engine/execution-coordinator.ts +70 -0
  77. package/src/engine/execution-plan.ts +51 -0
  78. package/src/engine/resources.ts +42 -0
  79. package/src/engine/scheduler.ts +67 -0
  80. package/src/engine/session.ts +247 -0
  81. package/src/eval/README.md +44 -1
  82. package/src/eval/kl.ts +236 -3
  83. package/src/eval/reference-logits.ts +207 -0
  84. package/src/eval/runner.ts +8 -96
  85. package/src/fill/echo-index.ts +188 -0
  86. package/src/fill/fill-session.ts +452 -0
  87. package/src/fill/proposal.ts +68 -0
  88. package/src/fill/schema-rows.ts +506 -0
  89. package/src/fill/tool-boundary.ts +103 -0
  90. package/src/fit.ts +1 -1
  91. package/src/generate.ts +656 -268
  92. package/src/grammar.ts +12 -7
  93. package/src/harness-pi.ts +14 -33
  94. package/src/hub-rest.ts +4 -4
  95. package/src/index.ts +7 -0
  96. package/src/inference/denoising.ts +17 -0
  97. package/src/inference/graph.ts +33 -0
  98. package/src/inference/prefill.ts +39 -0
  99. package/src/inference/rollback.ts +7 -0
  100. package/src/jobs/db.ts +4 -0
  101. package/src/jobs/index.ts +2 -0
  102. package/src/jobs/job-entry.ts +12 -12
  103. package/src/jobs/runner.ts +98 -13
  104. package/src/jobs/task-client.ts +19 -0
  105. package/src/jobs/types.ts +2 -58
  106. package/src/kv-store.ts +109 -49
  107. package/src/lab/README.md +18 -0
  108. package/src/{assets → lab/curve}/curve-designer.html +2 -2
  109. package/src/{curve-sampler.ts → lab/curve/curve-sampler.ts} +2 -2
  110. package/src/{expert-trace.ts → lab/expert-trace/expert-trace.ts} +5 -5
  111. package/src/{model → lab/paged-kv}/paged-kv.ts +9 -6
  112. package/src/library.ts +40 -0
  113. package/src/lora.ts +18 -0
  114. package/src/media-fetch.ts +1 -1
  115. package/src/memory/article.ts +1 -1
  116. package/src/memory/chunk.ts +1 -1
  117. package/src/memory/model.ts +30 -169
  118. package/src/memory/prompts.ts +1 -1
  119. package/src/memory/rest.ts +1 -1
  120. package/src/memory/schedule.ts +1 -1
  121. package/src/memory/stages.ts +4 -1
  122. package/src/memory/vault.ts +1 -3
  123. package/src/mlx/array.ts +9 -10
  124. package/src/mlx/compile.ts +12 -2
  125. package/src/mlx/ffi.ts +58 -11
  126. package/src/mlx/materialize.ts +23 -0
  127. package/src/mlx/metal-kernel.ts +10 -11
  128. package/src/mlx/ops.ts +33 -13
  129. package/src/mlx/quantized-rows.ts +36 -0
  130. package/src/mlx/token-bitmask.ts +52 -0
  131. package/src/mlx/turboquant-kv-decode.ts +82 -0
  132. package/src/mlx/turboquant-kv-inverse.ts +122 -0
  133. package/src/mlx/turboquant-ops.ts +33 -3
  134. package/src/mlx/turboquant-tables.ts +2 -2
  135. package/src/model/batched-mask.ts +8 -2
  136. package/src/model/batched-quant.ts +1 -1
  137. package/src/model/batched-rotating-quant.ts +2 -2
  138. package/src/model/batched-rotating.ts +1 -1
  139. package/src/model/compiled-decode.ts +1 -1
  140. package/src/model/diffusion-gemma.ts +8 -8
  141. package/src/model/factory.ts +76 -33
  142. package/src/model/fingerprint.ts +1 -1
  143. package/src/model/flash-attention.ts +3 -3
  144. package/src/model/gemma4-base.ts +26 -9
  145. package/src/model/gemma4.ts +6 -6
  146. package/src/model/generated/gemma4-12b.ts +2 -2
  147. package/src/model/generated/gemma4-26b.ts +2 -2
  148. package/src/model/generated/gemma4-e4b.ts +2 -2
  149. package/src/model/glm52-dsa.ts +13 -9
  150. package/src/model/glm52-memory.ts +1 -1
  151. package/src/model/implementation.ts +69 -0
  152. package/src/model/minicpm5.ts +6 -8
  153. package/src/model/profile.ts +12 -2
  154. package/src/model/qwen-append.ts +12 -0
  155. package/src/model/qwen-conv.ts +52 -0
  156. package/src/model/qwen3_5-checkpoint.ts +164 -0
  157. package/src/model/qwen3_5.ts +140 -56
  158. package/src/model/support.ts +1 -1
  159. package/src/model/trellis-balanced-scatter.ts +51 -0
  160. package/src/model/trellis-linear.ts +672 -0
  161. package/src/model/trellis-shared-m.ts +151 -0
  162. package/src/model/trellis-shared-scatter.ts +144 -0
  163. package/src/model/trellis-splitk-prefill.ts +158 -0
  164. package/src/model/trellis-tiled-prefill.ts +136 -0
  165. package/src/model/trellis-vector-expand.ts +62 -0
  166. package/src/model/trellis-wide-prefill.ts +94 -0
  167. package/src/model/universal/dense.ts +1 -1
  168. package/src/model/universal/modules.ts +8 -1
  169. package/src/native-pack.ts +6 -5
  170. package/src/pi-provider.ts +6 -64
  171. package/src/pi-web.ts +8 -315
  172. package/src/prompt-cache.ts +96 -53
  173. package/src/quantize/allocator.ts +1 -1
  174. package/src/quantize/config-writer.ts +10 -3
  175. package/src/quantize/rotate.ts +4 -4
  176. package/src/quantize/trellis.ts +586 -0
  177. package/src/responses.ts +28 -1
  178. package/src/runtime-config.ts +18 -4
  179. package/src/sampler.ts +4 -4
  180. package/src/serve/batch-scheduler.ts +8 -1671
  181. package/src/serve/chat-request.ts +391 -0
  182. package/src/serve/chat-stage.ts +195 -0
  183. package/src/serve/checkpoint-identity.ts +64 -0
  184. package/src/serve/completion-executor.ts +15 -7
  185. package/src/serve/discovery-routes.ts +12 -13
  186. package/src/serve/generation-gateway.ts +154 -193
  187. package/src/serve/http.ts +159 -0
  188. package/src/serve/inference-request.ts +79 -0
  189. package/src/serve/isolate.ts +297 -105
  190. package/src/serve/lab-routes.ts +3 -0
  191. package/src/serve/lane-registry.ts +2 -1
  192. package/src/serve/model-admin-routes.ts +7 -5
  193. package/src/serve/model-binding.ts +43 -0
  194. package/src/serve/model-host.ts +512 -0
  195. package/src/serve/openai-wire.ts +103 -0
  196. package/src/serve/parent-application.ts +56 -0
  197. package/src/serve/pipeline.ts +33 -0
  198. package/src/serve/preparation.ts +33 -0
  199. package/src/serve/request-plan.ts +19 -15
  200. package/src/serve/request-prep.ts +346 -0
  201. package/src/serve/response-lease.ts +24 -0
  202. package/src/serve/responses-client.ts +69 -0
  203. package/src/serve/session-completion-engine.ts +110 -0
  204. package/src/serve/session-search.ts +1 -1
  205. package/src/serve/static-routes.ts +5 -5
  206. package/src/serve/text-completion-stage.ts +104 -0
  207. package/src/serve/token-streams.ts +519 -0
  208. package/src/server.ts +317 -2649
  209. package/src/spec/assistant-source.ts +10 -44
  210. package/src/spec/deepspec-source.ts +6 -6
  211. package/src/spec/dflash-source.ts +8 -8
  212. package/src/spec/dspark/ab-stats.ts +3 -3
  213. package/src/spec/dspark/calibration.ts +2 -2
  214. package/src/spec/dspark/deepspec-module.ts +2 -2
  215. package/src/spec/dspark/generate-dflash.ts +2 -2
  216. package/src/spec/dspark/generate.ts +2 -2
  217. package/src/spec/dspark/loader.ts +1 -1
  218. package/src/spec/dspark/module-dflash.ts +4 -3
  219. package/src/spec/dspark/module.ts +3 -3
  220. package/src/spec/dspark/quantize-drafter.ts +1 -1
  221. package/src/spec/generate.ts +1 -1
  222. package/src/spec/glm52-mtp-source.ts +1 -1
  223. package/src/spec/ngram-source.ts +1 -1
  224. package/src/spec/prefix-state.ts +45 -0
  225. package/src/spec/qwen-mtp-source.ts +166 -69
  226. package/src/spec/serve-loop.ts +154 -156
  227. package/src/spec/source.ts +42 -10
  228. package/src/spec/two-model.ts +1 -1
  229. package/src/ssd-cache.ts +80 -12
  230. package/src/ssd-durability.ts +25 -13
  231. package/src/tool-call.ts +51 -14
  232. package/src/train/flash-cce.ts +2 -2
  233. package/src/train/forward.ts +6 -0
  234. package/src/train/loss.ts +1 -1
  235. package/src/train/prefix-shared.ts +5 -1
  236. package/src/train/segmented.ts +5 -3
  237. package/src/train/trainer.ts +6 -6
  238. package/src/vision/prompt.ts +1 -1
  239. package/src/vision/qwen3vl-tower.ts +52 -10
  240. package/src/web/app.html +1 -1
  241. package/src/web/app.js +5 -3
  242. package/src/web/src/adapters-panel.ts +3 -3
  243. package/src/web/src/assistant.ts +2 -36
  244. package/src/web/src/chat.ts +4 -4
  245. package/src/web/src/composer.ts +4 -4
  246. package/src/web/src/hub.ts +1 -1
  247. package/src/web/src/markdown.ts +0 -0
  248. package/src/web/src/memory-panel.ts +1 -1
  249. package/src/web/src/model-picker.ts +1 -1
  250. package/src/web/src/palette.ts +1 -1
  251. package/src/web/src/protocol.ts +3 -39
  252. package/src/web/src/sessions.ts +1 -1
  253. package/src/web/src/ui-catalog.ts +4 -18
  254. package/src/web-assets.ts +47 -0
  255. package/src/weights.ts +55 -9
  256. package/docs/archive/investigations/curve-runs/bisector-route.json +0 -98
  257. package/docs/archive/investigations/curve-runs/bisector-trace.json +0 -1393
  258. package/docs/archive/investigations/curve-runs/curve-terrain.json +0 -1179
  259. package/docs/archive/investigations/curve-runs/curve-terrain.svg +0 -42
  260. package/docs/archive/investigations/hlg-ab.html +0 -34
  261. package/docs/archive/investigations/hlg-explorer.html +0 -238
  262. package/docs/archive/investigations/hlg-figs/cliff.svg +0 -1
  263. package/docs/archive/investigations/hlg-figs/cross.svg +0 -1
  264. package/docs/archive/investigations/hlg-figs/edges.svg +0 -1
  265. package/docs/archive/investigations/hlg-figs/k20-grids.svg +0 -1
  266. package/docs/archive/investigations/hlg-figs/k20-peaks.svg +0 -1
  267. package/docs/archive/investigations/hlg-figs/lw-curve.svg +0 -1
  268. package/docs/archive/investigations/hlg-figs/terrain.svg +0 -1
  269. package/docs/archive/investigations/hlg-report.html +0 -262
  270. package/docs/archive/investigations/hlg-runs/hlg-ab.json +0 -149
  271. package/docs/archive/investigations/hlg-runs/hlg-blind-prompts.md +0 -87
  272. package/docs/archive/investigations/hlg-runs/hlg-blind.json +0 -107
  273. package/docs/archive/investigations/hlg-runs/hlg-cliff.json +0 -197
  274. package/docs/archive/investigations/hlg-runs/hlg-cross.json +0 -505
  275. package/docs/archive/investigations/hlg-runs/hlg-edge.json +0 -388
  276. package/docs/archive/investigations/hlg-runs/hlg-eval1.json +0 -25
  277. package/docs/archive/investigations/hlg-runs/hlg-frontier.json +0 -86
  278. package/docs/archive/investigations/hlg-runs/hlg-grid.json +0 -242
  279. package/docs/archive/investigations/hlg-runs/hlg-grid2.json +0 -343
  280. package/docs/archive/investigations/hlg-runs/hlg-lw-coarse.json +0 -110
  281. package/docs/archive/investigations/hlg-runs/hlg-map.json +0 -383
  282. package/docs/archive/investigations/hlg-runs/run0-original.log +0 -159
  283. package/docs/archive/investigations/hlg-runs/runA-recommended.log +0 -131
  284. package/docs/archive/investigations/hlg-runs/runAA-cliff-cpm5.log +0 -26
  285. package/docs/archive/investigations/hlg-runs/runAB-grid2-e4b.log +0 -18
  286. package/docs/archive/investigations/hlg-runs/runAC-peak-e4b-partial.log +0 -25
  287. package/docs/archive/investigations/hlg-runs/runAD-ab-samples.log +0 -8
  288. package/docs/archive/investigations/hlg-runs/runAE-edge-e4b.log +0 -51
  289. package/docs/archive/investigations/hlg-runs/runAF-logit-calibration.log +0 -10
  290. package/docs/archive/investigations/hlg-runs/runAG-eval-config2.log +0 -16
  291. package/docs/archive/investigations/hlg-runs/runB-gain-variants.log +0 -192
  292. package/docs/archive/investigations/hlg-runs/runC-pivots.log +0 -162
  293. package/docs/archive/investigations/hlg-runs/runD-oetf.log +0 -132
  294. package/docs/archive/investigations/hlg-runs/runE-eotf.log +0 -162
  295. package/docs/archive/investigations/hlg-runs/runF-shaper-working.log +0 -86
  296. package/docs/archive/investigations/hlg-runs/runG-shaper-sweep.log +0 -101
  297. package/docs/archive/investigations/hlg-runs/runH-wsweep.log +0 -176
  298. package/docs/archive/investigations/hlg-runs/runI-asweep.log +0 -176
  299. package/docs/archive/investigations/hlg-runs/runJ-knobmap.log +0 -49
  300. package/docs/archive/investigations/hlg-runs/runK-frontier.log +0 -19
  301. package/docs/archive/investigations/hlg-runs/runL-widemap.log +0 -65
  302. package/docs/archive/investigations/hlg-runs/runM-frontier-emb.log +0 -15
  303. package/docs/archive/investigations/hlg-runs/runN-verify.log +0 -116
  304. package/docs/archive/investigations/hlg-runs/runO-frontier-seed5000.log +0 -15
  305. package/docs/archive/investigations/hlg-runs/runP-grid-AxSm.log +0 -15
  306. package/docs/archive/investigations/hlg-runs/runQ-grid-tgW.log +0 -15
  307. package/docs/archive/investigations/hlg-runs/runR-blind-transcript.log +0 -79
  308. package/docs/archive/investigations/hlg-runs/runS-cross-coherence.log +0 -33
  309. package/docs/archive/investigations/hlg-runs/runT-cross-cpm5.log +0 -33
  310. package/docs/archive/investigations/hlg-runs/runU-cross-12b.log +0 -33
  311. package/docs/archive/investigations/hlg-runs/runV-lw-e4b.log +0 -22
  312. package/docs/archive/investigations/hlg-runs/runW-lw-12b.log +0 -22
  313. package/docs/archive/investigations/hlg-runs/runX-lw-cpm5.log +0 -22
  314. package/docs/archive/investigations/hlg-runs/runY-cliff-e4b.log +0 -26
  315. package/docs/archive/investigations/hlg-runs/runZ-cliff-12b.log +0 -26
  316. package/docs/archive/mlx-bun-lab-report.html +0 -275
  317. package/docs/archive/wwdc-mlx-bun.html +0 -848
  318. package/docs/design/adapters-end-to-end.md +0 -205
  319. package/docs/design/batching-perf-path.md +0 -255
  320. package/docs/design/batching-v2-plan.md +0 -529
  321. package/docs/design/bucketing-stage.md +0 -197
  322. package/docs/design/compat-cli-surface-design.md +0 -230
  323. package/docs/design/docs-reorg-plan.md +0 -165
  324. package/docs/design/dspark-serving-program.md +0 -371
  325. package/docs/design/dspark-speculative-decoding.md +0 -318
  326. package/docs/design/grammar-spec-batching-integration.md +0 -301
  327. package/docs/design/memory-inference-path.md +0 -422
  328. package/docs/design/memory-synthesis.md +0 -287
  329. package/docs/design/memory-system.md +0 -237
  330. package/docs/design/mlx-lm-serving-execution-seam.md +0 -81
  331. package/docs/design/mlx-lm-tool-parity-plan.md +0 -545
  332. package/docs/design/omlx-adoption-map.md +0 -98
  333. package/docs/design/orpo-dynamic-lambda.md +0 -220
  334. package/docs/design/orpo-future-enhancements.md +0 -340
  335. package/docs/design/paged-kv-cache.md +0 -214
  336. package/docs/design/parallel-slots.md +0 -567
  337. package/docs/design/parity-tier-dag.md +0 -161
  338. package/docs/design/repo-cleanup-plan.md +0 -231
  339. package/docs/design/runtime-isolation.md +0 -263
  340. package/docs/design/segmented-backward-training.md +0 -543
  341. package/docs/design/spec-decode-larger-targets.md +0 -229
  342. package/docs/design/ssd-kv-cold-tier.md +0 -415
  343. package/docs/design/structured-output.md +0 -484
  344. package/docs/design/tauri-desktop-app.md +0 -127
  345. package/docs/design/the-dreaming-master-plan.md +0 -445
  346. package/docs/design/turboquant-kv.md +0 -235
  347. package/docs/design/turboquant-weights.md +0 -467
  348. package/docs/design/w4a16-compute-precision-spike.md +0 -232
  349. package/docs/design/web-chat-beat-matrix.md +0 -315
  350. package/docs/design/write-pipeline-entity.md +0 -468
  351. package/docs/planning/IDEAS.md +0 -69
  352. package/docs/reference/embedding.md +0 -197
  353. package/docs/reference/features-matrix.md +0 -101
  354. package/docs/reference/orpo-quickstart.md +0 -135
  355. package/src/flags.ts +0 -11
  356. /package/docs/{design → archive/investigations}/colibri-glm52-port.md +0 -0
  357. /package/docs/{planning → archive/planning}/chunk-task-roadmap.md +0 -0
  358. /package/docs/{planning → archive/planning}/curve-sampler-research-plan.md +0 -0
  359. /package/docs/{planning → archive/planning}/memory-docs-and-dag-plan.md +0 -0
  360. /package/docs/{planning → archive/planning}/pi-integration-review.md +0 -0
  361. /package/docs/{planning → archive/planning}/release-notes-v0.2.0.md +0 -0
  362. /package/src/{memory-plan.ts → admission-plan.ts} +0 -0
  363. /package/{docs/archive/investigations → src/serve/assets}/curve-terrain.html +0 -0
  364. /package/{docs/dag → src/serve/assets}/training-inference-map.html +0 -0
@@ -1,291 +1,684 @@
1
- # The Dreaming — nightly memory pipeline (CANONICAL design)
2
-
3
- Status: **canonical**, 2026-07-01. This document states the system design as
4
- settled with Josh and **supersedes any conflicting statement** in
5
- [the-dreaming-master-plan.md](./the-dreaming-master-plan.md) (task breakdown still
6
- useful; several decisions overridden below),
7
- [memory-synthesis.md](./memory-synthesis.md) (superseded), and
8
- [the-dreaming-handoff.md](./the-dreaming-handoff.md) (session log, mostly aligned).
9
-
10
- The system is the second iteration of Lucien's memory pipeline, rebuilt to run
11
- **entirely on local models** inside mlx-bun (`src/memory/`). Same methodology and
12
- pipeline at the core; the local constraint drives two design consequences:
13
-
14
- 1. **Small tasks.** Every AI step is decomposed to the smallest single-purpose
15
- unit a small local model can do well — one chunk, one bucket, one section,
16
- ideally a binary judgment. Never a whole-article blob in or out.
17
- **And every AI step is pure text-in/text-out — no tool calls, no agent
18
- loop.** The model never edits a file, never searches, never decides what to
19
- do next; the pipeline hands it exactly the context it needs and applies its
20
- output deterministically. This lowers the capability bar dramatically: a
21
- small model that could never drive an agentic edit session can absolutely
22
- rewrite one section given the section and the new information — and pure
23
- completion tasks are exactly what per-stage LoRA SFT trains well.
24
- 2. **Deterministic gates between AI steps.** AI does judgment; deterministic
25
- code enforces invariants (citations, footnotes, wikilinks, infobox shape,
26
- word floors). A weak AI output is gated to NO-OP, never allowed to corrupt
27
- the vault.
28
-
29
- ## Fixed principles (do not re-derive, do not contradict)
1
+ ---
2
+ status: active
3
+ axis: USING
4
+ canonical-for: memory-synthesis (the Dreaming)
5
+ plan-anchor: none
6
+ last-verified: 2026-08-23
7
+ ---
8
+
9
+ # The Dreaming — memory write path + synthesis (canonical design)
10
+
11
+ This is THE design doc for mlx-bun's memory write path: the pipeline that
12
+ turns conversations into a personal wiki (`src/memory/`, `scripts/memory/`),
13
+ the local-model inference seam it runs on, the read path it feeds, and the
14
+ evaluation methodology that sits beside it. It consolidates, in supersession
15
+ order, `docs/design/dreaming-nightly-pipeline.md` → `docs/design/dreaming-nightly-pipeline.md` →
16
+ `docs/design/dreaming-nightly-pipeline.md` → this doc, plus the architecture half of
17
+ `docs/design/dreaming-nightly-pipeline.md`, `docs/design/dreaming-nightly-pipeline.md`, and the still-open task list
18
+ from `docs/design/dreaming-nightly-pipeline.md`. Where those docs disagree, this doc wins.
19
+
20
+ - User-facing behaviour (CLI, tools, vault layout as the user sees it) lives
21
+ in `docs/reference/memory.md`. Status and changelog prose belong in
22
+ `PLAN.md`; this doc carries design plus a short dated History.
23
+ - Every code claim below was re-read against `src/memory/` on 2026-08-23.
24
+ Where the code and the settled design differ, both are stated
25
+ ("as built" vs "target") — the code is the truth about what runs; the
26
+ design is the truth about where it goes.
27
+
28
+ The system is the second iteration of Lucien's memory pipeline
29
+ (`~/Code/lucien`, cloud models), rebuilt to run entirely on local models. Same
30
+ methodology at the core — do whatever Wikipedia does — with two consequences
31
+ of the local constraint:
32
+
33
+ 1. **Small tasks.** Every AI step is the smallest single-purpose unit a small
34
+ local model does well — one chunk, one section, ideally a binary judgment.
35
+ Never a whole-article blob in or out. And every AI step is pure
36
+ text-in/text-out: no tool calls, no agent loop. The pipeline hands the
37
+ model exactly the context it needs and applies the output
38
+ deterministically.
39
+ 2. **Deterministic gates between AI steps.** AI does judgment; code enforces
40
+ invariants (citations, footnotes, wikilinks, infobox shape, word floors).
41
+ A weak output is gated to NO-OP, never allowed to corrupt the vault.
42
+ "LoRAs propose, gates dispose."
43
+
44
+ ## 1. Contract and fixed principles
45
+
46
+ **The memory contract (ported from Lucien):** chat-time agents READ memory;
47
+ the synthesis pipeline WRITES memory. Articles are durable priors,
48
+ conversations are evidence, synthesis is the deliberate, auditable process
49
+ that turns evidence into updated articles. Normal chat never mutates an
50
+ article; writes happen only through `mlx-bun memory synthesize` (or its
51
+ per-stage subcommands) and the user's own edits in the vault.
52
+
53
+ Fixed principles (do not re-derive, do not contradict):
30
54
 
31
55
  - **Conversations are the base.** The pipeline's only input is conversation
32
- transcripts (pi sessions daily; historical imports are a one-time onboarding
33
- path through the same code).
34
- - **No notability filter.** *"If I talk about it, it's notable."* Everything
35
- surfaces; nothing is dropped or parked as trivia. Any prior "owned/decided/≥3
36
- chunks else PARK" gate is dead — the catch-all bucket exists only so that
37
- low-volume topics remain searchable until they warrant their own article.
38
- - **No embeddings, anywhere in the project.** Not for routing, not for
39
- clustering, not as "offline instruments." Grouping and routing are done by
40
- deterministic name/alias/category lookup plus small local-model judgments.
41
- - **Articles are real single-topic wiki articles with sections** — one subject
42
- per article, a lead, an infobox, `##` sections, See also, References. All
43
- subject mentions are `[[wikilinks]]`.
44
- - **Vault = truth, DB = derived cache.** Markdown in the vault is canonical and
45
- git-tracked; the SQLite store is pipeline state + rebuildable index
56
+ transcripts — pi sessions in steady state, a one-time historical import
57
+ through the same code for onboarding.
58
+ - **Articles are THINGS (subjects), with sections.** One subject per
59
+ article: a lead, an infobox, `##` sections, See also, References. Subject
60
+ mentions are `[[wikilinks]]`. Every article is "the thing AND how the user
61
+ relates to it"; general world-facts link out.
62
+ - **No notability filter.** "If I talk about it, it's notable." Nothing is
63
+ dropped as trivia. A thin subject is CAPTURED (kept searchable in
64
+ `_captured`) until it recurs; stubs are a feature.
65
+ - **Capacity by decomposition.** A small model's effective capacity scales
66
+ inversely with task size; you don't make the model smarter, you make each
67
+ task narrower. Decompose to binary where possible and run M×N — affordable
68
+ because local inference is nearly free. Coarse gates prune fine ones
69
+ (chunk → article → section).
70
+ - **No embeddings in the pipeline.** Routing, grouping, and reading are
71
+ deterministic name/alias/category lookup plus small-model judgments. The
72
+ hot path asserts the `src/embed.ts` tripwire (`getEmbedCounter()`) stays
73
+ at 0. Embeddings survive only as offline eval instruments (§8) — see the
74
+ contradiction note there.
75
+ - **Vault = truth, DB = derived cache.** Markdown in the vault is canonical
76
+ and git-tracked; SQLite is pipeline ledger + rebuildable index
46
77
  (`reindex`).
47
- - **Self-healing through ordinary use.** Corrections arrive as ordinary chunks;
48
- chronological processing (`updated_at ASC`) resolves claims toward the user's
49
- latest position.
50
- - **Prompts and Meta pages are SCHEMATIC** (no real names/specs/dates) — the
51
- small model parrots concrete example values into unrelated articles.
52
- - **Runs nightly**, locally, unattended (launchd, `src/memory/schedule.ts`).
53
- - **Model evals are not part of this design.** The eval harnesses (chunk-eval,
54
- judges, F1/Jaccard gates, cloud-judge sampling) are tooling for *choosing and
55
- training the models used by* the pipeline. They live beside the system, never
56
- inside it, and belong in their own docs.
57
-
58
- ## The pipeline
78
+ - **Chronology is load-bearing.** Every stage walks
79
+ `conversations.updated_at ASC, chunks.start ASC`. This is what makes
80
+ self-healing correct: the user's latest statement dominates.
81
+ - **Self-healing through ordinary use.** Corrections arrive as ordinary
82
+ chunks; a later-dated contradiction resolves the article toward the
83
+ user's latest position. The user's evolving positions keep a `## History`
84
+ trace; an AI factual error is silently overwritten.
85
+ - **Prompts and Meta pages are SCHEMATIC** — no real names, specs, or dates.
86
+ The small model parrots concrete example values into unrelated articles
87
+ (observed five times during bring-up). Every prompt carries a no-copy
88
+ guard; leaky drafts are rejected (`isLeakyDraft`).
89
+ - **Runs nightly, locally, unattended** (launchd, `src/memory/schedule.ts`).
90
+ - **Lucien is a read-only oracle.** `~/Code/lucien/.lucien/lucien.db` is
91
+ opened read-only; `~/Dreaming` is never written. Never suggest modifying
92
+ Lucien's code, DB, or vault.
93
+ - **Model evals are beside the system, not inside it.** Judges, gold sets,
94
+ and signal scripts choose and train the models the pipeline uses; they are
95
+ never a pipeline stage.
96
+
97
+ ## 2. Data model
98
+
99
+ ### 2.1 Vault (truth)
100
+
101
+ - Root `~/.mlx-bun/wiki/` (override `MLX_BUN_WIKI`, honoured by `vaultRoot()`
102
+ — every script and test redirects the whole pipeline by setting it).
103
+ - `articles/*.md` user articles · `Meta/*.md` editable policy pages ·
104
+ `Talk/` restructure suggestions · `Reference/*.md` read-only symlinks to
105
+ mlx-bun's own docs (chat tools may read them; synthesis must never write
106
+ through them).
107
+ - Plain Markdown, git-tracked, usable as an Obsidian vault. Every pipeline
108
+ write ends in `commitVault`.
109
+ - Meta pages seeded by `setupVault` (`src/memory/vault.ts` `META_PAGES`):
110
+ Editorial_Guidelines, Bucketing, Article_Conventions, Infobox_Schemas,
111
+ Category_Definitions, Entities, Summary_Style, Buckets, Chunking,
112
+ Topics_to_Ignore. Policy lives in the vault, not in code:
113
+ `loadMetaPolicy(names)` (`src/memory/prompts.ts`) inlines pages into a
114
+ stage prompt (single-turn, bounded prefill — never "go read the page")
115
+ and throws if a requested page is missing.
116
+
117
+ ### 2.2 Article grammar (`src/memory/article.ts`)
118
+
119
+ Fixed skeleton, top to bottom: `# H1` (== filename stem with `_`→space) →
120
+ optional series banner `*Part of a series on [[X]].*` → optional infobox
121
+ (fenced ```` ```info ```` block of `key: value` lines) → lead (present-tense
122
+ relationship abstract) → `##` sections → `## See also` → optional `## Notes`
123
+ → `## References` last. Templates the parser and NORMALIZE recognise:
124
+ `{{stub}}`, `*Main article: [[Child]]*`, `*Redirects to [[X]]*`.
125
+
126
+ - **Infobox** — `snake_case` keys; entity-valued fields are `[[wikilinks]]`
127
+ (they feed the link graph); `aliases:` is a comma list feeding the entity
128
+ index; `kind:` is the closed set `ENTITY_KINDS = thing | person | domain |
129
+ project | standard`; `type:` and every other field label are EMERGENT
130
+ (governed by `Infobox_Schemas.md`, never a code enum). Fields are either
131
+ world-facts (citable specs) or relationship-facts (owned / used_for /
132
+ opinion / status … — the personal layer). The infobox is CONTENT the model
133
+ reads, never a query/filter engine; there is no field query anywhere.
134
+ - **Categories** — emergent and explicit: an article declares
135
+ `[[Category:Name]]`; the deriver accumulates names into `categories`. A
136
+ fresh vault has zero.
137
+ - **Citations** — footnote defs in the Lucien standard
138
+ `` [^N]: `conv:HASH` (YYYY-MM-DD, source) — desc ``, HASH = first 8 hex of
139
+ the conversation UUID. A hash identifies the CONVERSATION only; chunk →
140
+ section provenance is recovered from the ledger, never by parsing
141
+ footnotes. The model only PLACES markers; code writes every definition
142
+ line (`buildFootnoteMap`, `footnoteDefLine` in `synthesize.ts`).
143
+ - One shared fence scanner (`isFenceLine`/`fenceLabel`) so `article.ts`,
144
+ `vault.ts`, `normalize.ts`, `crosslink.ts` agree on "inside a code block";
145
+ `repairFences` closes a dangling fence deterministically.
146
+
147
+ ### 2.3 Store (`src/memory/db.ts`, `~/.cache/mlx-bun/memory.sqlite`)
148
+
149
+ bun:sqlite, WAL, `PRAGMA foreign_keys = ON`, `ON DELETE CASCADE`. A DB
150
+ carrying the pre-rewrite schema (`chunks.text` column or a
151
+ `synthesized_bucket_chunks` table) is dropped and recreated on open.
152
+
153
+ - **Derived cache** (rebuilt from markdown by `reindex`, never the reverse):
154
+ `entities` · `entity_aliases` · `categories` · `article_categories` ·
155
+ `infobox_facts` · `links` (`via ∈ prose|infobox|seealso|series|main`).
156
+ - **Synthesis ledger** (authoritative pipeline progress, not derivable from
157
+ the vault): `conversations` · `messages` · `chunks` · `chunk_entities` ·
158
+ `chunk_sections` · `synthesized_chunk_sections` · `reconciled_articles` ·
159
+ `watermarks` · `buckets` / `chunk_buckets` · `subject_redirects`.
160
+ - **Pointers, not copies.** `messages` holds text once, keyed
161
+ `(conv, position)`. A chunk is a `[start,end]` range plus a label, id
162
+ `${conv}:${start}-${end}` (deterministic, idempotent re-chunking);
163
+ `chunkText(id)` reassembles on demand. Assignments are join tables
164
+ (many-to-many at every layer). The article markdown is the only
165
+ materialised synthesis; its `conv:` citations point back at sources.
166
+ - `reindex` (`src/memory/reindex.ts`) drops and rebuilds exactly the six
167
+ derived tables from the vault with foreign keys off (so pipeline
168
+ `chunk_entities` rows survive), in sorted-stem order, deterministically.
169
+ It also PROJECTS `subject_redirects` into `entity_aliases` so a subject
170
+ that folded into or was captured near another article still resolves.
171
+
172
+ Turso/sqlite-vec were considered as a vector-capable engine and are moot
173
+ under the no-embeddings rule; the driver stays bun:sqlite.
174
+
175
+ ## 3. The pipeline as built (verified 2026-08-23)
59
176
 
60
177
  ```
61
- conversations (ingest, watermark/cursor-gated)
62
- │
63
- ▼ SEGMENT ──────────────── AI: find the topics in each conversation
64
- chunks: single-topic segments with labels (pointer ranges, no text copies)
65
- │
66
- ▼ BUCKET ───────────────── AI + deterministic: group similar topics
67
- buckets: one per STORY/TOPIC; everything discussed about that topic since the
68
- last run accumulates here (a bucket usually has NO article of its own)
69
- │
70
- ▼ RESOLVE ──────────────── which articles does each story affect?
71
- bucket delta → the subject articles it touches (update · create · sections)
72
- │
73
- ▼ SYNTHESIZE ───────────── AI: per-section create/patch, bounded in and out
74
- create: outline → per-section draft · update: patch one section, rest byte-identical
75
- │
76
- ▼ ── deterministic gates ── citation survival · footnote bijection ·
77
- │ word floor · wikilink resolution · weak output → NO-OP
78
- │
79
- ▼ RECONCILE ────────────── infobox = ground truth; lead/body/infobox agree
80
- ▼ LINK ─────────────────── cross-link: mentions → [[links]], co-occurrence → See also
81
- ▼ NORMALIZE ────────────── deterministic: footnote renumber, wikilink canonicalize,
82
- │ structure guards (References last, stub marking)
83
- ▼ EDITORIAL (wikify) ───── AI: remove redundancy, organize, make it read well —
84
- │ per-section improve + structural pass over the TOC
85
- ▼
86
- commit to vault (git) + run changelog
178
+ pi sessions / Lucien import ingest.ts (watermark per source) [NOT wired into the nightly run — §9]
179
+ │
180
+ ▼ SEGMENT ─────────── AI (memory-chunk adapter): one conversation → single-topic chunks + labels
181
+ ▼ ENTITY-EXTRACT ──── AI (base+Entities.md): one chunk → the THINGS it is about (name list)
182
+ ▼ ROUTE ────────────── deterministic resolve → binary disambiguate → create | routed | capture
183
+ ▼ SYNTHESIZE
184
+ │ CREATE ─────────── AI: sub-cluster → OUTLINE → per-section DRAFT → INFOBOX → assemble
185
+ │ SECTION-ROUTE ──── AI: M×N binary "does this chunk update THIS section?" (+ name a new one)
186
+ │ PATCH ──────────── AI: one (chunk, section) → replacement section, rest byte-identical
187
+ │ RECONCILE ──────── AI+code: infobox = ground truth; lead/body/infobox agree; History
188
+ │ ── every write: NORMALIZE (code) → conservative gate (code) → weak ⇒ NO-OP ──
189
+ ▼ LINK ─────────────── code: first-mention [[links]] + co-occurrence See also (idempotent)
190
+ ▼ commit (git)
191
+ ▼ WIKIFY sweep ─────── AI (editor): per-section tighten + infobox refresh, every article
87
192
  ```
88
193
 
89
- Every stage is an independent, resumable, DB-state-keyed worker
90
- (`src/memory/stages.ts`), runnable separately or as the full DAG
91
- (`mlx-bun memory synthesize`).
92
-
93
- ### SEGMENT — conversations → topics
94
-
95
- The e4b chunk adapter (`e4b-chunk-300`, symlinked `memory-chunk`) segments each
96
- conversation into single-topic chunks with short labels. One chunk = one
97
- coherent topic; a multi-stage process is ONE topic; long single-topic
98
- conversations stay whole. Chunks are pointer ranges over `messages` — text is
99
- materialized exactly once. Deterministic anchor repair validates every boundary.
100
-
101
- ### BUCKET — group similar topics (first-class stage)
102
-
103
- **The Wikipedia-editor analogy (this is the model — Josh's framing).** A news
104
- story gets reported seven times in a day — Fox says one thing, MSNBC another,
105
- CBS a third, each report carrying a bit of different information. A Wikipedia
106
- editor who heard ALL of the reports incorporates everything they learned into
107
- every article the story affects. In our system: the news shows are
108
- **conversations**, the stories are **topics (chunks)**, and the **bucket is the
109
- editor's brain** — the place where everything reported TODAY about THAT TOPIC
110
- accumulates before any article is touched.
111
-
112
- **A bucket is keyed by the STORY/TOPIC, not by a subject.** The worked example:
113
- the topic is *"2026 Lincoln Memorial Reflecting Pool controversy"* (the algae
114
- issue in the pool that Trump renovated). Every report about it that day goes in
115
- that one bucket. There is **no article for that topic** — and there may never
116
- be — but the story affects THREE articles: *Lincoln Memorial Reflecting Pool*,
117
- *Second presidency of Donald Trump*, and *John J. Cafaro*. Each article
118
- receives the facet of the delta relevant to it (renovation/algae facts vs.
119
- abuse-of-power framing vs. the contract angle). This is why bucket ≠ article is
120
- **structural**: articles are THINGS (subjects); buckets are STORIES (what got
121
- discussed); the story→things relationship is inherently many-to-many.
122
-
123
- Assignment mechanics: group chunks by topic — normalize/compare chunk labels
124
- deterministically first, with a small-model binary judgment ("are these two
125
- chunks about the same story? yes/no") for near-misses, and a new-bucket
126
- proposal for genuinely new topics. The taxonomy is emergent: buckets come into
127
- existence because the user talked about something, never from a predefined
128
- list. Buckets are naturally short-lived relative to articles — a story
129
- accumulates while it's live and goes quiet when the user stops discussing it;
130
- the articles it fed persist.
131
-
132
- `_captured` is the reserved catch-all bucket: chunks whose subject doesn't yet
133
- justify assignment stay there, searchable, and are revisited on later runs —
134
- **nothing is dropped**.
135
-
136
- ### RESOLVE — bucket deltas → articles/sections
137
-
138
- The unit of work from here on is the **bucket delta**: all of a bucket's new
139
- (un-synthesized) chunks since the last run, taken TOGETHER. For each non-empty
140
- delta, RESOLVE answers the editor's question: **"which articles does this story
141
- affect?"** (the Reflecting Pool story → the pool article, the presidency
142
- article, the contractor article). For each affected subject:
143
-
144
- - **The article exists** → UPDATE. Section-route the delta's *facet for that
145
- article* via binary per-section judgments over the TOC ("does this new
146
- information update THIS section? yes/no"); all-no → propose a new section.
147
- - **The article doesn't exist yet** → CREATE the subject article (a THING —
148
- never an article named after the story itself). Thin is fine — 2–3 chunks
149
- make a legitimate stub (stubs are a feature, not a failure).
150
-
151
- The per-section ledger (`synthesized_chunk_sections`) records which chunks each
152
- fold incorporated, making every fold idempotent and every claim traceable to
153
- its source chunks.
154
-
155
- ### SYNTHESIZE — one edit per (bucket, article), never chunk-at-a-time
156
-
157
- **The cardinal rule:** synthesis is the editor sitting down ONCE per article
158
- with everything the bucket learned. Never patch a section from one chunk and
159
- then re-patch it from the next chunk five minutes later — sequential
160
- single-chunk edits overwrite each other, churn citations, and produce
161
- patchwork prose. The whole point of the bucket layer is that the delta arrives
162
- at the article **already aggregated**.
163
-
164
- - **CREATE**: group the bucket's chunks into proto-sections (by label/subject
165
- affinity — small-model judgment, no embeddings) → outline (title, lead,
166
- section names) → draft each section from only its chunks. The model never
167
- holds a whole article in its head.
168
- - **UPDATE (patch)**: hand the model one section + the delta's facet relevant
169
- to that article and section (all of it, with per-conversation `[^N]` citation
170
- material); it rewrites that one section once, weaving everything in. Facet
171
- extraction is implicit: the same story becomes algae/renovation facts in the
172
- Reflecting Pool article and framing in the presidency article, because the
173
- model writes what's relevant to the section it's editing.
174
- Contradiction = correction: resolve toward the latest statement, including
175
- contradictions *within* the delta (latest chunk wins — the editor heard all
176
- seven reports and writes the reconciled version, not seven amendments).
177
- User's evolving positions keep a `## History` trace; AI factual errors are
178
- silently overwritten.
179
- - **Edit mechanics (SETTLED 2026-07-01, Josh-confirmed — and how Lucien always
180
- worked): the model never edits the article; it is handed the current section
181
- + the new information and outputs a complete REPLACEMENT SECTION.** Not an
182
- in-place file edit (the
183
- model would have to hold the whole article — the exact blob failure this
184
- design avoids) and not a diff (small local models produce brittle patch
185
- formats, and a diff is hard to gate). A full replacement section is bounded
186
- in and out, trivially appliable (`extractSection` swap — the rest of the file
187
- byte-identical by construction), and directly gateable: the gate compares old
188
- section vs. new section for word floor and citation survival. Deterministic
189
- code performs the actual write. CREATE works the same way — the model emits
190
- one section at a time; code assembles the article.
191
- - **Multiple buckets converging on one article** (the algae-controversy bucket
192
- AND the olympian-arrest bucket both affect *Reflecting Pool*): each bucket's
193
- facet folds as its own section-level edit within the run — different stories
194
- usually land in different sections. If two buckets touch the SAME section,
195
- fold chronologically (latest wins, per self-healing) and let the nightly
196
- EDITORIAL pass smooth the seam; the editorial pass runs after all folds, so
197
- the article the user reads next morning is one coherent account.
198
- - **Conservative write gate** on every output: ≥70% word floor on the touched
199
- section, every pre-existing `conv:` citation survives, H1/References survive,
200
- footnote bijection. Weak output → NO-OP; the delta retries next run.
201
-
202
- ### RECONCILE · LINK · NORMALIZE — nightly deterministic/structural stages
203
-
204
- These run **every night**, not periodically:
205
-
206
- - **RECONCILE** (`reconcile.ts`): the infobox is the article's ground truth;
207
- lead, body sections, and infobox must agree; date-aware (latest wins).
208
- Infobox extraction/refresh is part of this — every article carries a current,
209
- parseable infobox whose entity-valued fields are `[[wikilinks]]`.
210
- - **LINK** (`crosslink.ts`): cross-linking is a dedicated stage, NOT
211
- synthesis's job (synthesis sees one article and cannot know the others).
212
- Mention-based prose links + co-occurrence See-also. Idempotent. Ensures **all
213
- subjects are wikilinked** across the vault.
214
- - **NORMALIZE** (`normalize.ts`): pure code — footnote renumber/merge, wikilink
215
- canonicalization, orphan repair, structure guards (`## References` last,
216
- `{{stub}}`/`{{Main}}` recognition, title == H1 == stem).
217
-
218
- ### EDITORIAL (wikify) — make the article good
219
-
220
- The final AI pass, and the reason articles stay readable instead of accreting
221
- patches: per-section "tighten this — remove redundancy, keep every citation and
222
- every specific detail" plus a structural pass over the TOC (reorder sections,
223
- refresh the lead, split a section that outgrew the article into its own article
224
- with a `{{Main}}` summary, merge duplicate stubs). Behind the same conservative
225
- gate; a weak edit is rejected and the original kept.
226
-
227
- ## Nightly run contract
228
-
229
- - Trigger: launchd (`com.mlx-bun.memory`), logs under `~/.mlx-bun/logs/`.
230
- - Scope: only conversations past the per-source watermark — a normal night is
231
- minutes of work, not hours.
232
- - Inference: one resident base model (Gemma-4-e4b), per-stage adapter mounted
233
- iff `~/.cache/mlx-bun/adapters/memory-<stage>` exists, else base+policy.
234
- Route generation through the persistent server (load-once) when available;
235
- no-server fallback works but pays model load per invocation.
236
- - Every article change is a git commit; the run ends with a changelog summary.
237
- - Failure isolation: stages are resumable; a failed bucket/article never blocks
238
- the rest of the run.
239
-
240
- ## Decisions overridden from the master plan (explicit, so no agent re-derives them)
241
-
242
- | Master-plan statement | Superseded by |
194
+ Orchestration: `runSynthesis` (`src/memory/pipeline.ts`) drives
195
+ `runPipeline` = `runSegmentStage → runExtractStage → runRouteStage →
196
+ runSynthesizeStage → runLinkStage`, then `wikifyVault` over every article.
197
+ Each stage worker (`src/memory/stages.ts`) pulls its eligible work from the
198
+ DB by state, processes a bounded batch, persists, and is individually
199
+ runnable: `mlx-bun memory segment | extract | route | synthesize-stage |
200
+ link`; `mlx-bun memory synthesize` is the full DAG; `--dry-run` plans without
201
+ model calls. A failed article never blocks the rest of a run.
202
+
203
+ ### 3.1 INGEST (`ingest.ts`)
204
+
205
+ Two sources, watermark-gated per `source`: pi sessions via
206
+ `SessionManager.listAll()` (`ingestSessions`) and the one-time Lucien
207
+ bootstrap (`ingestLucien`, read-only; maps Lucien's UUID-anchored chunks to
208
+ positions). Idempotent — a re-touched conversation is rewritten in place.
209
+ **As built, neither entry point is called by `runSynthesis` or any
210
+ `scripts/memory/` driver** (only `tests/memory-ingest.test.ts` exercises
211
+ `ingestLucien`); `runPipeline` emits "ingest: corpus already in the store".
212
+ The nightly fold-in of new pi sessions is therefore not yet end-to-end — see
213
+ §9.
214
+
215
+ ### 3.2 SEGMENT (`chunk.ts`, `chunk-validation.ts`)
216
+
217
+ The `memory-chunk` adapter (`e4b-chunk-300`, symlinked to
218
+ `~/.cache/mlx-bun/adapters/memory-chunk`) segments each conversation whose
219
+ `chunked_at` is NULL or older than `updated_at`. Prompt = `CHUNK_SYSTEM`
220
+ (the exact trained system turn, byte-for-byte) + `CHUNK_PROMPT` with
221
+ `Chunking.md` + `Topics_to_Ignore.md` inlined. Output is JSON of message-UUID
222
+ anchors + a 4–10-word label; anchors are validated and repaired
223
+ (`validateChunks`) before pointer rows are written; a conversation whose
224
+ output fails validation is left unmarked and retried next run. Re-run is a
225
+ no-op by watermark. Chunk purity is the foundation of everything downstream:
226
+ a chunk that fuses two topics pollutes every article it routes to.
227
+
228
+ ### 3.3 ENTITY-EXTRACT (`entity.ts`, `resolve.ts`)
229
+
230
+ One chunk per call, base model + `Entities.md`: "what things is this chunk
231
+ about? canonical wiki-title names, one per line, specific things AND the
232
+ broad domain, NONE if nothing nameable." Plain newline list parsed by
233
+ `parseLines` — no multi-item JSON, no batch indices (the batch-index drift
234
+ that sank Lucien's batched assign). Persists `chunk_entities(chunk_id,
235
+ entity_name, surface_form)` and a candidate `entities` row (article_stem
236
+ NULL). Canonicalisation is deterministic and conservative
237
+ (`EntityResolver`): `canonicalize` (casefold, whitespace, leading
238
+ articles/possessives) → `squeeze` (strip all non-alphanumerics) → alias index
239
+ (store + `goldens/dreaming-entities-gold.json` seeds) → token-subset fuzzy
240
+ match requiring a distinctive token (the over-merge guard). When in doubt it
241
+ mints a new canonical rather than fusing two things.
242
+
243
+ ### 3.4 ROUTE (`route.ts`, `redirect.ts`)
244
+
245
+ Per surface: (1) RESOLVE against the known-entity index — a hit folds into
246
+ that article with no model call; (2) DISAMBIGUATE a miss with a trigram
247
+ shortlist of existing entities and one bounded binary per candidate ("is X
248
+ the same thing as Y? yes/no", `parseBinary`, existing wins); (3) CREATE /
249
+ CAPTURE for a surface matching nothing: it earns its own article when it
250
+ RECURS (`RECURRENCE_THRESHOLD = 2` routed chunks) or, with the optional
251
+ `useSubjectGate`, when a chunk engages it as a genuine subject; otherwise it
252
+ is CAPTURED — its chunks go to the reserved `_captured` bucket, `notable = 0`,
253
+ still searchable, never dropped, promoted later if it recurs. There is no
254
+ ownership/usefulness test. `runRouteStage` also registers
255
+ `subject_redirects` so every discussed subject resolves to a home (a folded
256
+ subject → its article; a captured subject → the article it most co-occurs
257
+ with). A chunk fans out to every article it names — this is where facet
258
+ extraction falls out: the same chunk lands on several articles, each keeping
259
+ only what is relevant to it at patch time.
260
+
261
+ ### 3.5 SYNTHESIZE (`stages.ts` `runSynthesizeStage`, `synthesize.ts`, `cluster.ts`)
262
+
263
+ Chronological throughout; commits once at the end.
264
+
265
+ - **CREATE** (`synthesizeCreate`) for each `notable = 1` entity with no
266
+ article file, oldest entity first, capped by `DEFAULT_ARTICLE_CAP = 20`
267
+ per pass: `subClusterChunks` groups the entity's chunks into proto-sections
268
+ by label/text token overlap (deterministic, no embeddings) → OUTLINE (2–6
269
+ headings) → per-section DRAFT from only that section's chunks → LEAD →
270
+ seed INFOBOX (`buildInfoboxFields`, kind + declared aliases) → assemble →
271
+ NORMALIZE → gate → write + `synthesized_chunk_sections` ledger. The model
272
+ never holds a whole article. Leaky drafts retry once, then NO-OP.
273
+ - **SECTION-ROUTE** (`routeSections`) for each chunk routed to an existing
274
+ article and not yet in the ledger: one binary per routable section
275
+ (structural tails References/See also/Notes excluded; the lead is a
276
+ first-class target, `LEAD_ANCHOR`), prompt = title + heading + section's
277
+ first ~2 sentences + chunk label + gist. All-no + substantive → "name a new
278
+ section (2–5 words) or NONE"; a named section is honoured
279
+ (`synthesizeNewSection`), never a silent drop. Hierarchical pruning is
280
+ structural: only articles ROUTE matched are ever gridded.
281
+ - **PATCH** (`synthesizePatch`, `patchSection`) — the model is handed the
282
+ current section + the chunk (with its pre-assigned `[^N]`) and outputs a
283
+ complete REPLACEMENT SECTION; code swaps exactly that section
284
+ (`replaceSection`), appends the definition line, and leaves the rest of
285
+ the file byte-identical. Contradiction = correction: rewrite toward the
286
+ latest statement, classified by `classifyContradiction` (`history.ts`) as
287
+ EVOLUTION (keep a `## History` entry citing both sources; default when
288
+ ambiguous) or ERROR (silent overwrite, the wrong value's citation may be
289
+ pruned via `allowDroppedHashes`). Idempotent via the ledger PK.
290
+ - **RECONCILE** (`reconcileArticle`) runs after the patch loop over every
291
+ article touched this run: the infobox is ground truth; relationship
292
+ fields (favourite/opinion/status/owned … — never `PHYSICAL_SPEC_KEYS`) and
293
+ sibling body sections still asserting a superseded value are refreshed
294
+ toward the latest-dated position, provenance-preserving (every `[^k]`
295
+ survives, phrased as change over time). Consistent article ⇒ NO-OP;
296
+ unresolved present-tense assertions are logged, not forced.
297
+
298
+ ### 3.6 The conservative write gate (`gate.ts`) and NORMALIZE (`normalize.ts`)
299
+
300
+ `gateEdit(before, after)` is a faithful port of Lucien's editorial gate and
301
+ guards every write (create, patch, new section, reconcile, link, wikify):
302
+ every pre-existing `conv:` citation survives (unless deliberately pruned),
303
+ footnote bijection, edited word count ≥ `floor` (default 0.7) of the
304
+ original, H1 and `## References` survive, at most one `## See also`. It can
305
+ only reject, never rewrite; it catches citation/structure/bulk-prose loss,
306
+ not nuance loss (that is the judge's job, §8).
307
+
308
+ `normalizeArticle` runs inside every write before the gate, pure and
309
+ idempotent: footnote renumber/merge (`normalizeFootnotes`), wikilink
310
+ canonicalisation to on-disk stems (`normalizeWikilinks`), infobox key sort,
311
+ section order (`## See also` before `## References` last), `{{stub}}` guard
312
+ for thin articles (`articleThinness`), `title = H1 = stem`, fence repair.
313
+ Non-fatal per step. `normalizeVault` exists for a whole-vault pass but is not
314
+ a nightly stage; normalisation is constitutive of each write instead.
315
+
316
+ ### 3.7 LINK (`crosslink.ts`)
317
+
318
+ Cross-linking is its own deterministic stage because synthesis is bounded to
319
+ one article and cannot know the others. (1) MENTION edges: wrap the first
320
+ whole-word mention of another article's title/alias as
321
+ `[[Stem|surface]]` — never inside an existing link, code span, marker,
322
+ infobox, heading, or References. (2) CO-OCCURRENCE edges from shared folded
323
+ chunks (`synthesized_chunk_sections` ⋈ `chunk_entities`). (3) Rebuild
324
+ `## See also` idempotently (mentions first, then top co-occurring articles
325
+ that exist). Every edit passes `normalizeWikilinks` + `gateEdit`; a fully
326
+ linked article re-runs to a byte-identical no-op. Measured on a 587-article
327
+ snapshot of the import (2026-06-28, M1 Max 32 GB): See-also coverage 18% →
328
+ 96%, resolved edges 309 → 7026.
329
+
330
+ ### 3.8 WIKIFY — the editorial pass (`wikify.ts`)
331
+
332
+ The periodic sweep, distinct from NORMALIZE: the model supplies judgment, the
333
+ gate disposes of weak judgment. As built, `runSynthesis` runs `wikifyVault`
334
+ over EVERY article every night after the write branch; each article commits
335
+ its own change or NO-OPs, and a wikify defect cannot lose the committed
336
+ create/patch work.
337
+
338
+ - Per-section IMPROVE (`improveSections`, `editor` stage): "tighten this
339
+ section — remove redundancy, keep every citation and every specific
340
+ detail." Behind the gate.
341
+ - Infobox EXTRACT/REFRESH (`refreshInfobox`): creates a missing infobox,
342
+ refreshes present ones without losing a grounded fact, entity-valued
343
+ fields as wikilinks, declared `aliases:` feed the entity index on reindex.
344
+ - Summary-style SPLIT (`detectSplitCandidate`, `applySplit`): when one body
345
+ section dominates the article it becomes a child article with the full
346
+ cited prose; the parent keeps a summary + `*Main article: [[Child]]*`
347
+ (a `via=main` edge). Skips the word floor (a sanctioned bulk move); gated
348
+ structurally instead. Series banner maintenance (`setSeriesBanner`).
349
+ - MERGE signals (`detectMergeCandidates`) and structure suggestions are
350
+ written to `Talk/<stem>.md` (`writeTalkPage`) — the pass never
351
+ restructures across articles on its own. `sectionCohesion` (an injected
352
+ `EmbedFn` silhouette) exists here as an offline triage instrument only;
353
+ `wikify.ts` never imports `src/embed.ts`.
354
+
355
+ ### 3.9 Nightly run contract
356
+
357
+ - Trigger: launchd `com.mlx-bun.memory` (`schedule.ts`; `mlx-bun memory
358
+ schedule --at HH:MM`, default 03:00), running `mlx-bun memory synthesize`;
359
+ logs `~/.mlx-bun/logs/memory-synthesis.{out,err}.log`.
360
+ - Scope: only work past each stage's state (watermark / missing downstream
361
+ row / ledger). A normal night is minutes, not hours; throughput only
362
+ matters for the one-time import.
363
+ - Inference: one resident Gemma-4-e4b (§4); a stage's adapter is mounted iff
364
+ `~/.cache/mlx-bun/adapters/memory-<stage>` exists, else base + policy.
365
+ - Every article change is a git commit; the run ends with a summary line.
366
+ - Never started by an agent: the full-corpus import and any GPU training are
367
+ user actions.
368
+
369
+ ## 4. The inference seam (`src/memory/model.ts`)
370
+
371
+ One shared runtime: one `RuntimeModel` (Gemma-4-e4b OptiQ-4bit, snapshot
372
+ resolved by globbing for `config.json`), the `memory-chunk` adapter mounted
373
+ once and ACTIVATED per call (`loraState.active`), one in-process
374
+ `BatchScheduler`. Two seams: `callLocal(stage, {system?, user})` (bit-exact
375
+ greedy via the eval runner's raw-forward decode) and `callLocalBatch(stage,
376
+ inputs[])` (order-preserving; batched only when `MLX_BUN_MEMORY_BATCH > 1`
377
+ and N > 1, else the same serial loop). Never auto-downloads; throws with the
378
+ `hf download` hint until the snapshot lands.
379
+
380
+ **Templating.** Each stage renders a `[system?, user]` message array through
381
+ the model's chat template exactly as the server and the trainer's
382
+ prompt-region render do (render with generation prompt → encode → dedupe a
383
+ double BOS; `memoryPromptIds`). This fixed the bring-up defect where the
384
+ chunk adapter — SFT'd with a system turn that Gemma-4 renders as a distinct
385
+ `<|turn>system…<turn|>` block — was decoded with the system segment dropped.
386
+ `tests/memory-templating.test.ts` asserts `memoryPromptIds("chunk", …)` is
387
+ id-identical to a real training row's prompt region. Base stages get concise
388
+ default systems (`DEFAULT_STAGE_SYSTEM`); only `chunk` must match training
389
+ byte-for-byte.
390
+
391
+ **Token budget.** `MAX_OUTPUT_TOKENS = 64_000` is the single backstop; stages
392
+ pass it rather than per-call caps, because a finished answer stops at EOS and
393
+ a cap can only truncate an unfinished one. (The per-stage 4/16/128/2048 caps
394
+ in the original inference-path design are superseded; `extractEntityNamesRaw`
395
+ still defaults to 128 when called directly.)
396
+
397
+ **Batching — implemented, off by default (decision: Josh, 2026-07-01).**
398
+ Measured 2026-06-28 on Gemma-4-e4b OptiQ-4bit, held-out
399
+ `chunk-data-le4000/valid.jsonl` + the live 2096-conversation store, M-series
400
+ GPU: 8 extract-style prompts (~400-token prefills) batched 9345 ms vs serial
401
+ 5001 ms (≈1.9× slower, 8/8 exact); 8 short replies batched 2085 ms vs 1067 ms
402
+ (≈2× slower, 7/8 match — one documented near-tie divergence); only 16 short
403
+ uniform one-liners won (≈1.2× faster). The warm uniform chunk workload on the
404
+ M1 Max 32 GB measured 0.607 vs 0.357 s/chunk (~1.7× slower). Root cause: the
405
+ scheduler pads heterogeneous prefills to the batch max, losing to one
406
+ efficient single-sequence prefill per row. The real efficiency lever for
407
+ daily use is load-once (the shared runtime above; a persistent-server route
408
+ remains open, §9), not batching.
409
+
410
+ **Adapter constraint.** `loraState.active` is a single global field read per
411
+ forward, so a batch is necessarily single-adapter; `callLocalBatch` is
412
+ per-stage and stage→adapter is fixed. The seam must never be shared with a
413
+ live server process.
414
+
415
+ ## 5. Target design: the BUCKET layer (settled with Josh 2026-07-01, not built)
416
+
417
+ **The Wikipedia-editor analogy.** A news story is reported seven times in a
418
+ day, each report carrying a little different information; an editor who
419
+ heard ALL of them incorporates everything into every article the story
420
+ affects. Conversations are the news shows, topics (chunks) are the stories,
421
+ and the **bucket is the editor's brain** — where everything reported TODAY
422
+ about THAT STORY accumulates before any article is touched.
423
+
424
+ **A bucket is keyed by the STORY, not by a subject.** Worked example: the
425
+ story "2026 Lincoln Memorial Reflecting Pool controversy" has no article of
426
+ its own and may never get one, but it affects three: *Lincoln Memorial
427
+ Reflecting Pool*, *Second presidency of Donald Trump*, *John J. Cafaro*. Each
428
+ receives the facet relevant to it. Bucket ≠ article is structural: articles
429
+ are THINGS, buckets are STORIES, story→things is many-to-many. Buckets are
430
+ emergent (never a predefined list) and short-lived relative to articles.
431
+
432
+ Target stage order:
433
+
434
+ ```
435
+ SEGMENT → BUCKET → RESOLVE → SYNTHESIZE (delta-at-once) → RECONCILE → LINK → NORMALIZE → EDITORIAL
436
+ ```
437
+
438
+ - **BUCKET** — group chunks by story: normalise/compare labels
439
+ deterministically first, a binary "same story? yes/no" for near-misses, a
440
+ new-bucket proposal for genuinely new stories. `_captured` remains the
441
+ catch-all so nothing is dropped.
442
+ - **RESOLVE** — per non-empty bucket delta (all new chunks since the last
443
+ run, TOGETHER): "which articles does this story affect?" Existing article →
444
+ UPDATE (section-route the delta's facet for that article); missing →
445
+ CREATE the subject article (a THING, never an article named after the
446
+ story). The as-built entity-keyed ROUTE becomes RESOLVE.
447
+ - **SYNTHESIZE, one edit per (bucket, article)** — the cardinal rule. Never
448
+ patch a section from one chunk and re-patch it from the next five minutes
449
+ later: sequential single-chunk edits overwrite each other, churn
450
+ citations, and produce patchwork prose. The delta arrives aggregated; the
451
+ model rewrites one section once, weaving everything in, resolving
452
+ contradictions within the delta toward the latest chunk. Edit mechanics
453
+ stay as built (replacement section, code performs the write, same gate).
454
+ Two buckets touching the SAME section of one article fold
455
+ chronologically; the editorial pass smooths the seam.
456
+
457
+ As-built vs target, so nobody re-derives it:
458
+
459
+ | Target (2026-07-01) | As built (2026-08-23) |
460
+ | --- | --- |
461
+ | Story-keyed BUCKET stage between SEGMENT and article fan-out | No bucket stage; `buckets`/`chunk_buckets` hold only `_captured` |
462
+ | RESOLVE: bucket delta → affected articles | ROUTE: chunk-entity surfaces → articles (`route.ts`) |
463
+ | One edit per (bucket, article) per run | One PATCH per (chunk, section), chronological |
464
+ | RECONCILE every night over every article | RECONCILE over articles touched by this run's patches |
465
+ | EDITORIAL over touched articles (cadence open) | `wikifyVault` over every article every run |
466
+
467
+ ## 6. The read path (design; user-facing details in `docs/reference/memory.md`)
468
+
469
+ The read path is deliberately trivial — all the acceptance pressure is on
470
+ the write side keeping each article a good, current account. FIND → READ →
471
+ ANSWER in small reads, never a context dump: find the one article (name /
472
+ alias / category / a `[[link]]` hop), open its TOC, read the ONE relevant
473
+ section, answer from the user's recorded position there. Silent-colleague
474
+ contract (ported from Lucien): look up before answering; each article is the
475
+ consolidated position, don't reconstruct from raw history; speak as a
476
+ continuation, never "per the wiki…".
477
+
478
+ - Tools (`src/memory/tools.ts`, registered by the shared pi surface for
479
+ terminal and web, read-only, auto-allowed): `memory_resolve`,
480
+ `memory_category`, `memory_read` (TOC + lead by default; full text only on
481
+ request), `memory_section` (the default read), `memory_links`,
482
+ `memory_infobox` (readable content, not a query), `memory_list`,
483
+ `memory_status`, `memory_search` (last-resort substring fallback). There is
484
+ deliberately no infobox-field query tool. mlx-bun's own docs are a
485
+ separate `reference_*` surface, never mixed into personal memory.
486
+ - Navigation index (`src/memory/query.ts`): mtime-incremental in-memory maps
487
+ (alias → stem, category/series/kind/type → stems, infobox, lead, link
488
+ graph) mirroring the derived tables; zero embedding calls by contract.
489
+ - Exposure: tools and the bundled memory skill appear only when the vault
490
+ exists; `memoryIndexHint()` adds a one-line presence hint, never a title
491
+ dump. Memory is for personal continuity, not weather, public facts, or
492
+ ordinary coding tasks.
493
+ - Acceptance budget (the bar Lucien's traces failed at 33–150 KB per query,
494
+ one 671-line blob read up to 42× in a query): mean context < 5 KB (8 KB
495
+ hard ceiling), no single read > 2 KB, `getEmbedCounter() == 0`, local
496
+ only, silent. Harness: `scripts/memory/eval-northstar.ts` over the frozen
497
+ `scripts/memory/northstar-queries.json` (Q1–Q6; Q6 is the negative
498
+ control), graded by `scripts/memory/judge-answer.ts`; traversal proof
499
+ `scripts/memory/north-star-traversal.ts`.
500
+
501
+ ## 7. Adapters and training
502
+
503
+ One base model for every stage. Per-stage adapters are product assets (the
504
+ creator trains them, publishes to Hugging Face, `memory init` may offer to
505
+ download); they never get write authority — they produce candidates, the
506
+ gates decide.
507
+
508
+ | stage string | task | adapter |
509
+ | --- | --- | --- |
510
+ | `chunk` | one conversation → single-topic chunks + labels | **`memory-chunk` = `e4b-chunk-300` (exists, the only trained adapter)** |
511
+ | `entity` | one chunk → the things it is about | base + `Entities.md` (`memory-entity` only on a measured recall miss) |
512
+ | `route` | binary "same thing?" / "engages as subject?" | base |
513
+ | `section` | binary "does this chunk update THIS section?" / name a new one | base (`memory-section` only if the binary misfires) |
514
+ | `synthesis` | draft one section / integrate one chunk into one section / reconcile | base (`memory-synthesis` is the highest-value adapter to train) |
515
+ | `editor` | tighten one section / refresh an infobox / summarise a split | base (`memory-editor` if base misfires) |
516
+
517
+ Staged LoRA decision, per stage: measure the base + inlined policy on the
518
+ stage's own judge first; train only on a measured miss; pick checkpoints by
519
+ the task judge, never validation loss. Recipe: `scripts/examples/chunk-finetune.ts`
520
+ (sft, rank 16, lr 1e-5, seq 8192, `ops.sdpa`; e4b needs the segmented-backward
521
+ path — `SEG=2` as `e4b-chunk-300` was trained; see
522
+ `docs/design/orpo-training.md`). Training data uses the
523
+ trainer's `messages` row shape with loss masked to the assistant turn.
524
+ Section-granular synthesis gold can be mined from Lucien's structured tables
525
+ plus `~/Dreaming` git history (chunk identity from `chunks`/`chunk_buckets`/
526
+ `synthesized_bucket_chunks`, section before/after from integration commits) —
527
+ never from footnote parsing. All training is a user action.
528
+
529
+ Recorded chunk-adapter numbers (`reports/orpo-chunk-paper/research-journal.md`,
530
+ via `scripts/examples/chunk-eval.ts`): `e4b-chunk-300` purity 92.2 /
531
+ cohesion 87.8 vs base e4b 99.5 / 58.8; the e4b metric is purity × cohesion.
532
+ The chunk eval score is well-formedness, not accuracy — see the memory note
533
+ "chunk-eval 95.10 = format, not accuracy".
534
+
535
+ ## 8. Evaluation methodology (beside the system)
536
+
537
+ - **Lucien bucket-gold is NOT the write-path oracle.** Its 379-bucket /
538
+ 385-article topic-bin taxonomy (245 `X_and_Y` bins, 0 infoboxes) is
539
+ exactly what this pipeline replaces; bucket-F1 / Jaccard against it are
540
+ routing-instrument numbers, never a pipeline-health gate.
541
+ - **Cloud LLM-judge on real output + hand-curated gold.** Quality is judged
542
+ by a strong cloud model reading END articles (the article the user reads
543
+ next morning, after every stage has run — not intermediate drafts), against
544
+ `goldens/dreaming-entities-gold.json` (hand-curated variant groups and
545
+ notable entities, grounded by reading `~/Dreaming` and live chunks; NOT
546
+ derived from bucket-gold). The judge is never in the hot path.
547
+ - **Metrics that mean something:** topic aggregation (many conversations →
548
+ one article), coverage (0 engaged-with things lost even as article count
549
+ drops), structural invariants (title is a thing, infobox present and
550
+ resolvable, footnote bijection, every claim cited), gate pass-rate, and
551
+ the read-path byte/embedding budgets. Do not measure articles per
552
+ conversation — there is no conv→article link.
553
+ - **Embeddings as instruments only.** They measure cohesion/dispersion well
554
+ (silhouette ρ≈0.68 vs the LLM judges, per-conversation) and purity poorly
555
+ (ρ≈0.32) — so purity rides on the chunker, never on cosine geometry. The
556
+ P4 label-proxy routing measured a pessimistic ~39% recall@12 on chunk
557
+ labels, which is why ENTITY-EXTRACT reads full chunk text.
558
+
559
+ Instruments, all under `scripts/memory/` (moved 2026-08-23; Lucien read-only
560
+ throughout):
561
+
562
+ | script | what it measures |
243
563
  | --- | --- |
244
- | Personal-notability checklist; CREATE gate "≥3 chunks OR owned/decided, else PARK as trivia"; ≤40-article first-run cap (P1-T5, P5-T4, R5, P9-T3) | **No notability filter.** Everything surfaces; `_captured` keeps the tail searchable; thin buckets → stubs. |
245
- | Buckets demoted to "optional staging, may ship empty" (L4) | **Buckets are first-class**: the per-subject accumulation layer between chunks and articles; synthesis consumes buckets. |
246
- | Embeddings allowed as offline instruments (P4 recall tripwire experiments, P8-T4 silhouette triage) | **No embeddings anywhere in the project.** The tripwire (`getEmbedCounter()==0`) remains as a guard, permanently. |
247
- | Routing/section-route acceptance gated on Lucien bucket-F1 / Jaccard (P4-T2, P7-T1) | Model-selection evals live **outside** the system design; pipeline health is judged on real output quality, never Lucien's bin taxonomy. |
248
- | Cross-linking + infobox work assigned to periodic WIKIFY (P8) | RECONCILE + LINK are **nightly stages** in the DAG; editorial wikify is the readability pass. |
249
-
250
- ## Work plan (docs-only reconciliation done; implementation next)
251
-
252
- Ordered; none of it touches the running full-corpus import
253
- (`~/.mlx-bun/wiki-full`, cursor file `~/.mlx-bun/full-run-cursor.txt`) or the
254
- uncommitted `src/model/universal` / trainer work.
255
-
256
- 1. **Bucket layer made explicit** — a topic-keyed BUCKET stage between SEGMENT
257
- and the article fan-out (`buckets` + `chunk_buckets` tables already exist):
258
- group chunks by story (label normalization + binary "same story?"
259
- judgments), `_captured` catch-all, and **delta-at-once synthesis** — the run
260
- collects each bucket's new chunks, then makes ONE edit per (bucket, article).
261
- The as-built entity-keyed ROUTE becomes RESOLVE (bucket → affected subject
262
- articles); the per-chunk patch loop is replaced, not wrapped. (Settled
263
- 2026-07-01 — the Wikipedia-editor / Reflecting-Pool framing above; buckets
264
- are STORIES, articles are THINGS, many-to-many by nature.)
265
- 2. **Nightly DAG order** — ensure `mlx-bun memory synthesize` runs
266
- segment → bucket → resolve → synthesize → reconcile → link → normalize →
267
- editorial every run (reconcile/link/infobox currently periodic/partial).
268
- 3. **Editorial stage** — wire the per-section improve + structural pass as the
269
- closing stage behind the conservative gate.
270
- 4. **Persistent-server inference routing** — load-once for all memory
271
- generation, no-server fallback (the in-progress inference-path rework;
272
- finish, don't fork).
273
- 5. **Promote + schedule** — after the import is judged good: point
274
- `~/.mlx-bun/wiki` at the bootstrapped content, enable the nightly launchd
275
- fold-in from pi sessions.
276
- 6. **Known quality fixes** — schematic-prompt leaks (<1% of articles), run-on
564
+ | `bucket-cohesion-signal.ts` | embedding shortlist recall ceiling chunk→bucket, per bucket-size bin (the bucketing-stage foundation check) |
565
+ | `chunk-embedding-signal.ts` · `chunk-judge-correlate.ts` | boundary-local embedding features vs the two LLM chunk judges |
566
+ | `eval-name-recall.ts` · `dreaming-lex.ts` | lexical (trigram + token-overlap) shortlist recall over names, no model |
567
+ | `eval-route.ts` | no-vector routing proof; `--phase embed` and `--phase route` run as separate processes so the embedder and e4b are never co-resident |
568
+ | `eval-entity-extract.ts` | precision/recall/F1 + canonicalisation rate for ENTITY-EXTRACT |
569
+ | `eval-section-route.ts` | per-pair accuracy + section-set Jaccard on frozen proxy gold; new-section-not-drop |
570
+ | `eval-patch.ts` | only-that-section-changed + self-healing scenarios, judged on output |
571
+ | `eval-wikify-split.ts` · `p8-judge-wikify.ts` · `wikify-smoke.ts` | split/series/merge/Talk mechanics; cloud-judge fixture for improve-not-degrade |
572
+ | `audit-bootstrap.ts` · `eval-coverage.ts` | structural audit of a bootstrap; migration parity against the notable-entity gold |
573
+ | `north-star-traversal.ts` · `eval-northstar.ts` · `judge-answer.ts` · `northstar-queries.json` | read-path proof, acceptance harness, grader |
574
+ | `segment-smoke.ts` · `dreaming-create-smoke.ts` · `dreaming-coldstart-smoke.ts` · `dreaming-stages-independent.ts` | wiring smokes (real adapter; one entity; whole DAG; stage independence, model-free) |
575
+ | `dreaming-selfheal-demo.ts` | the timestamp-ordered self-healing demonstration |
576
+ | `dreaming-full-run.ts` · `dreaming-slice-select.ts` · `dreaming-slice-run.ts` · `dreaming-slice-inventory.ts` · `dreaming-chunk-diag.ts` | the resumable full-corpus import driver (user-launched, writes `~/.mlx-bun/wiki-full`, cursor `~/.mlx-bun/full-run-cursor.txt`) and its slice tools |
577
+ | `mine-gold-entities.ts` | the frozen entity vocabulary (`goldens/entities.json`) from `~/Dreaming` titles/lead bolds/bucket names |
578
+ | `eval-pi-memory-fixation.ts` | the pi surface exposes memory only when enabled and names the negative cases |
579
+
580
+ **Contradiction with the no-embeddings principle, recorded rather than
581
+ hidden:** `wikify.ts sectionCohesion` (injected `EmbedFn`), the
582
+ `bucket-cohesion-signal` / `chunk-embedding-signal` / `chunk-judge-correlate`
583
+ scripts and `eval-route.ts --phase embed` still use an embedder. None of them
584
+ is on the pipeline or read path (`src/memory/` never imports `src/embed.ts`;
585
+ the tripwire asserts 0). The design rule as settled 2026-07-01 says "no
586
+ embeddings anywhere in the project"; the code keeps them as offline
587
+ instruments. Deleting them is an open decision (§9), not a silent rewrite.
588
+
589
+ ## 9. Open work (verified against `src/memory/` 2026-08-23)
590
+
591
+ Ordered. None of it touches the user's real vault (`~/.mlx-bun/wiki`) without
592
+ the promotion step.
593
+
594
+ 1. **Wire ingest into the nightly run.** `ingestSessions` has no caller
595
+ outside tests; `runSynthesis` assumes the corpus is already in the store.
596
+ Without this the launchd job cannot fold in new pi sessions. Sequence:
597
+ ingest → the existing DAG; watermark per source already exists.
598
+ 2. **Bucket layer** (§5): story-keyed BUCKET stage, RESOLVE, delta-at-once
599
+ synthesis replacing the per-chunk patch loop (not wrapping it). The
600
+ `buckets`/`chunk_buckets` tables already exist.
601
+ 3. **Nightly DAG order**: RECONCILE over every article (today: touched
602
+ stems only) and a single NORMALIZE/EDITORIAL closing pass in the settled
603
+ order.
604
+ 4. **Persistent-server inference routing**: route memory generation through
605
+ the resident server when one is running (load-once for daily use), with
606
+ the in-process runtime as the no-server fallback. Batching is not the
607
+ lever (§4).
608
+ 5. **Chunk user-turn drift**: `CHUNK_PROMPT` still differs from the trained
609
+ user turn (an added no-copy line; 2 inlined Meta pages vs 8 in training;
610
+ abstracted label examples; the training vault-path line). Either
611
+ reconcile the prompt to training or retrain `e4b-chunk-300` on the live
612
+ prompt — pick one, don't stay half-aligned. Re-segmenting conversations
613
+ chunked under the old user-only template is principled but low-urgency
614
+ (old output was valid in-range).
615
+ 6. **Promote + schedule**: after the import (`~/.mlx-bun/wiki-full`) is
616
+ judged good on a broad, diverse cloud-judged sample — `reindex`,
617
+ `mlx-bun memory link`, browse in Obsidian — point `~/.mlx-bun/wiki` at
618
+ it and enable the nightly job. The `Reference/` symlinks into the repo
619
+ are non-portable; drop them for the real personal vault.
620
+ 7. **Known quality fixes**: schematic-prompt leaks (<1% of articles), run-on
277
621
  leads, `owned:` framing on abstract concepts (reframe the lead spec per
278
- subject kind).
279
-
280
- ## Open questions (for discussion with Josh)
281
-
282
- - ~~Bucket granularity vs article granularity~~ — **SETTLED 2026-07-01**:
283
- buckets are a separate layer from articles (many-to-many both ways); the
284
- bucket is the editor's brain that aggregates the day's information about a
285
- subject, and RESOLVE fans the delta out to every affected article. Bucket is
286
- never a 1:1 alias for an article.
287
- - **When does a `_captured` chunk graduate?** Re-scan `_captured` every night
288
- for subjects that have since accumulated enough related chunks, or only when
289
- a new bucket's name matches?
290
- - **Editorial cadence.** Every night over every touched article, or touched
291
- articles nightly + a slower full-vault sweep?
622
+ subject kind), and stale in-code comments that still describe the dead
623
+ "≥3 chunks or owned/decided" CREATE rule (`synthesize.ts` header;
624
+ `prompts.ts` M1 stub constants) — the live rule is
625
+ `RECURRENCE_THRESHOLD = 2` in `route.ts`.
626
+ 8. **Per-stage adapters** (§7), each a user-launched training run gated on a
627
+ measured base miss: `memory-synthesis` first (RECONCILE logs
628
+ "UNRESOLVED … a memory-synthesis LoRA would close it" when the base model
629
+ leaves a stale present-tense assertion), then section, entity, editor.
630
+ Prerequisite: give `chunk-finetune.ts` a `TASK` selector defaulting
631
+ DATA/ADAPTER per stage and an e4b (not MiniCPM5) default model.
632
+ 9. **Embedding residue** (§8): decide whether `sectionCohesion` and the
633
+ embedding signal scripts stay as instruments or go.
634
+ 10. **Jobs-runner integration**: move `memory synthesize` onto the jobs
635
+ runner with SSE progress once manual synthesis is boring.
636
+
637
+ ## 10. Decisions overridden (explicit, so no agent re-derives them)
638
+
639
+ | Earlier statement | Superseded by |
640
+ | --- | --- |
641
+ | Personal-notability checklist; CREATE gate "≥3 chunks OR owned/decided, else PARK as trivia"; ≤40-article first-run cap (master plan P1-T5, P5-T4, R5, P9-T3) | **No notability filter.** Everything surfaces; `_captured` keeps the tail searchable; recurrence (2 chunks) or subject engagement earns an article; thin subjects → stubs. |
642
+ | Buckets demoted to "optional staging, may ship empty" (master plan L4, write-pipeline-entity) | **Buckets are first-class** story-keyed accumulators between chunks and articles (§5). |
643
+ | Embeddings as routing narrower / offline instruments (bucketing-stage, memory-synthesis section-routing, master plan P4/P8-T4) | **No embeddings in the pipeline**; the tripwire is permanent. Residual instruments are catalogued in §8 pending deletion. |
644
+ | Routing / section-route acceptance gated on Lucien bucket-F1 / Jaccard (P4-T2, P7-T1) | Model-selection evals live outside the design; pipeline health is judged on real END output by a cloud judge, never Lucien's bin taxonomy. |
645
+ | Cross-linking + infobox work assigned to periodic WIKIFY (P8) | LINK and RECONCILE are nightly stages; wikify is the readability pass. |
646
+ | Whole-article synthesis (Lucien `synthesize-update`) and per-chunk sequential patches (as built) | One replacement section per (bucket, article) per run, code performs the write (§5). |
647
+ | Per-stage `maxTokens` caps 4/16/128/2048 (inference-path design) | One `MAX_OUTPUT_TOKENS` backstop; never cap below it. |
648
+ | Batching as the import throughput lever | Measured slower for the real workload; default serial; load-once is the lever. |
649
+ | Turso / vector-capable store evaluation | Moot without embeddings; bun:sqlite stays. |
650
+
651
+ ## 11. Open questions
652
+
653
+ - **When does a `_captured` chunk graduate?** Re-scan `_captured` nightly for
654
+ subjects that have since accumulated related chunks, or only when a new
655
+ bucket's name matches? (Today: promotion happens when ROUTE recomputes and
656
+ the subject recurs.)
657
+ - **Editorial cadence.** Every night over every article (as built), or
658
+ touched articles nightly plus a slower full-vault sweep?
659
+ - **Provisional / temporal facts.** A convention (e.g. "as of YYYY-MM" tied
660
+ to the citation date) so "work set to begin July" updates cleanly to
661
+ "began / completed" rather than contradicting.
662
+ - **Domain vs specific granularity.** ENTITY-EXTRACT emits both; the
663
+ summary-style split decides what becomes its own page. Is that enough?
664
+ - **Bootstrap on a stronger model?** Is first creation good enough locally,
665
+ or is the one-time import the one place a stronger model earns its cost?
666
+
667
+ ## History
668
+
669
+ - 2026-06-19 — M0 landed: vault, read tools, CLI, launchd scheduling
670
+ (`docs/design/dreaming-nightly-pipeline.md`); synthesis a safe no-op stub.
671
+ - 2026-06-26 — M1 design (`docs/design/dreaming-nightly-pipeline.md`): pointer DB, create/update
672
+ split, wikify as its own node; bucketing decomposition
673
+ (`docs/design/dreaming-nightly-pipeline.md`); entity-keyed routing (`docs/design/dreaming-nightly-pipeline.md`).
674
+ - 2026-06-28 — Pipeline built end-to-end on real data; stage workers,
675
+ chronological order, self-healing, LINK stage, templating fix; batching
676
+ measured and shelved (`docs/design/dreaming-nightly-pipeline.md`); full import resumed
677
+ from 720/2096 into `~/.mlx-bun/wiki-full`.
678
+ - 2026-07-01 — Settled with Josh: no notability filter, no embeddings,
679
+ buckets are stories, one edit per (bucket, article), replacement-section
680
+ edit mechanics; `MLX_BUN_MEMORY_BATCH` default 1. This doc became
681
+ canonical.
682
+ - 2026-08-23 — Consolidated the source docs into this one; scripts moved to
683
+ `scripts/memory/`; every code claim re-verified; ingest-not-wired and
684
+ embedding-residue contradictions recorded.