mlx-bun 0.0.12 → 0.2.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 (226) hide show
  1. package/README.md +40 -24
  2. package/bin/mlx-bun.mjs +1 -1
  3. package/docs/{design → archive}/hlg-sampling.md +3 -3
  4. package/docs/{investigations → archive/investigations}/hlg-sampling-investigation.md +1 -1
  5. package/docs/archive/investigations/pareto-specialized-runtime-findings.md +92 -0
  6. package/docs/{planning → archive/planning}/chat-ui-vision.md +1 -1
  7. package/docs/archive/planning/release-notes-v0.0.13.md +77 -0
  8. package/docs/{planning → archive/planning}/release-notes-v0.0.9.md +1 -1
  9. package/docs/{planning → archive/planning}/website-readme-pass-plan.md +1 -1
  10. package/docs/design/colibri-glm52-port.md +8 -1
  11. package/docs/design/compat-cli-surface-design.md +1 -1
  12. package/docs/design/decode-speed-program.md +1 -1
  13. package/docs/design/docs-reorg-plan.md +1 -1
  14. package/docs/design/dspark-serving-program.md +1 -1
  15. package/docs/design/dspark-speculative-decoding.md +53 -3
  16. package/docs/design/grammar-spec-batching-integration.md +1 -1
  17. package/docs/design/mlx-lm-serving-execution-seam.md +81 -0
  18. package/docs/design/orpo-dynamic-lambda.md +1 -1
  19. package/docs/design/orpo-future-enhancements.md +1 -1
  20. package/docs/design/orpo-training.md +2 -2
  21. package/docs/design/parity-tier-dag.md +1 -1
  22. package/docs/design/repo-cleanup-plan.md +46 -4
  23. package/docs/design/segmented-backward-training.md +2 -2
  24. package/docs/design/ssd-kv-cold-tier.md +127 -0
  25. package/docs/design/turboquant-weights.md +467 -0
  26. package/docs/design/w4a16-compute-precision-spike.md +1 -1
  27. package/docs/design/web-chat-redesign.md +5 -5
  28. package/docs/planning/chunk-task-roadmap.md +1 -1
  29. package/docs/planning/curve-sampler-research-plan.md +4 -4
  30. package/docs/planning/memory-docs-and-dag-plan.md +1 -1
  31. package/docs/planning/release-notes-v0.2.0.md +49 -0
  32. package/docs/planning/video-series.md +421 -0
  33. package/docs/reference/cli.md +15 -4
  34. package/docs/reference/distribution.md +1 -0
  35. package/docs/reference/features-matrix.md +3 -1
  36. package/docs/reference/library-api.md +41 -1
  37. package/docs/reference/models.md +33 -0
  38. package/docs/reference/server-api.md +86 -19
  39. package/docs/reference/server-config.md +57 -33
  40. package/package.json +2 -2
  41. package/src/anthropic.ts +92 -3
  42. package/src/chat-template.ts +34 -1
  43. package/src/cli.ts +131 -36
  44. package/src/config.ts +10 -0
  45. package/src/eval/kl.ts +27 -20
  46. package/src/eval/runner.ts +23 -4
  47. package/src/eval/tasks/bfcl.ts +2 -1
  48. package/src/eval/tasks/gsm8k.ts +2 -1
  49. package/src/eval/tasks/hashhop.ts +2 -1
  50. package/src/eval/tasks/humaneval.ts +5 -4
  51. package/src/eval/tasks/ifeval.ts +2 -1
  52. package/src/eval/tasks/mmlu.ts +2 -1
  53. package/src/expert-io.ts +3 -1
  54. package/src/expert-offload.ts +3 -1
  55. package/src/expert-trace.ts +3 -1
  56. package/src/fit.ts +24 -43
  57. package/src/flags.ts +7 -9
  58. package/src/generate.ts +107 -92
  59. package/src/grammar.ts +2 -1
  60. package/src/harness-pi.ts +1 -1
  61. package/src/index.ts +20 -0
  62. package/src/jobs/job-entry.ts +3 -2
  63. package/src/kv-scheme.ts +199 -0
  64. package/src/kv-store.ts +370 -205
  65. package/src/media-fetch.ts +19 -3
  66. package/src/memory/model.ts +2 -1
  67. package/src/memory/vault.ts +2 -1
  68. package/src/memory-plan.ts +18 -0
  69. package/src/mlx/ffi.ts +8 -11
  70. package/src/mlx/ops.ts +31 -6
  71. package/src/model/batched-mask.ts +1 -1
  72. package/src/model/batched-quant.ts +1 -1
  73. package/src/model/batched-rotating-quant.ts +43 -97
  74. package/src/model/batched-rotating-state.ts +73 -0
  75. package/src/model/batched-rotating.ts +56 -105
  76. package/src/model/batched-row-storage.ts +127 -0
  77. package/src/model/compiled-decode.ts +3 -2
  78. package/src/model/factory.ts +44 -49
  79. package/src/model/gemma4-base.ts +163 -10
  80. package/src/model/generated/gemma4-12b.ts +50 -146
  81. package/src/model/generated/gemma4-26b.ts +32 -92
  82. package/src/model/generated/gemma4-e4b.ts +26 -74
  83. package/src/model/glm52-cache.ts +19 -9
  84. package/src/model/glm52-memory.ts +28 -1
  85. package/src/model/minicpm5.ts +2 -1
  86. package/src/model/profile.ts +433 -0
  87. package/src/model/qwen3-delta.ts +98 -2
  88. package/src/model/qwen3-mrope.ts +252 -0
  89. package/src/model/qwen3_5.ts +217 -25
  90. package/src/native/frame_extract.swift +140 -0
  91. package/src/native-pack.ts +13 -6
  92. package/src/pi-web.ts +6 -5
  93. package/src/prompt-cache.ts +2 -2
  94. package/src/quantize/atomic-output.ts +38 -0
  95. package/src/quantize/calibration.ts +2 -1
  96. package/src/quantize/config-writer.ts +6 -0
  97. package/src/quantize/index.ts +28 -1
  98. package/src/quantize/job.ts +11 -0
  99. package/src/quantize/quantizer.ts +265 -100
  100. package/src/quantize/rotate.ts +603 -0
  101. package/src/quantize/safetensors-writer.ts +79 -1
  102. package/src/quantize/sensitivity.ts +41 -59
  103. package/src/quantize/weight-transform.ts +292 -0
  104. package/src/responses.ts +102 -2
  105. package/src/runtime-config.ts +67 -0
  106. package/src/sampler.ts +272 -0
  107. package/src/serve/admin-routes.ts +184 -0
  108. package/src/serve/aux-routes.ts +132 -0
  109. package/src/serve/batch-scheduler.ts +200 -87
  110. package/src/serve/completion-executor.ts +346 -0
  111. package/src/serve/completion-sink.ts +157 -0
  112. package/src/serve/discovery-routes.ts +205 -0
  113. package/src/serve/generation-gateway.ts +116 -109
  114. package/src/serve/kv-budget.ts +14 -0
  115. package/src/serve/lab-routes.ts +338 -0
  116. package/src/serve/model-admin-routes.ts +153 -0
  117. package/src/serve/prompt-response-trace.ts +147 -0
  118. package/src/serve/request-plan.ts +158 -0
  119. package/src/serve/static-routes.ts +101 -0
  120. package/src/server.ts +768 -1367
  121. package/src/spec/dflash-source.ts +2 -1
  122. package/src/spec/dspark/module-dflash.ts +1 -1
  123. package/src/spec/glm52-mtp-source.ts +8 -0
  124. package/src/spec/qwen-mtp-source.ts +431 -0
  125. package/src/spec/serve-loop.ts +36 -68
  126. package/src/ssd-cache.ts +17 -14
  127. package/src/ssd-durability.ts +195 -0
  128. package/src/train/flash-cce.ts +12 -11
  129. package/src/train/forward.ts +32 -1
  130. package/src/train/loss.ts +2 -1
  131. package/src/train/segmented.ts +4 -3
  132. package/src/train/trainer.ts +3 -2
  133. package/src/vision/preprocess.ts +1 -1
  134. package/src/vision/prompt.ts +47 -1
  135. package/src/vision/qwen3vl-preprocess.ts +409 -0
  136. package/src/vision/qwen3vl-prompt.ts +196 -0
  137. package/src/vision/qwen3vl-tower.ts +513 -0
  138. package/src/vision/video-frames.ts +148 -0
  139. package/src/web/app.js +10 -3
  140. package/src/web/src/chat.ts +12 -5
  141. package/src/weights.ts +81 -26
  142. /package/docs/{investigations → archive/investigations}/chunk-finetune-experiment.md +0 -0
  143. /package/docs/{investigations → archive/investigations}/colibri-oracle-pin.md +0 -0
  144. /package/docs/{investigations → archive/investigations}/curve-bisector-routing.md +0 -0
  145. /package/docs/{investigations → archive/investigations}/curve-runs/bisector-route.json +0 -0
  146. /package/docs/{investigations → archive/investigations}/curve-runs/bisector-trace.json +0 -0
  147. /package/docs/{investigations → archive/investigations}/curve-runs/curve-terrain.json +0 -0
  148. /package/docs/{investigations → archive/investigations}/curve-runs/curve-terrain.svg +0 -0
  149. /package/docs/{investigations → archive/investigations}/curve-terrain.html +0 -0
  150. /package/docs/{investigations → archive/investigations}/decode-roofline-lookagain.md +0 -0
  151. /package/docs/{investigations → archive/investigations}/dspark-handoff.md +0 -0
  152. /package/docs/{investigations → archive/investigations}/expert-offload-single-user-moe.md +0 -0
  153. /package/docs/{investigations → archive/investigations}/hlg-ab.html +0 -0
  154. /package/docs/{investigations → archive/investigations}/hlg-explorer.html +0 -0
  155. /package/docs/{investigations → archive/investigations}/hlg-figs/cliff.svg +0 -0
  156. /package/docs/{investigations → archive/investigations}/hlg-figs/cross.svg +0 -0
  157. /package/docs/{investigations → archive/investigations}/hlg-figs/edges.svg +0 -0
  158. /package/docs/{investigations → archive/investigations}/hlg-figs/k20-grids.svg +0 -0
  159. /package/docs/{investigations → archive/investigations}/hlg-figs/k20-peaks.svg +0 -0
  160. /package/docs/{investigations → archive/investigations}/hlg-figs/lw-curve.svg +0 -0
  161. /package/docs/{investigations → archive/investigations}/hlg-figs/terrain.svg +0 -0
  162. /package/docs/{investigations → archive/investigations}/hlg-report.html +0 -0
  163. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-ab.json +0 -0
  164. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-blind-prompts.md +0 -0
  165. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-blind.json +0 -0
  166. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-cliff.json +0 -0
  167. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-cross.json +0 -0
  168. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-edge.json +0 -0
  169. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-eval1.json +0 -0
  170. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-frontier.json +0 -0
  171. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-grid.json +0 -0
  172. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-grid2.json +0 -0
  173. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-lw-coarse.json +0 -0
  174. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-map.json +0 -0
  175. /package/docs/{investigations → archive/investigations}/hlg-runs/run0-original.log +0 -0
  176. /package/docs/{investigations → archive/investigations}/hlg-runs/runA-recommended.log +0 -0
  177. /package/docs/{investigations → archive/investigations}/hlg-runs/runAA-cliff-cpm5.log +0 -0
  178. /package/docs/{investigations → archive/investigations}/hlg-runs/runAB-grid2-e4b.log +0 -0
  179. /package/docs/{investigations → archive/investigations}/hlg-runs/runAC-peak-e4b-partial.log +0 -0
  180. /package/docs/{investigations → archive/investigations}/hlg-runs/runAD-ab-samples.log +0 -0
  181. /package/docs/{investigations → archive/investigations}/hlg-runs/runAE-edge-e4b.log +0 -0
  182. /package/docs/{investigations → archive/investigations}/hlg-runs/runAF-logit-calibration.log +0 -0
  183. /package/docs/{investigations → archive/investigations}/hlg-runs/runAG-eval-config2.log +0 -0
  184. /package/docs/{investigations → archive/investigations}/hlg-runs/runB-gain-variants.log +0 -0
  185. /package/docs/{investigations → archive/investigations}/hlg-runs/runC-pivots.log +0 -0
  186. /package/docs/{investigations → archive/investigations}/hlg-runs/runD-oetf.log +0 -0
  187. /package/docs/{investigations → archive/investigations}/hlg-runs/runE-eotf.log +0 -0
  188. /package/docs/{investigations → archive/investigations}/hlg-runs/runF-shaper-working.log +0 -0
  189. /package/docs/{investigations → archive/investigations}/hlg-runs/runG-shaper-sweep.log +0 -0
  190. /package/docs/{investigations → archive/investigations}/hlg-runs/runH-wsweep.log +0 -0
  191. /package/docs/{investigations → archive/investigations}/hlg-runs/runI-asweep.log +0 -0
  192. /package/docs/{investigations → archive/investigations}/hlg-runs/runJ-knobmap.log +0 -0
  193. /package/docs/{investigations → archive/investigations}/hlg-runs/runK-frontier.log +0 -0
  194. /package/docs/{investigations → archive/investigations}/hlg-runs/runL-widemap.log +0 -0
  195. /package/docs/{investigations → archive/investigations}/hlg-runs/runM-frontier-emb.log +0 -0
  196. /package/docs/{investigations → archive/investigations}/hlg-runs/runN-verify.log +0 -0
  197. /package/docs/{investigations → archive/investigations}/hlg-runs/runO-frontier-seed5000.log +0 -0
  198. /package/docs/{investigations → archive/investigations}/hlg-runs/runP-grid-AxSm.log +0 -0
  199. /package/docs/{investigations → archive/investigations}/hlg-runs/runQ-grid-tgW.log +0 -0
  200. /package/docs/{investigations → archive/investigations}/hlg-runs/runR-blind-transcript.log +0 -0
  201. /package/docs/{investigations → archive/investigations}/hlg-runs/runS-cross-coherence.log +0 -0
  202. /package/docs/{investigations → archive/investigations}/hlg-runs/runT-cross-cpm5.log +0 -0
  203. /package/docs/{investigations → archive/investigations}/hlg-runs/runU-cross-12b.log +0 -0
  204. /package/docs/{investigations → archive/investigations}/hlg-runs/runV-lw-e4b.log +0 -0
  205. /package/docs/{investigations → archive/investigations}/hlg-runs/runW-lw-12b.log +0 -0
  206. /package/docs/{investigations → archive/investigations}/hlg-runs/runX-lw-cpm5.log +0 -0
  207. /package/docs/{investigations → archive/investigations}/hlg-runs/runY-cliff-e4b.log +0 -0
  208. /package/docs/{investigations → archive/investigations}/hlg-runs/runZ-cliff-12b.log +0 -0
  209. /package/docs/{investigations → archive/investigations}/indexshare-performance-spike.md +0 -0
  210. /package/docs/{investigations → archive/investigations}/kernel-perf-review-2026-07.md +0 -0
  211. /package/docs/{investigations → archive/investigations}/lab-build-journal.md +0 -0
  212. /package/docs/{investigations → archive/investigations}/openwiki-evaluation.md +0 -0
  213. /package/docs/{investigations → archive/investigations}/orpo-base-uf-experiment-and-directions.md +0 -0
  214. /package/docs/{investigations → archive/investigations}/orpo-flash-cce-pin-leak.md +0 -0
  215. /package/docs/{investigations → archive/investigations}/orpo-uf-testing-handoff.md +0 -0
  216. /package/docs/{investigations → archive/investigations}/pi-builtin-investigation.md +0 -0
  217. /package/docs/{investigations → archive/investigations}/segmented-backward-handoff.md +0 -0
  218. /package/docs/{investigations → archive/investigations}/starter-model-port-handoff.md +0 -0
  219. /package/docs/{investigations → archive/investigations}/steel-flash-cce-handoff.md +0 -0
  220. /package/docs/{investigations → archive/investigations}/trainer-validation-experiment.md +0 -0
  221. /package/docs/{planning → archive/planning}/chat-ui-vision-memory-first.md +0 -0
  222. /package/docs/{planning → archive/planning}/journal.md +0 -0
  223. /package/docs/{planning → archive/planning}/release-notes-v0.0.10.md +0 -0
  224. /package/docs/{planning → archive/planning}/release-notes-v0.0.11.md +0 -0
  225. /package/docs/{planning → archive/planning}/release-notes-v0.0.12.md +0 -0
  226. /package/docs/{planning → archive/planning}/web-ui-pass-plan.md +0 -0
@@ -0,0 +1,81 @@
1
+ # mlx-lm serving execution seam
2
+
3
+ ## Question
4
+
5
+ What should mlx-bun borrow from the pinned mlx-lm oracle when deepening Completion execution around this flow?
6
+
7
+ ```text
8
+ prepared prompt -> semantic events -> terminal summary
9
+ ```
10
+
11
+ This note reads the local mlx-lm 0.31.3 source installed in the reference venv. The comparison uses the serving term defined in `CONTEXT.md`: a **Completion execution** includes output, resource lifetime, usage, and scheduling placement.
12
+
13
+ mlx-lm is the correctness oracle only for behavior and numeric schemes it implements. It is not mlx-bun's architecture oracle or capability ceiling. mlx-bun also implements explicitly selected features such as mixed-precision KV, TurboQuant, and native draft methods. The execution path must preserve those resolved settings exactly.
14
+
15
+ ## Implementation status
16
+
17
+ The first consolidation slice landed on 2026-08-21. `prepareCompletion()` performs admission before a streaming response starts and returns an opaque, single-use `PreparedCompletion`. `CompletionExecutor.execute()` then owns placement reporting, `CompletionSink`, logprobs, semantic event delivery, finish reason, usage, and pre-generation cleanup. [`completion-executor.ts:75`](/Users/joshrossi/.codex/worktrees/e408/mlx-bun/src/serve/completion-executor.ts:75) [`completion-executor.ts:219`](/Users/joshrossi/.codex/worktrees/e408/mlx-bun/src/serve/completion-executor.ts:219)
18
+
19
+ Chat and raw-text adapters still declare their semantic components and format their own wire responses. Their streaming and non-streaming branches now call the same executor. `GenerationGateway` still owns placement and model execution. The change does not add or remove flags, alter defaults, or substitute one inference method for another.
20
+
21
+ S3 completed the scheduling declaration at the same seam. A
22
+ `GenerationPlacement` now names `serial` or `continuous`; it no longer calls a
23
+ lone scheduler request "batched." The continuous scheduler chooses its B=1
24
+ fast path or B=N step from active rows. The strict serial executor remains for
25
+ `--batch 1` and for declared compositions the continuous scheduler does not yet
26
+ implement. Existing parity evidence covers both the default scheduler's B=1
27
+ path and strict serial. The recorded scheduler/serial decode ratios are
28
+ 0.992–0.996, and a fresh main-vs-branch serial gate after the sampler
29
+ consolidation measured a 1.000 median-best ratio (about 265.0 tok/s on both
30
+ sides, MiniCPM5, two interleaved 256-token repeats).
31
+
32
+ Final acceptance after the GPQA pause exercised the seam through the complete
33
+ two-shard suite and a real two-turn browser conversation. The suite passed
34
+ 2,064 tests with zero failures. The browser run verified streaming, tool
35
+ events, context carryover, metrics, and turn recovery. A mainline-only queued
36
+ DOM render race found by that run was fixed at the UI lifecycle boundary; no
37
+ decode-loop work was added. The architecture changes likewise keep accounting
38
+ and capability decisions outside numerical kernels. The fresh serial A/B plus
39
+ the established scheduler B=1 parity and latency evidence are the performance
40
+ gate.
41
+
42
+ ## What the oracle actually does
43
+
44
+ The HTTP adapter creates a small `CompletionRequest` that still contains protocol-shaped chat messages or a raw text prompt. It separately builds `GenerationArguments`. Prompt preparation has not happened yet. [`server.py:182`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:182) [`server.py:198`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:198) [`server.py:1368`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:1368)
45
+
46
+ `ResponseGenerator.generate()` submits that pair to one worker thread through a queue. The worker applies the chat template, tokenizes, finds cache segments, chooses the initial reasoning state, constructs stop/tool/reasoning transitions, selects a lane, owns cache work, and emits per-token `Response` records. [`server.py:516`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:516) [`server.py:626`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:626) [`server.py:688`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:688) [`server.py:1026`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:1026)
47
+
48
+ That organization can be summarized as:
49
+
50
+ ```text
51
+ HTTP request
52
+ -> CompletionRequest + GenerationArguments
53
+ -> ResponseGenerator queue
54
+ -> tokenize + state machine + lane + cache
55
+ -> token Response { text, token, state, match, logprob, finish_reason }
56
+ -> HTTP handler accumulates semantics and formats the protocol response
57
+ ```
58
+
59
+ The model loader is separate. `utils.load()` returns the model and a tokenizer wrapper, while tokenizer loading chooses the streaming detokenizer and tool parser. This keeps model construction out of per-request execution. [`utils.py:453`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/utils.py:453) [`tokenizer_utils.py:578`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/tokenizer_utils.py:578)
60
+
61
+ ## Comparison by concern
62
+
63
+ | Concern | Oracle organization | Consequence for the mlx-bun seam |
64
+ | --- | --- | --- |
65
+ | Prepared prompt | The worker owns chat templating, tokenization, initial reasoning state, and cache-oriented prompt segmentation. The HTTP adapter does not know those details. [`server.py:530`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:530) [`server.py:565`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:565) | A prepared-prompt value is a sound seam, but it should be richer than token ids. It must carry the generation inputs and owned media or grammar state needed by either lane. Protocol adapters should not assemble `RequestShape` or transfer resources piecemeal. |
66
+ | Semantic events | The state machine classifies tokens as `normal`, `reasoning`, or `tool`. `_process_control_tokens` withholds matched control sequences. The HTTP handler then accumulates reasoning, tool text, visible text, and parses completed tool calls. [`server.py:236`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:236) [`server.py:654`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:654) [`server.py:1442`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:1442) | Do not copy this split. mlx-bun's `CompletionSink` already emits content, reasoning, and tool-call events through one interface. [`completion-sink.ts:7`](/Users/joshrossi/.codex/worktrees/e408/mlx-bun/src/serve/completion-sink.ts:7) [`completion-sink.ts:90`](/Users/joshrossi/.codex/worktrees/e408/mlx-bun/src/serve/completion-sink.ts:90) The deepened module should own that semantic work so all protocol adapters consume the same events. |
67
+ | Terminal summary | `generate.stream_generate()` puts timing, token counts, memory, and finish reason on every low-level response, but the server discards most of it. The HTTP handler counts emitted tokens, reads prompt and cache counts from `GenerationContext`, derives tool-call finish, and builds usage. [`generate.py:269`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/generate.py:269) [`generate.py:728`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/generate.py:728) [`server.py:1495`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:1495) [`server.py:1517`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:1517) | The oracle has no single terminal summary at the server seam. mlx-bun should add one. It should be the sole source of finish reason, prompt/generated/cached counts, final lane, speculation facts, and collected result. |
68
+ | Streaming and non-streaming | Both modes consume the same response iterator and semantic accumulation loop. Streaming flushes deltas and optionally emits a final usage packet; non-streaming sends one accumulated response. [`server.py:1454`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:1454) [`server.py:1507`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:1507) [`server.py:1529`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:1529) | Keep one Completion execution interface. Streaming and non-streaming are protocol adapters over the same semantic events and terminal summary, not separate execution paths. |
69
+ | Scheduling placement | Model loading marks a model batchable only when no draft model exists and every cache supports merge. Per-request placement also excludes a fixed seed. The worker joins the active batch, creates a batch, runs serially, or drains an incompatible batch. [`server.py:370`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:370) [`server.py:685`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:685) [`server.py:731`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:731) [`server.py:801`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:801) [`server.py:831`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:831) | `RequestShape` describes settings already resolved from flags, request fields, model metadata, and defaults. `place()` asks only whether the continuous scheduler implements that exact composition; it never replaces MTP, a KV scheme, or another requested method. The frozen declaration names the `serial` or `continuous` mechanism, and both reporting and `run()` consume it. Active-row count selects B=1 or B=N inside the continuous mechanism. |
70
+ | Cancellation and flow | Cancellation is cooperative. The handler's `finally` sets a flag; serial execution checks it after tokens, while batched execution checks it during prompt and generation work and removes the row. [`server.py:1008`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:1008) [`server.py:861`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:861) [`server.py:911`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:911) [`server.py:1551`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:1551) The response queue is unbounded, and socket writes block only the HTTP thread, so there is no end-to-end demand signal from the client to generation. [`server.py:444`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:444) [`server.py:1032`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:1032) [`server.py:1489`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:1489) | Do not regress to the oracle here. mlx-bun's token callback accepts a promise or `false`, and `GenerationGateway.run()` accepts an `AbortSignal`. [`generation-gateway.ts:94`](/Users/joshrossi/.codex/worktrees/e408/mlx-bun/src/serve/generation-gateway.ts:94) [`generation-gateway.ts:354`](/Users/joshrossi/.codex/worktrees/e408/mlx-bun/src/serve/generation-gateway.ts:354) Cancellation and async flow control are part of the interface, not HTTP details. |
71
+ | Cache and resource lifetime | The worker fetches or creates the serial cache, generates, then inserts it. Batched execution fetches before admission, snapshots segment caches, inserts the final cache, and removes cancelled rows. [`server.py:963`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:963) [`server.py:1018`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:1018) [`server.py:752`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:752) [`server.py:864`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:864) [`server.py:901`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:901) `LRUPromptCache` hides prefix matching, copying, trimming, and count/byte eviction behind a small interface. [`cache.py:1623`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/models/cache.py:1623) [`cache.py:1674`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/models/cache.py:1674) [`cache.py:1696`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/models/cache.py:1696) | `prepareCompletion()` now disposes owned media and grammar state on admission rejection. `execute()` disposes the same plan on placement or pre-generation failure, then transfers ownership exactly once before `GenerationGateway.run()`. Cache and in-generation disposal remain in the gateway and generation path. [`completion-executor.ts:75`](/Users/joshrossi/.codex/worktrees/e408/mlx-bun/src/serve/completion-executor.ts:75) [`completion-executor.ts:219`](/Users/joshrossi/.codex/worktrees/e408/mlx-bun/src/serve/completion-executor.ts:219) |
72
+ | Errors | Errors before the response iterator exists become a JSON error, although the oracle uses status 404 for that whole class. Errors delivered later through the worker queue are raised by the iterator. The handler guarantees `ctx.stop()` but has no matching mid-stream error packet in this loop. [`server.py:1417`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:1417) [`server.py:1424`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:1424) [`server.py:1035`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:1035) [`server.py:1454`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:1454) | Preserve a typed distinction between rejection before execution and failure after events begin. Both must run the same ownership cleanup. Protocol adapters decide how to encode the failure after headers have been sent. |
73
+
74
+ ## Strongest conclusions
75
+
76
+ 1. **Keep resolved inference settings authoritative.** Serial and continuous execution are scheduling mechanisms for the already-selected composition. Consolidating placement may shrink the HTTP adapter, but it must not turn explicit features or opinionated defaults into inferred choices.
77
+ 2. **Do not borrow the oracle's semantic split.** Its worker emits state-tagged token records, then the HTTP handler performs semantic accumulation and tool parsing. mlx-bun already has a better starting point in `CompletionSink`. Move that module behind the Completion execution seam so protocol adapters see semantic events only.
78
+ 3. **Keep the terminal summary as the only completion result.** It now settles finish reason, usage, cached-token count, final lane, speculation, logprobs, and collected output once. The four HTTP branches only format that summary. [`completion-executor.ts:103`](/Users/joshrossi/.codex/worktrees/e408/mlx-bun/src/serve/completion-executor.ts:103) [`server.ts:3104`](/Users/joshrossi/.codex/worktrees/e408/mlx-bun/src/server.ts:3104)
79
+ 4. **Treat cancellation, async flow control, and ownership as interface requirements.** The oracle's queue makes cancellation and socket pressure indirect. mlx-bun has stronger mechanisms already. The deepening should hide their implementation without weakening their behavior.
80
+
81
+ The deletion test is positive. If this Completion execution module disappeared, scheduling policy, cache participation, semantic parsing, finish rules, usage, and cleanup would spread back across four protocol branches. Concentrating those rules gives callers leverage and gives maintainers locality. Feature selection and fidelity policy remain outside this seam as resolved inputs.
@@ -158,7 +158,7 @@ than the absolute max-bearable (the edge is fragile).
158
158
  **Synergy with the diffusion frontier:** all of this is noise-robustness machinery for
159
159
  controlling off a noisy process variable — so it transfers *especially* well to a
160
160
  diffusion LM, where the likelihood is a stochastic ELBO (even noisier). See
161
- [orpo-base-uf-experiment-and-directions.md](../investigations/orpo-base-uf-experiment-and-directions.md) §6.7.
161
+ [orpo-base-uf-experiment-and-directions.md](../archive/investigations/orpo-base-uf-experiment-and-directions.md) §6.7.
162
162
 
163
163
  ## Wiring
164
164
 
@@ -11,7 +11,7 @@ comfortable.
11
11
  > trainer)** shipped as `orpo_prefix_shared`, composed with the flash head AND the
12
12
  > segmented backward for MiniCPM5 **and** e4b. See [training.md](../reference/training.md),
13
13
  > [orpo-quickstart.md](../reference/orpo-quickstart.md), and the live record
14
- > [steel-flash-cce-handoff.md](../investigations/steel-flash-cce-handoff.md). The
14
+ > [steel-flash-cce-handoff.md](../archive/investigations/steel-flash-cce-handoff.md). The
15
15
  > remaining sections (§2, §4–§9, the objective variants) are still genuinely future.
16
16
 
17
17
  ## Deferred levers — queued, do NOT start mid-run (2026-06-19)
@@ -19,7 +19,7 @@ runs half the forwards per step.
19
19
  > The ✅/🔬 entries + optimization log below are the **design/build history**; for the
20
20
  > current usage, flags, and numbers see [training.md](../reference/training.md) +
21
21
  > [orpo-quickstart.md](../reference/orpo-quickstart.md), and the live engineering record
22
- > [steel-flash-cce-handoff.md](../investigations/steel-flash-cce-handoff.md).
22
+ > [steel-flash-cce-handoff.md](../archive/investigations/steel-flash-cce-handoff.md).
23
23
 
24
24
  > Oracle note: neither `mlx_lm` nor `optiq` ships ORPO, so — unlike DPO
25
25
  > ([`loss.ts`](../../src/train/loss.ts) ported `optiq/lora/dpo.py`) — ORPO is
@@ -38,7 +38,7 @@ runs half the forwards per step.
38
38
  > hiddens before judging** (it's plausibly a free speedup to enable). Standing Lab
39
39
  > gates (filter-on-real-data, teacher-forced grad fidelity, end-to-end quality)
40
40
  > + the host-buffer pin-leak post-mortem:
41
- > [orpo-flash-cce-pin-leak.md](../investigations/orpo-flash-cce-pin-leak.md).
41
+ > [orpo-flash-cce-pin-leak.md](../archive/investigations/orpo-flash-cce-pin-leak.md).
42
42
 
43
43
  ## Implementation status
44
44
 
@@ -26,7 +26,7 @@ exists AND it matches bit-exactly; otherwise it bubbles up. Note the subtlety th
26
26
  map already shows: an *optimization* that still matches the oracle stays low —
27
27
  `compiled-decode` is ours but is L1 because it replays the same ops bit-for-bit.
28
28
  Only nodes with **no** oracle (flash-CCE) are *forced* into the Lab. (Background +
29
- post-mortem: [orpo-flash-cce-pin-leak.md](../investigations/orpo-flash-cce-pin-leak.md).)
29
+ post-mortem: [orpo-flash-cce-pin-leak.md](../archive/investigations/orpo-flash-cce-pin-leak.md).)
30
30
 
31
31
  ## Two axes: parity gates performance
32
32
 
@@ -45,7 +45,7 @@ check). Untracked ≠ deleted ≠ unrunnable.
45
45
  + the two docs that reference `repro/` (grep first: `grep -rn "repro/"
46
46
  docs/ *.md`).
47
47
  - Also updated every path reference (CLAUDE.md, PLAN.md, PLAN-archive.md,
48
- docs/investigations/lab-build-journal.md, docs/design/docs-reorg-plan.md,
48
+ docs/archive/investigations/lab-build-journal.md, docs/design/docs-reorg-plan.md,
49
49
  and the `repro/`-pointing comments in src/mlx/{ffi,array,autograd,ops}.ts
50
50
  + tests/{ffi-jit,train-autograd}.test.ts + the repro's own ISSUE.md).
51
51
  - `[x]` **A2. root strays** (all untracked; no git surgery): delete
@@ -168,12 +168,12 @@ suspenders until both machines are re-cloned.
168
168
  threads). The closed `oMLX` phase (2026-07-02) and Phase 19 are
169
169
  *newer* than 17, so they stay in PLAN.md as recent history per the
170
170
  criterion. Revisit D1 when a <17 phase is confirmed closed.
171
- - `[x]` **D2.** `docs/investigations/curve-runs/` + two untracked HTML
171
+ - `[x]` **D2.** `docs/archive/investigations/curve-runs/` + two untracked HTML
172
172
  strays in docs/investigations: gitignore or move under `reports/`.
173
- - Already satisfied: `docs/investigations/curve-runs/` is gitignored
173
+ - Already satisfied: `docs/archive/investigations/curve-runs/` is gitignored
174
174
  (`.gitignore`), and the four HTML strays (`curve-terrain.html`,
175
175
  `hlg-ab.html`, `hlg-report.html`, `hlg-explorer.html`) are gitignored
176
- via `docs/investigations/*.html`. No action needed.
176
+ via `docs/archive/investigations/*.html`. No action needed.
177
177
  - `[x]` **D3.** The CLAUDE.md doc-map lists are hand-maintained; add a
178
178
  `scripts/check-hygiene.ts` assertion that every `docs/**/*.md` appears in
179
179
  the map (same guardrail spirit as B1).
@@ -187,3 +187,45 @@ A and D anytime (safe, agent-executable solo) — DONE 2026-07-02. B gate
187
187
  DONE 2026-07-02 (B2 adapter-untrack deferred to Josh — see B2 finding).
188
188
  C executed 2026-07-02 on Josh's explicit go — only remaining tail is the
189
189
  M4 Pro reset + deleting the backup tarball once both boxes are green.
190
+
191
+ ## Phase D — work-artifact hygiene + rules (2026-08-18) — DONE
192
+
193
+ Trigger: Josh — "this is a software project; we should not be adding all
194
+ these random work things into it." Compared against ml-explore/mlx-lm's
195
+ root (9 files, 4 dirs, zero work artifacts).
196
+
197
+ - **D1 root sweep**: six tracked `benchmarks-serve-2026-07-0*.md` dumps
198
+ git-rm'd (they leaked past a gitignore pattern that only covered
199
+ `benchmarks-h2h-*`); seven untracked root bench artifacts moved to
200
+ `reports/bench-archive/`; `tmp/` deleted. `.gitignore` now blocks
201
+ `/benchmarks-*`, `/bench-*`, `/tmp/`, `/*.log` at root.
202
+ - **D2 guardrail**: `scripts/check-hygiene.ts` gained a third gate —
203
+ tracked ROOT files must be on an explicit allowlist (16 meta files).
204
+ - **D3 CONTRIBUTING.md**: the rules (what-goes-where table; no dated
205
+ artifacts; models → `~/models`; docs lifecycle; PLAN/STATUS discipline;
206
+ external oracle venv is not a repo).
207
+ - **D4 docs archive**: all 20 `docs/investigations/` →
208
+ `docs/archive/investigations/` (investigations document finished work);
209
+ 5 release-notes + journal + 2 chat-ui visions + 2 pass-plans →
210
+ `docs/archive/planning/`; `hlg-sampling.md` (self-declared superseded) →
211
+ `docs/archive/`. All inbound links rewritten; docs-map gate green.
212
+ - **D5 journal trim (rule 4)**: STATUS.md 1648 → 106 lines (Colibri
213
+ gate-history + all "where we were" + the stale July ranked backlog →
214
+ PLAN-archive under "STATUS archive 2026-08-18"); PLAN.md 3815 → 2679
215
+ (8 closed `[x]` phase blocks → PLAN-archive with one-line pointers).
216
+ - Model artifacts moved out of the tree entirely (`~/models/`) earlier
217
+ the same day.
218
+
219
+ Remaining (curated, not mechanical — needs judgment per file):
220
+ - `docs/design/` still holds 46 docs; many describe LANDED work and
221
+ should archive under rule 3 (candidates: optimization_plan,
222
+ faithful-base superseded pair, diffusion/audio/omlx/dspark docs whose
223
+ phases closed). Do this per-doc with link fixes, not as a bulk move.
224
+ - `docs/planning/` leftovers (chunk-task-roadmap, curve-sampler-research-
225
+ plan, memory-docs-and-dag-plan, pi-integration-review) — keep-or-archive
226
+ per Josh.
227
+ - `scripts/experiments/` = 199 files; prune ones whose findings are fully
228
+ recorded in archived docs (keep regen/oracle scripts).
229
+ - `runs/` is 32 GB machine-local (Colibri evidence + quant lab arms) —
230
+ delete lab arms once the TQ/GPTQ program closes; Colibri evidence dirs
231
+ are cited from the archive and stay.
@@ -35,7 +35,7 @@ All committed-worthy, validated this session:
35
35
  Baseline 11.89/100 → fine-tuned **91.70/100** on lucien's 25-case chunk holdout
36
36
  (valid-JSON rate 12% → 100%). Full mlx-bun loop: train → save adapter (hot-swap
37
37
  format) → `AdapterManager.mount` → generate. Writeup:
38
- `docs/investigations/chunk-finetune-experiment.md`. Scripts: `scripts/chunk-eval.ts`,
38
+ `docs/archive/investigations/chunk-finetune-experiment.md`. Scripts: `scripts/chunk-eval.ts`,
39
39
  `scripts/chunk-finetune.ts`, `scripts/chunk-filter.ts`. Adapter:
40
40
  `~/.cache/mlx-bun-finetunes/minicpm5-chunk-final`.
41
41
 
@@ -69,7 +69,7 @@ active-after 8.66 + cache-after 6.61 = 15.27 > peak 14.90, impossible if peak we
69
69
  total). MLX holds freed buffers in a **cache** (not returned to OS) so RSS overstates
70
70
  live memory; use `activeMemory()` / `cacheMemory()` / `clearCache()` to separate
71
71
  them (`src/mlx/ffi.ts`). Caveat from the MoE expert-offload work
72
- (`docs/investigations/expert-offload-single-user-moe.md`): `clearCache()` does NOT
72
+ (`docs/archive/investigations/expert-offload-single-user-moe.md`): `clearCache()` does NOT
73
73
  reliably return device-buffer RAM to the OS — only the mmap clean-page path does.
74
74
 
75
75
  Facts established:
@@ -286,3 +286,130 @@ SIGINT/SIGTERM during (or within the debounce after) traffic loses queued
286
286
  write-behind entries; restart survival degrades to whatever already
287
287
  flushed. This is a best-effort cache tier; the cost of a lost entry is one
288
288
  re-prefill. `SpillQueue.drain()` exists if a shutdown hook is ever wanted.
289
+
290
+ This acceptance was superseded by the 2026-08-22 durability fix below.
291
+
292
+ ## Bug brief 2026-08-22: restart can outrun SSD write-behind
293
+
294
+ ### User-visible failure
295
+
296
+ A completed long-context request can report a full RAM-cache hit on repeat,
297
+ then lose that prefix when the server restarts a few seconds later. The next
298
+ request pays a full prefill even though `--ssd-cache` was enabled.
299
+
300
+ The 2026-08-22 M1 Max serve matrix reproduced this with the standard restart
301
+ leg, which waits 2.5 seconds after the long request before sending SIGTERM:
302
+
303
+ | model and mode | cached tokens after restart | expected |
304
+ |---|---:|---:|
305
+ | MiniCPM5, unified bf16 | 15,817 | about 15.8k |
306
+ | e4b, unified bf16 | 4 | about 15.9k |
307
+ | e4b, serial bf16 | 15,940 | about 15.9k |
308
+ | 12B, unified bf16 | 4 | about 15.9k |
309
+ | 12B, serial bf16 | 15,866 | about 15.9k |
310
+ | Qwen3.8, unified bf16 | 0 | about 15.1k |
311
+ | Qwen3.8, serial bf16 | 0 | about 15.1k |
312
+ | Qwen3.8, unified mixed KV | 15,112 | about 15.1k |
313
+
314
+ The Bun 1.4 rerun reproduced the lane split before this brief was written:
315
+ e4b unified restored 2 of 15,942 tokens, while e4b serial restored 15,939.
316
+ The completed run also restored only 4 tokens for unified 12B versus 15,794
317
+ for serial 12B, and 5 for mixed-KV 12B. Qwen bf16 restored zero in both
318
+ lanes, while mixed-KV Qwen restored all 15,114 tokens. This is not a Bun
319
+ 1.3-only result.
320
+
321
+ ### Expected contract
322
+
323
+ After a request completes and the server has reported the cache durable, a
324
+ normal SIGTERM and restart must restore the longest compatible prefix. For
325
+ the benchmark's exact-repeat request, `cached_tokens` should be at least
326
+ `prompt_tokens - 1`. The server must never imply durability while a matching
327
+ snapshot is still waiting on its debounce timer or the spill queue.
328
+
329
+ ### Evidence and current diagnosis
330
+
331
+ The read and reconstruction path works when the file reaches disk. MiniCPM,
332
+ serial e4b and 12B, and mixed-KV Qwen all restore a full prefix through the
333
+ same `PromptCache.take()` and `SsdCacheStore.restore()` path. That makes a
334
+ model-format or prefix-matching defect unlikely as the primary cause.
335
+
336
+ The likely failure is the durability boundary:
337
+
338
+ 1. `PromptCache.onPut` schedules a snapshot with a one-second debounce.
339
+ 2. A busy gateway re-arms that timer for five seconds.
340
+ 3. The snapshot then joins a serial `SpillQueue`; large bf16 entries can still
341
+ be queued or in flight when the benchmark's 2.5-second wait expires.
342
+ 4. SIGTERM stops the process without forcing pending timers to fire or awaiting
343
+ `SpillQueue.drain()`. The queue's own comment calls drain a future shutdown
344
+ hook.
345
+
346
+ Entry size explains the broad pattern. Small MiniCPM and smaller mixed-KV Qwen
347
+ files finish in time; large Qwen bf16 files do not. The remaining unified versus
348
+ serial split needs telemetry before assigning a second root cause. The unified
349
+ lane may enqueue more snapshots, take longer to extract row caches, or hit the
350
+ queue cap. The current report does not capture that state before termination.
351
+
352
+ ### Fix plan
353
+
354
+ 1. Expose `pending_snapshots`, `pending_spills`, `pending_spill_bytes`,
355
+ `dropped_spills`, and the longest durable prefix in `/stats.ssd_cache`.
356
+ `pending_snapshots` must include debounce and busy-rearm timers, not only
357
+ entries already placed in `SpillQueue`.
358
+ 2. Add one async durability operation that cancels debounce timers, snapshots
359
+ matching RAM entries under the gateway lock, and awaits
360
+ `SpillQueue.drain()`. Return success only after the atomic rename completes.
361
+ 3. Route graceful SIGINT/SIGTERM through that operation with a bounded timeout.
362
+ Stop new admission first. A timeout may drop the cache, but must log the
363
+ number of pending and dropped snapshots.
364
+ 4. Change the benchmark restart leg to request or poll durability instead of
365
+ sleeping for 2.5 seconds. A fixed sleep measures SSD timing luck, not the
366
+ restart contract.
367
+ 5. Capture pre-kill stats and the recovered entry count in every restart row so
368
+ a write miss cannot masquerade as a restore miss again.
369
+
370
+ ### Implemented 2026-08-22
371
+
372
+ - `SsdDurabilityCoordinator` owns debounce, busy retry, queue completion, and
373
+ explicit flush as one state machine. A snapshot stays dirty until the atomic
374
+ store succeeds. Queue drops and write failures remain retryable.
375
+ - Dirty keys include the exact token sequence, so two conversations with the
376
+ same namespace and token count cannot cancel each other.
377
+ - `POST /admin/cache/flush` returns `200` only at the durability boundary and
378
+ exposes pending, dropped, failed, entry-count, and longest-prefix evidence.
379
+ - `mlx-bun serve` handles `SIGINT` and `SIGTERM` by stopping admission,
380
+ draining active requests, and awaiting the same flush, bounded by
381
+ `MLX_BUN_SHUTDOWN_TIMEOUT_MS`.
382
+ - The standard restart benchmark calls the endpoint instead of sleeping and
383
+ records its evidence in the Markdown report and eval database notes.
384
+ - Model-free regression coverage holds an atomic store open and proves flush
385
+ waits, forces the busy-rearm path, retries a failed write, preserves
386
+ equal-length conversations, and reports a missing RAM boundary honestly.
387
+
388
+ Focused real-server smoke on unified e4b passed the repaired path: a
389
+ 4,026-token prompt flushed in 408 ms with `durable=true`, zero pending,
390
+ dropped, or failed work, then restored 4,025 cached tokens after SIGTERM and
391
+ respawn. The machine failed the clean-performance preflight, so this is cache
392
+ correctness evidence only, not a performance result. The full clean 16k
393
+ e4b/12B/Qwen matrix remains the acceptance run.
394
+
395
+ ### Acceptance tests
396
+
397
+ - A deterministic coordinator test holds `storeAsync`, requests a flush,
398
+ releases the store, and proves the durability boundary waits for the atomic
399
+ file. The focused real-server smoke exercises the SIGTERM path through that
400
+ same operation before restart.
401
+ - A busy-gateway test forces the five-second re-arm path, then requests a flush
402
+ and proves the timer becomes an immediate queued snapshot without blocking an
403
+ active generation.
404
+ - Batch 8 and `--batch 1` both restore at least `prompt_tokens - 1` for e4b,
405
+ 12B, and Qwen3.8 at roughly 16k context.
406
+ - The mixed-KV and SSM cache kinds retain their existing continuation parity.
407
+ - Steady-state decode remains unchanged because normal writes still pause while
408
+ the engine is busy. Queued clone memory stays within the spill cap.
409
+
410
+ Focused reproduction:
411
+
412
+ ```sh
413
+ ./benchmark.sh --models e4b,12B,qwen27b \
414
+ --arms mlx-bun,mlx-bun-serial,mlx-bun-mixed
415
+ ```