mlx-bun 0.0.8 → 0.0.10

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 (144) hide show
  1. package/README.md +112 -20
  2. package/docs/design/batching-perf-path.md +232 -0
  3. package/docs/design/batching-v2-plan.md +515 -0
  4. package/docs/design/bucketing-stage.md +196 -0
  5. package/docs/design/compat-cli-surface-design.md +11 -11
  6. package/docs/design/docs-reorg-plan.md +1 -1
  7. package/docs/design/dreaming-nightly-pipeline.md +291 -0
  8. package/docs/design/dspark-speculative-decoding.md +106 -0
  9. package/docs/design/generic-model-support.md +381 -0
  10. package/docs/design/memory-inference-path.md +422 -0
  11. package/docs/design/memory-synthesis.md +287 -0
  12. package/docs/design/minicpm5-decode-megakernel.md +484 -0
  13. package/docs/design/mlx-lm-tool-parity-plan.md +535 -0
  14. package/docs/design/omlx-adoption-map.md +90 -0
  15. package/docs/design/orpo-dynamic-lambda.md +41 -0
  16. package/docs/design/orpo-training.md +35 -5
  17. package/docs/design/parallel-slots.md +12 -1
  18. package/docs/design/parity-tier-dag.md +13 -5
  19. package/docs/design/segmented-backward-training.md +15 -0
  20. package/docs/design/spec-decode-larger-targets.md +229 -0
  21. package/docs/design/ssd-kv-cold-tier.md +158 -0
  22. package/docs/design/tauri-desktop-app.md +2 -2
  23. package/docs/design/the-dreaming-handoff.md +141 -0
  24. package/docs/design/the-dreaming-master-plan.md +446 -0
  25. package/docs/design/write-pipeline-entity.md +468 -0
  26. package/docs/investigations/curve-bisector-routing.md +1 -1
  27. package/docs/investigations/decode-roofline-lookagain.md +348 -0
  28. package/docs/investigations/dspark-handoff.md +58 -0
  29. package/docs/investigations/kernel-perf-review-2026-07.md +171 -0
  30. package/docs/planning/PRODUCT_ROADMAP.md +2 -2
  31. package/docs/planning/curve-sampler-research-plan.md +331 -0
  32. package/docs/planning/memory-docs-and-dag-plan.md +185 -0
  33. package/docs/planning/pi-integration-review.md +178 -0
  34. package/docs/planning/release-notes-v0.0.10.md +57 -0
  35. package/docs/planning/release-notes-v0.0.9.md +122 -0
  36. package/docs/planning/web-ui-pass-plan.md +213 -0
  37. package/docs/planning/website-readme-pass-plan.md +224 -0
  38. package/docs/reference/cli.md +255 -0
  39. package/docs/reference/embedding.md +7 -2
  40. package/docs/reference/library-api.md +22 -3
  41. package/docs/reference/memory.md +5 -5
  42. package/docs/reference/models.md +137 -0
  43. package/docs/reference/orpo-quickstart.md +6 -0
  44. package/docs/reference/server-api.md +187 -9
  45. package/docs/reference/server-config.md +43 -9
  46. package/docs/reference/training.md +31 -12
  47. package/package.json +1 -1
  48. package/src/anthropic.ts +22 -0
  49. package/src/assets/curve-designer.html +1 -1
  50. package/src/chat-template.ts +25 -7
  51. package/src/cli.ts +1077 -90
  52. package/src/config.ts +4 -1
  53. package/src/download.ts +99 -8
  54. package/src/embed.ts +77 -0
  55. package/src/eval/perplexity.ts +164 -0
  56. package/src/eval/runner.ts +15 -6
  57. package/src/eval/tasks/bfcl.ts +1 -1
  58. package/src/generate.ts +149 -18
  59. package/src/harness-pi.ts +43 -17
  60. package/src/index.ts +2 -0
  61. package/src/kv-store.ts +240 -98
  62. package/src/lora.ts +2 -2
  63. package/src/memory/article.ts +558 -0
  64. package/src/memory/chunk-validation.ts +150 -0
  65. package/src/memory/chunk.ts +305 -12
  66. package/src/memory/cluster.ts +344 -17
  67. package/src/memory/coverage.ts +134 -0
  68. package/src/memory/crosslink.ts +0 -0
  69. package/src/memory/db.ts +184 -45
  70. package/src/memory/entity.ts +203 -0
  71. package/src/memory/gate.ts +187 -0
  72. package/src/memory/history.ts +152 -0
  73. package/src/memory/ingest.ts +324 -11
  74. package/src/memory/model.ts +321 -0
  75. package/src/memory/normalize.ts +671 -0
  76. package/src/memory/parse.ts +22 -0
  77. package/src/memory/pipeline.ts +242 -30
  78. package/src/memory/prompts.ts +42 -0
  79. package/src/memory/query.ts +334 -0
  80. package/src/memory/reconcile.ts +734 -0
  81. package/src/memory/redirect.ts +108 -0
  82. package/src/memory/reindex.ts +325 -0
  83. package/src/memory/resolve.ts +307 -0
  84. package/src/memory/route.ts +272 -0
  85. package/src/memory/schedule.ts +6 -4
  86. package/src/memory/stages.ts +774 -0
  87. package/src/memory/synthesize.ts +1629 -20
  88. package/src/memory/tools.ts +446 -96
  89. package/src/memory/vault.ts +462 -35
  90. package/src/memory/wikify.ts +1057 -16
  91. package/src/mlx/array.ts +21 -0
  92. package/src/mlx/ffi.ts +3 -0
  93. package/src/mlx/ops.ts +72 -0
  94. package/src/model/compiled-decode.ts +43 -8
  95. package/src/model/factory.ts +29 -8
  96. package/src/model/flash-attention.ts +4 -2
  97. package/src/model/fused-decode-kernel.ts +14 -7
  98. package/src/model/fused-mlp-kernel.ts +169 -0
  99. package/src/model/fused-swiglu-kernel.ts +240 -0
  100. package/src/model/gemma4-base.ts +96 -7
  101. package/src/model/gemma4.ts +33 -2
  102. package/src/model/minicpm5.ts +106 -8
  103. package/src/model/qwen3-delta.ts +48 -1
  104. package/src/model/qwen3.ts +276 -0
  105. package/src/model/qwen3_5.ts +11 -2
  106. package/src/model/steel-linear-kernel.ts +96 -0
  107. package/src/model/support.ts +38 -8
  108. package/src/model/universal/archs.ts +332 -0
  109. package/src/model/universal/dense.ts +576 -0
  110. package/src/model/universal/modules.ts +163 -0
  111. package/src/model/universal/rope.ts +366 -0
  112. package/src/pi-provider.ts +1 -1
  113. package/src/pi-terminal.ts +29 -8
  114. package/src/pi-web.ts +19 -2
  115. package/src/prompt-cache.ts +55 -9
  116. package/src/quantize/config-writer.ts +1 -1
  117. package/src/registry.ts +219 -8
  118. package/src/responses.ts +23 -0
  119. package/src/sampler.ts +220 -9
  120. package/src/serve/batch-scheduler.ts +307 -83
  121. package/src/serve/generation-gateway.ts +156 -29
  122. package/src/server.ts +746 -73
  123. package/src/spec/drafter.ts +81 -49
  124. package/src/spec/dspark/data-dflash.ts +156 -0
  125. package/src/spec/dspark/data.ts +201 -0
  126. package/src/spec/dspark/generate-dflash.ts +193 -0
  127. package/src/spec/dspark/generate.ts +290 -0
  128. package/src/spec/dspark/loss.ts +213 -0
  129. package/src/spec/dspark/module-dflash.ts +361 -0
  130. package/src/spec/dspark/module.ts +540 -0
  131. package/src/spec/dspark/sample.ts +112 -0
  132. package/src/spec/generate.ts +40 -19
  133. package/src/ssd-cache.ts +213 -0
  134. package/src/status-page.html +1 -1
  135. package/src/tokenizer.ts +7 -0
  136. package/src/train/flash-cce.ts +81 -23
  137. package/src/train/fuse.ts +243 -0
  138. package/src/train/index.ts +3 -0
  139. package/src/train/job.ts +8 -0
  140. package/src/train/loss.ts +341 -40
  141. package/src/train/prefix-shared.ts +62 -11
  142. package/src/train/segmented.ts +215 -55
  143. package/src/train/trainer.ts +112 -38
  144. package/src/web/skills/memory/SKILL.md +40 -17
@@ -0,0 +1,287 @@
1
+ # Memory synthesis — the local Morpheus (M1 design)
2
+
3
+ > **SUPERSEDED (2026-07-01)** by
4
+ > [dreaming-nightly-pipeline.md](./dreaming-nightly-pipeline.md). Kept for
5
+ > history. Known-dead parts: embedding-based section-routing and silhouette
6
+ > signals (**no embeddings anywhere in the project**), bucket-as-optional
7
+ > framing, and the periodic-only wikify (RECONCILE/LINK/infobox are nightly
8
+ > stages now).
9
+
10
+ Status: **design**, 2026-06-26. Companion to [memory-system.md](./memory-system.md)
11
+ (the M0 substrate: vault, read tools, CLI, scheduling — all landed). This doc is
12
+ the M1 design for the **write side**: the synthesis pipeline that turns raw
13
+ conversations into well-formed wiki articles, **on local models**.
14
+
15
+ The inspiration and proof-of-concept is Lucien / The Dreaming / Morpheus
16
+ (`../lucien`) — it works, but every stage runs on a cloud model (`claude --model
17
+ opus`, `pi`). The whole point of M1 is to keep every learning from Lucien and
18
+ **solve the cost by running the pipeline locally**, using one local base model +
19
+ a per-stage LoRA adapter trained from Lucien's corpus (the e4b chunk adapter is
20
+ the first of these — designed components of the appliance, each distilled from
21
+ the cloud pipeline's gold output).
22
+
23
+ Guiding principle, inherited verbatim from Lucien: **do whatever Wikipedia does.**
24
+ Wikipedia turned a crowd of strangers into the largest organized, continuously-
25
+ updated knowledge base ever built. We are not going to out-design it — we copy
26
+ its conventions (articles, sections, talk pages, citations, wikilinks, edit
27
+ history) and supply the per-edit labor with local models instead of volunteers.
28
+
29
+ And the division of labor is fixed: **AI does the judgment; deterministic code
30
+ enforces the invariants we can't trust AI on** (footnote bijection, wikilink
31
+ canonicalization, the conservative write gate).
32
+
33
+ ## Current state: ZERO articles
34
+
35
+ The vault starts empty. So the pipeline has to handle the **creation flow**, not
36
+ just steady-state updates — most of the early design discussion assumed an
37
+ existing structured article to patch; from zero there is nothing to patch yet.
38
+ The two flows are genuinely different operations and the doc treats them
39
+ separately below.
40
+
41
+ ## The pipeline (DAG)
42
+
43
+ ```
44
+ WRITE · the local Morpheus (what `mlx-bun memory synthesize` runs)
45
+ ══════════════════════════════════════════════════════════════════
46
+
47
+ conversation transcripts (ingest, by cursor)
48
+ │
49
+ ▼ CHUNK ································· local model + chunk LoRA [e4b ✓]
50
+ single-topic (segment + label)
51
+ │
52
+ ▼ FILTER — worth keeping? ──no──▶ drop
53
+ │ yes
54
+ ▼ CLASSIFY → bucket(s) many-to-many ──no fit──▶ drop
55
+ │ bucket ≈ article; from zero, buckets are PROPOSED (taxonomy emerges)
56
+ ▼ SYNTHESIZE ······························ local model + synth LoRA
57
+ │ ├─ article does NOT exist → CREATE (sub-cluster → outline → per-section draft)
58
+ │ └─ article EXISTS → UPDATE (SECTION-ROUTE → PATCH one section)
59
+ │ + conservative write gate (≥70% prose floor, every citation survives, weak → NO-OP)
60
+ ▼
61
+ article written / one section changed (rest byte-identical)
62
+ │
63
+ ▼ NORMALIZE (deterministic code, not a model)
64
+ │ footnote renumber/repair · wikilink canonicalize
65
+ ▼
66
+ ▼ WIKIFY (periodic, local model + wikify LoRA) ◀── distinct LLM node
67
+ │ "make this a BETTER version" — per-section improve + structural pass
68
+ └──▶ Talk-page signals (embedding/silhouette): SPLIT · MERGE · CONTRADICTION
69
+
70
+
71
+ READ · any chat agent (M0, landed; read-only via memory_* tools)
72
+ ══════════════════════════════════════════════════════════════════
73
+ question ─▶ search / toc ─▶ read section · follow [[links]] ─▶ reason ─▶ answer
74
+ ```
75
+
76
+ ```mermaid
77
+ flowchart TD
78
+ T[conversation transcripts] --> C["CHUNK · local+LoRA"]
79
+ C -->|segment+label| F{worth keeping?}
80
+ F -->|no| D1((drop))
81
+ F -->|yes| CL["CLASSIFY → bucket(s)"]
82
+ CL -->|no fit| D2((drop))
83
+ CL --> EX{article exists?}
84
+ EX -->|no| CR["CREATE: sub-cluster → outline → per-section draft"]
85
+ EX -->|yes| UP["UPDATE: section-route → patch one section"]
86
+ CR --> GATE["conservative write gate"]
87
+ UP --> GATE
88
+ GATE --> NORM["NORMALIZE footnotes + wikilinks · deterministic"]
89
+ NORM --> WK["WIKIFY periodic · per-section improve + structural pass"]
90
+ WK -->|talk-page| SIG["SPLIT · MERGE · CONTRADICTION"]
91
+ ```
92
+
93
+ ## The two synthesis flows
94
+
95
+ ### CREATE (cold-start / bootstrap — the zero-article case)
96
+
97
+ Trigger: a bucket has accumulated enough chunks (≥N, conservative) and has **no
98
+ article file yet**. Naively this is whole-article generation from a pile of
99
+ chunks — exactly the open-ended task a small local model is *worst* at. So we
100
+ decompose it the same way we decompose updates — **section-granular**:
101
+
102
+ 1. **Sub-cluster** the bucket's chunks into proto-sections (cluster the chunks
103
+ *within* the bucket — the cohesion/embedding machinery applies one level down;
104
+ silhouette is a strong cohesion signal, ρ≈0.68 vs the judges).
105
+ 2. **Outline** — name each section from its sub-cluster, write the lead. A small
106
+ structural task, not long-form prose.
107
+ 3. **Draft each section** by synthesizing only its sub-cluster's chunks — the
108
+ *same bounded per-section operation* as a patch, just starting from empty.
109
+ 4. **Assemble** → article with `# Title`, lead, sections, `## References`, TOC.
110
+
111
+ So even creation never asks the model to hold a whole article in its head: it's
112
+ sub-cluster → outline → N bounded section drafts. Conservative bootstrap bias:
113
+ **under-cover rather than over-cover** — thin buckets stay `{{stub}}` or wait for
114
+ more material (Lucien's lesson: twenty good articles beat a sprawling mediocre
115
+ set).
116
+
117
+ ### UPDATE (steady state — article exists)
118
+
119
+ 1. **SECTION-ROUTE** the new chunk to the section(s) it affects, via the TOC —
120
+ "this topic affects THESE sections" (e.g. a Reflecting-Pool-renovation chunk →
121
+ the *Renovation* section, and possibly an *Abuse-of-Power* section of a Trump
122
+ article). Many-to-many at section granularity. If it fits no existing section,
123
+ the route is "new section."
124
+ 2. **PATCH** just that section: hand the model the section text + the chunk, ask
125
+ it to weave the chunk in with a `[^N]` citation. Bounded in, bounded out.
126
+ Facet extraction is implicit — the same chunk becomes the renovation facts in
127
+ one section and the no-bid-contract framing in another, because the model
128
+ writes what's relevant to the section it's editing.
129
+ 3. **Everything else stays byte-identical** — only the routed section is rewritten.
130
+
131
+ Both flows share the **conservative write gate** (already specced in
132
+ `synthesize.ts`): preserve prose, ≥70% word-count floor, every original `conv:`
133
+ citation survives, integrate-not-overwrite; a weak local pass is gated to NO-OP
134
+ rather than allowed to corrupt the vault.
135
+
136
+ ## Why WIKIFY is its own (necessary) node
137
+
138
+ Section-patches are greedy and local. Over many of them an article accumulates
139
+ cruft: a section sprawls, sentences go redundant, the lead goes stale, section
140
+ order drifts. **Wikify is the periodic global pass that keeps the article *good*
141
+ — "how do I turn this into a better version of this article" — and it is
142
+ necessary; without it you just keep bolting things on.** This is the LLM
143
+ editorial-restructure (Lucien's "Editorial restructure" commits), *distinct from*
144
+ the deterministic footnote/wikilink normalize that the current `wikify.ts` stub
145
+ conflates it with.
146
+
147
+ Structure makes even this global pass decompose toward local: wikify can run
148
+ **per-section "improve this section"** + a **cheap structural pass over the TOC**
149
+ (reorder, fix the lead, re-section, emit split/merge signals) instead of one
150
+ whole-article regeneration. So wikify is local-tractable for the same reason
151
+ synthesis is: sections.
152
+
153
+ ## Data model — pointers, not copies
154
+
155
+ The SQLite store (`~/.cache/mlx-bun/memory.sqlite`) is **derived, rebuildable**
156
+ state — not the source of truth. The principle that keeps it lean and consistent:
157
+ **materialize text in exactly one place; everything else points.**
158
+
159
+ - **Messages hold the text, once.** A `messages` table (text per message), keyed
160
+ by `(conv, position)`.
161
+ - **A chunk is a pointer range, not a copy.** `chunks(id, conv, start, end, label)`
162
+ — a segmentation is *where the boundaries are*, so storing it as a message range
163
+ + label is the natural representation. Synthesis *dereferences* to message text
164
+ only when it reads a chunk. Re-chunking rewrites pointer rows, never text.
165
+ - **Assignments are join tables (many-to-many).** `chunk_buckets(chunk_id,
166
+ bucket_name)` and, for the section design, `chunk_sections(chunk_id, article,
167
+ section_anchor)` — "this chunk affects THESE sections of THESE articles."
168
+ - **The article markdown is the only materialized synthesis.** Its `[^N]:
169
+ conv:HASH` citations are themselves *pointers back to source conversations*, not
170
+ copies. The vault is the precious git-tracked truth for *articles*; the DB is the
171
+ rebuildable index for *pipeline state*.
172
+
173
+ **The current `db.ts` stub violates this and must be fixed for M1** (it mirrors a
174
+ naive copy design, not Lucien's pointer design):
175
+ - `conversations.transcript` stores the whole transcript blob AND `chunks.text`
176
+ copies each slice → the same text twice. Replace with a `messages` table +
177
+ pointer `chunks` (drop `chunks.text`).
178
+ - `chunks.bucket TEXT` is one-bucket-per-chunk → breaks many-to-many. Replace with
179
+ `chunk_buckets`. Add `chunk_sections` for the section-route output.
180
+ - `synthesized_bucket_chunks` → `synthesized_chunk_sections(article, section_anchor,
181
+ chunk_id, synthesized_at)` so the patch ledger is per-section (fold each chunk
182
+ into each section exactly once).
183
+
184
+ Target tables: `messages` · `chunks` (pointers) · `conversations` · `buckets` ·
185
+ `chunk_buckets` · `chunk_sections` · `synthesized_chunk_sections` · `watermarks`.
186
+
187
+ ### Engine: bun:sqlite now, Turso swappable (for vectors)
188
+
189
+ Build M1 on **`bun:sqlite`** — native to Bun, zero-dependency, synchronous, the
190
+ most battle-tested DB there is, and already the project's pattern (`JobStore`,
191
+ `EvalDB`, `registry`, `MemoryStore`). No reason to pay friction on day one.
192
+
193
+ **Turso** (the current product — a from-scratch Rust rewrite of SQLite that is
194
+ *embedded / on-device / offline / SQLite-compatible*, NOT the old cloud-sync
195
+ product) is the evaluation path. Its one concrete payoff for us is **native vector
196
+ search** — and this design is embedding-heavy (section-routing, semantic article
197
+ search, the silhouette signals). Reasons to defer rather than adopt now: it's a
198
+ young engine vs C SQLite, and the Bun-integration story (native binding vs
199
+ FFI/overhead) needs checking.
200
+
201
+ The decision is cheap to defer because (a) Turso is SQLite-*compatible* so the
202
+ schema/SQL above is identical either way, (b) `MemoryStore` already wraps the DB
203
+ behind a small class, so it's a driver swap not a rewrite, and (c) this DB is a
204
+ **rebuildable cache** (truth = vault + git), so it's a low-risk place to adopt a
205
+ younger engine. Plan: keep the SQL standard, keep the driver swappable, and switch
206
+ to Turso *if/when* its vector search beats in-process cosine (or `sqlite-vec`) at a
207
+ corpus size that actually needs ANN.
208
+
209
+ ## The cascade (why local is possible at all)
210
+
211
+ ```
212
+ clean single-topic chunks → clean buckets → clean section-routing → bounded section-patch
213
+ └──── purity at the root ────┘
214
+ ```
215
+
216
+ Each stage's locality depends on the prior stage's cleanliness. If a chunk fuses
217
+ two topics, it pollutes every section it's routed into. So **chunk purity is the
218
+ foundation of the whole local system** — not an abstract metric.
219
+
220
+ Caution from the chunk research (research-journal, reports/orpo-chunk-paper):
221
+ embeddings measure **cohesion/dispersion well** (silhouette ρ≈0.68 per-conv vs
222
+ the LLM judges) but **purity/contamination poorly** (−intraSpread ρ≈0.32, ns
223
+ within variants). So a clean-bucket guarantee cannot lean on a cheap embedding
224
+ signal — it rides on the local **chunker** actually being good at single-topic
225
+ splitting (the e4b adapter's real job), backed where needed by a non-embedding
226
+ purity check, not by cosine geometry.
227
+
228
+ ## Stage → model → adapter
229
+
230
+ | stage | file | model | adapter |
231
+ |---|---|---|---|
232
+ | chunk | `src/memory/chunk.ts` | local | **chunk LoRA (e4b chunk-300, exists)** |
233
+ | classify | `src/memory/cluster.ts` | local + embeddings | classify LoRA (TODO) |
234
+ | synthesize (create/update) | `src/memory/synthesize.ts` | local | synth LoRA (TODO) |
235
+ | normalize | `src/memory/wikify.ts` (rename) | **deterministic code** | — |
236
+ | wikify (LLM) | **new node** | local | wikify LoRA (TODO) |
237
+
238
+ Section-routing (the new intra-article step) is embeddings-shaped: embed the
239
+ chunk (or its label) and the article's section headings/lead (from `parseToc` /
240
+ `extractSection`, already in `vault.ts`), route to the nearest section(s) over a
241
+ threshold. The local Qwen3-Embedding backbone (bit-exact, served, `/v1/embeddings`)
242
+ is exactly the "vector search over articles, not conversations" the vision calls
243
+ for — now applied at section granularity.
244
+
245
+ ## What changes in the scaffold
246
+
247
+ The M1 stubs are wired (`pipeline.ts` orchestrates ingest→chunk→cluster→
248
+ synthesize→wikify→changelog; stage bodies throw "not implemented"). Two
249
+ corrections vs the stubs:
250
+
251
+ 1. **`synthesize.ts` is whole-article today.** Make it `create-or-update`, with
252
+ update = section-route + section-patch (not whole-article regeneration).
253
+ 2. **The LLM wikify node is missing.** The current `wikify.ts` is *only*
254
+ deterministic normalize. Keep that (rename to `normalize.ts`), and add a
255
+ distinct **LLM wikify** node for the periodic "better version" pass.
256
+
257
+ ## Build sequence
258
+
259
+ Cold-start first, because the vault is empty and you can't patch what doesn't exist:
260
+
261
+ 1. **Wire CHUNK** to the e4b chunk adapter (`chunk.ts`) — the one stage with an
262
+ adapter in hand. Verify single-topic chunks + labels on real conversations.
263
+ 2. **CLASSIFY from zero** (`cluster.ts`) — emergent bucket taxonomy
264
+ (`Meta/Buckets.md`); assign chunks to buckets, propose new ones; filter no-fit.
265
+ 3. **CREATE flow** (`synthesize.ts`) — sub-cluster → outline → per-section draft
266
+ for buckets crossing the chunk threshold. Behind the conservative gate. This
267
+ is what gets us from zero to a first set of articles.
268
+ 4. **NORMALIZE** (`normalize.ts`) — wire the deterministic footnote/wikilink
269
+ repair (`vault.ts` already has the resolver).
270
+ 5. **UPDATE flow** — section-route + patch, once articles exist to update.
271
+ 6. **WIKIFY (LLM)** + Talk-page split/merge signals — once articles have
272
+ accumulated enough cruft/drift to need them.
273
+
274
+ Steps 1–4 are the cold-start path (zero → first articles); 5–6 are steady state.
275
+
276
+ ## Open questions
277
+
278
+ - **Article granularity / when to CREATE** — chunk threshold per bucket; when a
279
+ bucket should become one article vs several (the vision's "soft size caps").
280
+ - **Section-route confidence** — threshold for "fits an existing section" vs
281
+ "new section needed" vs "route to a different article."
282
+ - **Provisional facts** — representing "work set to begin July" so the next pass
283
+ cleanly updates it to "began / completed" (temporal/trajectory tracking).
284
+ - **Create vs strong-model** — is first-creation good enough on the local model,
285
+ or is bootstrap the one place a stronger model is worth it (vision: bootstrap is
286
+ heavier but the same code path)?
287
+ - **The purity check** — what non-embedding signal guarantees clean buckets.