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.
- package/README.md +112 -20
- package/docs/design/batching-perf-path.md +232 -0
- package/docs/design/batching-v2-plan.md +515 -0
- package/docs/design/bucketing-stage.md +196 -0
- package/docs/design/compat-cli-surface-design.md +11 -11
- package/docs/design/docs-reorg-plan.md +1 -1
- package/docs/design/dreaming-nightly-pipeline.md +291 -0
- package/docs/design/dspark-speculative-decoding.md +106 -0
- package/docs/design/generic-model-support.md +381 -0
- package/docs/design/memory-inference-path.md +422 -0
- package/docs/design/memory-synthesis.md +287 -0
- package/docs/design/minicpm5-decode-megakernel.md +484 -0
- package/docs/design/mlx-lm-tool-parity-plan.md +535 -0
- package/docs/design/omlx-adoption-map.md +90 -0
- package/docs/design/orpo-dynamic-lambda.md +41 -0
- package/docs/design/orpo-training.md +35 -5
- package/docs/design/parallel-slots.md +12 -1
- package/docs/design/parity-tier-dag.md +13 -5
- package/docs/design/segmented-backward-training.md +15 -0
- package/docs/design/spec-decode-larger-targets.md +229 -0
- package/docs/design/ssd-kv-cold-tier.md +158 -0
- package/docs/design/tauri-desktop-app.md +2 -2
- package/docs/design/the-dreaming-handoff.md +141 -0
- package/docs/design/the-dreaming-master-plan.md +446 -0
- package/docs/design/write-pipeline-entity.md +468 -0
- package/docs/investigations/curve-bisector-routing.md +1 -1
- package/docs/investigations/decode-roofline-lookagain.md +348 -0
- package/docs/investigations/dspark-handoff.md +58 -0
- package/docs/investigations/kernel-perf-review-2026-07.md +171 -0
- package/docs/planning/PRODUCT_ROADMAP.md +2 -2
- package/docs/planning/curve-sampler-research-plan.md +331 -0
- package/docs/planning/memory-docs-and-dag-plan.md +185 -0
- package/docs/planning/pi-integration-review.md +178 -0
- package/docs/planning/release-notes-v0.0.10.md +57 -0
- package/docs/planning/release-notes-v0.0.9.md +122 -0
- package/docs/planning/web-ui-pass-plan.md +213 -0
- package/docs/planning/website-readme-pass-plan.md +224 -0
- package/docs/reference/cli.md +255 -0
- package/docs/reference/embedding.md +7 -2
- package/docs/reference/library-api.md +22 -3
- package/docs/reference/memory.md +5 -5
- package/docs/reference/models.md +137 -0
- package/docs/reference/orpo-quickstart.md +6 -0
- package/docs/reference/server-api.md +187 -9
- package/docs/reference/server-config.md +43 -9
- package/docs/reference/training.md +31 -12
- package/package.json +1 -1
- package/src/anthropic.ts +22 -0
- package/src/assets/curve-designer.html +1 -1
- package/src/chat-template.ts +25 -7
- package/src/cli.ts +1077 -90
- package/src/config.ts +4 -1
- package/src/download.ts +99 -8
- package/src/embed.ts +77 -0
- package/src/eval/perplexity.ts +164 -0
- package/src/eval/runner.ts +15 -6
- package/src/eval/tasks/bfcl.ts +1 -1
- package/src/generate.ts +149 -18
- package/src/harness-pi.ts +43 -17
- package/src/index.ts +2 -0
- package/src/kv-store.ts +240 -98
- package/src/lora.ts +2 -2
- package/src/memory/article.ts +558 -0
- package/src/memory/chunk-validation.ts +150 -0
- package/src/memory/chunk.ts +305 -12
- package/src/memory/cluster.ts +344 -17
- package/src/memory/coverage.ts +134 -0
- package/src/memory/crosslink.ts +0 -0
- package/src/memory/db.ts +184 -45
- package/src/memory/entity.ts +203 -0
- package/src/memory/gate.ts +187 -0
- package/src/memory/history.ts +152 -0
- package/src/memory/ingest.ts +324 -11
- package/src/memory/model.ts +321 -0
- package/src/memory/normalize.ts +671 -0
- package/src/memory/parse.ts +22 -0
- package/src/memory/pipeline.ts +242 -30
- package/src/memory/prompts.ts +42 -0
- package/src/memory/query.ts +334 -0
- package/src/memory/reconcile.ts +734 -0
- package/src/memory/redirect.ts +108 -0
- package/src/memory/reindex.ts +325 -0
- package/src/memory/resolve.ts +307 -0
- package/src/memory/route.ts +272 -0
- package/src/memory/schedule.ts +6 -4
- package/src/memory/stages.ts +774 -0
- package/src/memory/synthesize.ts +1629 -20
- package/src/memory/tools.ts +446 -96
- package/src/memory/vault.ts +462 -35
- package/src/memory/wikify.ts +1057 -16
- package/src/mlx/array.ts +21 -0
- package/src/mlx/ffi.ts +3 -0
- package/src/mlx/ops.ts +72 -0
- package/src/model/compiled-decode.ts +43 -8
- package/src/model/factory.ts +29 -8
- package/src/model/flash-attention.ts +4 -2
- package/src/model/fused-decode-kernel.ts +14 -7
- package/src/model/fused-mlp-kernel.ts +169 -0
- package/src/model/fused-swiglu-kernel.ts +240 -0
- package/src/model/gemma4-base.ts +96 -7
- package/src/model/gemma4.ts +33 -2
- package/src/model/minicpm5.ts +106 -8
- package/src/model/qwen3-delta.ts +48 -1
- package/src/model/qwen3.ts +276 -0
- package/src/model/qwen3_5.ts +11 -2
- package/src/model/steel-linear-kernel.ts +96 -0
- package/src/model/support.ts +38 -8
- package/src/model/universal/archs.ts +332 -0
- package/src/model/universal/dense.ts +576 -0
- package/src/model/universal/modules.ts +163 -0
- package/src/model/universal/rope.ts +366 -0
- package/src/pi-provider.ts +1 -1
- package/src/pi-terminal.ts +29 -8
- package/src/pi-web.ts +19 -2
- package/src/prompt-cache.ts +55 -9
- package/src/quantize/config-writer.ts +1 -1
- package/src/registry.ts +219 -8
- package/src/responses.ts +23 -0
- package/src/sampler.ts +220 -9
- package/src/serve/batch-scheduler.ts +307 -83
- package/src/serve/generation-gateway.ts +156 -29
- package/src/server.ts +746 -73
- package/src/spec/drafter.ts +81 -49
- package/src/spec/dspark/data-dflash.ts +156 -0
- package/src/spec/dspark/data.ts +201 -0
- package/src/spec/dspark/generate-dflash.ts +193 -0
- package/src/spec/dspark/generate.ts +290 -0
- package/src/spec/dspark/loss.ts +213 -0
- package/src/spec/dspark/module-dflash.ts +361 -0
- package/src/spec/dspark/module.ts +540 -0
- package/src/spec/dspark/sample.ts +112 -0
- package/src/spec/generate.ts +40 -19
- package/src/ssd-cache.ts +213 -0
- package/src/status-page.html +1 -1
- package/src/tokenizer.ts +7 -0
- package/src/train/flash-cce.ts +81 -23
- package/src/train/fuse.ts +243 -0
- package/src/train/index.ts +3 -0
- package/src/train/job.ts +8 -0
- package/src/train/loss.ts +341 -40
- package/src/train/prefix-shared.ts +62 -11
- package/src/train/segmented.ts +215 -55
- package/src/train/trainer.ts +112 -38
- package/src/web/skills/memory/SKILL.md +40 -17
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# Bucketing / Classify stage — local build plan
|
|
2
|
+
|
|
3
|
+
Status: **design**, 2026-06-26. Companion to [memory-synthesis.md](./memory-synthesis.md).
|
|
4
|
+
Grounded in a research+critique workflow over the real Lucien classify + the local
|
|
5
|
+
foundation; re-centered on the governing principle below.
|
|
6
|
+
|
|
7
|
+
## Governing principle — capacity by decomposition
|
|
8
|
+
|
|
9
|
+
**A small local model's effective capacity scales inversely with task size. You don't
|
|
10
|
+
make the model smarter; you make each task narrower.** Every stage is a *ladder of
|
|
11
|
+
atomic, single-purpose tasks*, each one a single clear question. The full pipeline:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
"what buckets should we have?" ← taxonomy (cold-start)
|
|
15
|
+
"which buckets need this chunk?" ← classify / assignment
|
|
16
|
+
"which section needs this info?" ← section routing
|
|
17
|
+
"integrate THIS chunk into THIS section" ← synthesis patch (most atomic)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
…and each rung decomposes *further* into the smallest decisive question. We never ask
|
|
21
|
+
the local model to "assign a chunk among 12 buckets and emit JSON" — that is still too
|
|
22
|
+
big. We ask **"does this chunk belong in bucket B? yes/no,"** one bucket at a time.
|
|
23
|
+
|
|
24
|
+
**Retrieval (embeddings, lexical) exists only to NARROW the candidate set so the model
|
|
25
|
+
makes the fewest, smallest, clearest calls. It never decides — the model decides, one
|
|
26
|
+
tiny question at a time.** This is the production home for the embedding work: a
|
|
27
|
+
candidate-narrower, not a classifier.
|
|
28
|
+
|
|
29
|
+
**Why brute-force decomposition is affordable: the model is LOCAL, so inference is ~free.**
|
|
30
|
+
A cloud model forces you to minimize calls (which is exactly why Lucien made one big
|
|
31
|
+
"reason over everything" call). A local model flips the economics — you can run **M×N tiny
|
|
32
|
+
binary checks** ("does chunk *i* belong in bucket *j*?" / "should chunk *i* update section
|
|
33
|
+
*j*?") where a cloud model would have to make one expensive combined decision. We trade one
|
|
34
|
+
big unreliable call for many tiny reliable ones. Local-ness is what makes radical
|
|
35
|
+
decomposition viable; retrieval just keeps M×N from getting silly.
|
|
36
|
+
|
|
37
|
+
This is adapting + improving Lucien, not duplicating it. Lucien put all 379 buckets in
|
|
38
|
+
Opus's context and let it reason globally in one shot — a capability a 1B model does not
|
|
39
|
+
have. We replace "one global decision" with "many tiny local decisions."
|
|
40
|
+
|
|
41
|
+
## What we keep from Lucien
|
|
42
|
+
|
|
43
|
+
- Classify on chunk **labels** (~49-char topic strings), not text or vectors.
|
|
44
|
+
- The `Bucketing.md` policy governs behavior, inlined into the task prompt — editing it
|
|
45
|
+
re-tunes routing with no code change.
|
|
46
|
+
- **Many-to-many** (a chunk → 0..3 buckets; empty is legal = the substance filter).
|
|
47
|
+
- **Create buckets by judgment** when nothing fits and the topic is substantive.
|
|
48
|
+
- **Existing-buckets-win** (re-scoped to global below — fix E).
|
|
49
|
+
- Idempotent set-difference resumption (`NOT IN chunk_buckets`).
|
|
50
|
+
|
|
51
|
+
## The bucketing stage, decomposed
|
|
52
|
+
|
|
53
|
+
### "which buckets need this chunk?" — assignment
|
|
54
|
+
|
|
55
|
+
For one chunk (its label):
|
|
56
|
+
1. **Narrow (no model):** embedding shortlist — cosine(label, bucket centroid) → top-K
|
|
57
|
+
candidates; PLUS a **lexical floor** (any bucket whose name token-overlaps the label)
|
|
58
|
+
so an embedding miss can't drop an obvious match [fix C].
|
|
59
|
+
2. **Decide (atomic model task, one per candidate):** *"Does this chunk — `<label>` —
|
|
60
|
+
belong in bucket `<name>: <description>`? Answer yes or no."* Run over the (few)
|
|
61
|
+
shortlisted candidates. No multi-item JSON, no batch indices → the JSON-fidelity and
|
|
62
|
+
batching failure modes (critique D) simply don't exist. A chunk can get "yes" on
|
|
63
|
+
several candidates (many-to-many falls out naturally) or none.
|
|
64
|
+
3. **New bucket (atomic task, only if every candidate was "no" AND substantive):**
|
|
65
|
+
*"This topic fits no existing bucket. Propose one Wikipedia-style bucket name (2–6
|
|
66
|
+
words) + one-line description, or answer NONE if too thin."* Then the **global gate
|
|
67
|
+
[fix E — the #1 correctness fix]:** re-embed the proposed name against the FULL bucket
|
|
68
|
+
centroid cache; if the nearest existing bucket clears a similarity threshold, downgrade
|
|
69
|
+
to it. This restores Lucien's "none of 379 fit" discipline that a shortlist-only view
|
|
70
|
+
would break (→ taxonomy explosion).
|
|
71
|
+
|
|
72
|
+
Each model call is a tiny yes/no or a single short proposal — the clear single tasks a
|
|
73
|
+
1B model can do reliably. Cost is more calls, but each is fast and reliable; embeddings
|
|
74
|
+
keep K small so it's bounded.
|
|
75
|
+
|
|
76
|
+
### "what buckets should we have?" — cold-start taxonomy (from zero)
|
|
77
|
+
|
|
78
|
+
There are **7308 distinct labels of 7316** — dedup collapses nothing, and a 1B model
|
|
79
|
+
cannot globally compress thousands of labels into a coherent 30–60-bucket taxonomy
|
|
80
|
+
(that is exactly Opus's global-reasoning ability). Decompose:
|
|
81
|
+
1. **Group (geometry, no model):** embed all labels → k-means (k∈[30,60], pick by
|
|
82
|
+
silhouette). The geometry does the grouping the model can't do globally.
|
|
83
|
+
2. **Purity-gate each group (atomic task) [fix B]:** *"Do these labels form ONE topic or
|
|
84
|
+
more than one?"* k-means optimizes cohesion and is blind to fusion (the ρ≈0.32 purity
|
|
85
|
+
blind spot we measured) — split any group the model flags before it becomes a bucket.
|
|
86
|
+
3. **Name each group (atomic task):** *"Here are ~15 representative labels. Give ONE
|
|
87
|
+
Wikipedia-style bucket name (2–6 words) + a one-line description. Be specific, not
|
|
88
|
+
generic."*
|
|
89
|
+
4. **Seed centroids from members [fix H]:** each new bucket's centroid = mean of the
|
|
90
|
+
cluster labels that birthed it (those vectors already exist) — so the first assignment
|
|
91
|
+
pass runs on strong member-based centroids, not weak name-only ones.
|
|
92
|
+
|
|
93
|
+
### later rungs (designed here, built after classify)
|
|
94
|
+
|
|
95
|
+
- **"which section needs this info?"** — the **M×N binary** pattern: for an article's
|
|
96
|
+
assigned bucket of *M* chunks and its *N* TOC sections, ask *"should chunk i update
|
|
97
|
+
section j? yes/no"* — M×N times. The "yes" pairs become atomic patches. A chunk that gets
|
|
98
|
+
**all "no" and is substantive → an atomic CREATE-NEW-SECTION task** (mirrors new-bucket).
|
|
99
|
+
(Embeddings can prune N, but with a local model you can also just run all M×N.)
|
|
100
|
+
- **"integrate THIS chunk into THIS section"** — the atomic synthesis patch (memory-synthesis.md),
|
|
101
|
+
applied to each (chunk, section) "yes" pair.
|
|
102
|
+
|
|
103
|
+
### The entities (distinct — do not conflate)
|
|
104
|
+
|
|
105
|
+
- **chunk** — one topical segment of a conversation (pointer + label).
|
|
106
|
+
- **bucket** — a single-topic GROUP OF CHUNKS (the classify output; the "Cameras and
|
|
107
|
+
Camera Gear" container you drop camera chunks into). **A bucket is NOT an article.**
|
|
108
|
+
- **article** — a synthesized wiki page. A bucket RELATES TO **one or more** articles
|
|
109
|
+
(many-to-many): the reflecting-pool-renovation bucket relates to the *Lincoln Memorial
|
|
110
|
+
Reflecting Pool* article AND a *Trump* article.
|
|
111
|
+
- **section** — a part of an article (its TOC entries).
|
|
112
|
+
|
|
113
|
+
### Hierarchical pruning (coarse gates fine)
|
|
114
|
+
|
|
115
|
+
Decompose top-down and **prune the subtree at every "no."** The levels are distinct:
|
|
116
|
+
chunk → bucket → article(s) → section(s). The coarse gate is **"does this bucket relate to
|
|
117
|
+
this article? yes/no"** — if no, skip *every* section of that article (a bucket can't
|
|
118
|
+
relate to a section of an article it doesn't relate to). So the M×N section checks run
|
|
119
|
+
*only inside the few articles a bucket relates to*, never "× every section in the wiki."
|
|
120
|
+
Same idea recursively: each coarser binary check prunes the finer ones beneath it.
|
|
121
|
+
Retrieval narrows candidates *within* a level; pruning eliminates whole levels.
|
|
122
|
+
|
|
123
|
+
### The uniform growth rule (no-match → create)
|
|
124
|
+
|
|
125
|
+
At *every* rung, "no existing candidate matched" branches on substance, it does not just
|
|
126
|
+
drop: **substantive → CREATE, trivial → drop.** A chunk that fits no bucket → new bucket;
|
|
127
|
+
a bucket that relates to no existing article → new article; a chunk that fits no section of
|
|
128
|
+
a related article → new section. Only non-substantive trivia is filtered. So substantive
|
|
129
|
+
content always lands *somewhere* — this is how the taxonomy grows new buckets, the wiki
|
|
130
|
+
grows new articles, and articles grow new sections (the Wikipedia editor adds a page or a
|
|
131
|
+
section for a fact that fits nowhere). Each create is its own atomic task, gated against
|
|
132
|
+
the existing set (the new-bucket global gate, fix E).
|
|
133
|
+
|
|
134
|
+
## Data model (`src/memory/db.ts`)
|
|
135
|
+
|
|
136
|
+
Rewrite the copy-schema to the pointer design (memory-synthesis.md; no live data):
|
|
137
|
+
`messages` (text once, `(conv, position)`), `chunks` (pointers + `label`, no text),
|
|
138
|
+
`buckets` (writable), `chunk_buckets` (M:N — the classify output). `PRAGMA foreign_keys
|
|
139
|
+
= ON` + `ON DELETE CASCADE` (fixes Lucien's 368 orphan edges / 570 dangling pointers).
|
|
140
|
+
**Keep `synthesized_bucket_chunks` per-(bucket,chunk) [fix G]** — do not rename the
|
|
141
|
+
working ledger for the unbuilt section feature; `chunk_sections` may exist as a
|
|
142
|
+
future-proof empty table only.
|
|
143
|
+
|
|
144
|
+
## Eval — embeddings as the test instrument
|
|
145
|
+
|
|
146
|
+
`scripts/experiments/bucket-cohesion-signal.ts` (mirrors `chunk-embedding-signal.ts`):
|
|
147
|
+
- **[measure FIRST — the #1 risk] shortlist recall ceiling:** is each gold bucket in its
|
|
148
|
+
chunk's embedding top-K? Report overall **and per bucket-size-bin** (122/379 buckets
|
|
149
|
+
have <5 chunks — the global number hides tail collapse [fix C]). If low, the shortlist
|
|
150
|
+
caps recall and must be re-tuned before building on it.
|
|
151
|
+
- **bucket cohesion / separation / subcluster-gap** baseline over the 379 cloud buckets.
|
|
152
|
+
- **purity (LLM judge)** on lowest-subcluster-gap buckets — embeddings can't grade purity
|
|
153
|
+
(ρ≈0.32); confirm the asymmetry so the judge stays load-bearing.
|
|
154
|
+
- **decision accuracy:** since each task is binary, eval is per-(chunk,candidate-bucket)
|
|
155
|
+
yes/no accuracy + per-chunk Jaccard vs the 8441 valid gold edges. Report **oracle-shortlist**
|
|
156
|
+
F1 (force gold into candidates → pure model judgment) vs **real-shortlist** F1 (end-to-end);
|
|
157
|
+
the gap is the retrieval tax. **The LoRA decision keys off the oracle F1 [fix F].**
|
|
158
|
+
|
|
159
|
+
## LoRA decision (staged)
|
|
160
|
+
|
|
161
|
+
- **Stage 0 — embedding-only:** top-K cosine + threshold vs the 8809 gold edges → F1/Jaccard.
|
|
162
|
+
Might end it (classify could be embedding-shaped).
|
|
163
|
+
- **Stage 1 — base MiniCPM5-1B, prompt-only, binary tasks + shortlist:** measure oracle-F1,
|
|
164
|
+
yes/no consistency. The binary framing is *much* easier for a small base model than JSON
|
|
165
|
+
multi-assignment — Stage 1 may clear the bar with no LoRA precisely because the tasks are atomic.
|
|
166
|
+
- **Stage 2 — train `memory-bucket` LoRA, only if Stage 0/1 miss.** Data from lucien.db, one
|
|
167
|
+
example per (chunk, candidate-bucket) binary decision (gold = belongs?), shortlist regime =
|
|
168
|
+
serve regime. Recipe = `chunk-finetune.ts` (sft, rank 16, lr 1e-5, seq 8192,
|
|
169
|
+
`MLX_BUN_PERF_KERNEL=0 MLX_BUN_FUSED_GELU=0`, no train-flash); adapter →
|
|
170
|
+
`~/.cache/mlx-bun/adapters/memory-bucket`; pick checkpoints by the F1 judge, not val loss.
|
|
171
|
+
**GPU-gated.**
|
|
172
|
+
|
|
173
|
+
## Build sequence
|
|
174
|
+
|
|
175
|
+
Runnable now (no GPU training, no server):
|
|
176
|
+
1. **[FIRST]** `bucket-cohesion-signal.ts` → shortlist recall ceiling (per size-bin) +
|
|
177
|
+
cohesion/sep/subcluster baseline. Verifies the shortlist foundation before any build.
|
|
178
|
+
2. Rewrite `db.ts` to the pointer schema + `foreign_keys` + helpers; in-memory test.
|
|
179
|
+
3. Stage-0 embedding-only baseline (oracle + real F1).
|
|
180
|
+
4. Purity judge on lowest-subcluster-gap buckets; confirm the asymmetry.
|
|
181
|
+
5. Implement `cluster.ts` as **atomic tasks**: shortlist → per-candidate yes/no → new-bucket
|
|
182
|
+
proposal + global gate; cold-start = cluster → purity-gate → name → member-seed.
|
|
183
|
+
6. Stage-1 eval (base, no adapter) → apply LoRA criteria.
|
|
184
|
+
|
|
185
|
+
GPU-gated (only if needed): 7. build binary SFT data · 8. train `memory-bucket` · 9. re-eval.
|
|
186
|
+
Integration: 10. wire `clusterChunks` into `pipeline.ts`.
|
|
187
|
+
|
|
188
|
+
**First command:** `bun scripts/experiments/bucket-cohesion-signal.ts`
|
|
189
|
+
|
|
190
|
+
## Open risks
|
|
191
|
+
|
|
192
|
+
- Shortlist recall ceiling (measure per size-bin first — step 1).
|
|
193
|
+
- More model calls per chunk (K binary vs 1 JSON) — bounded by K; each call is tiny/fast.
|
|
194
|
+
- Cold-start naming going generic — seed with distinctive labels + "specific not generic".
|
|
195
|
+
- Distilled labels ≠ ground truth — don't over-trust exact-match F1; use reference-free
|
|
196
|
+
silhouette + purity on divergences.
|
|
@@ -55,14 +55,14 @@ shared subset; optiq-only features are additive (marked ⊕).
|
|
|
55
55
|
| `generate` | mlx_lm.generate | ✅ `generate.ts` (no CLI verb) | wire verb + full flag set |
|
|
56
56
|
| `chat` | mlx_lm.chat | engine ✅, web chat ✅; no terminal REPL (`tui.ts` is help-formatting) | build REPL frontend over `generate` |
|
|
57
57
|
| `server` | mlx_lm.server / optiq serve | ✅ `serve` | rename flags; ⊕ `--kv-config`, `--anthropic` honored (default on) |
|
|
58
|
-
| `convert` | mlx_lm.convert + optiq convert |
|
|
58
|
+
| `convert` | mlx_lm.convert + optiq convert | ✅ `convert` (wraps `quantize/quantizer.ts`) | -q/--q-bits/--q-group-size/--mlx-path/--upload-repo parity (upload rides `hf-push.ts`); ⊕ `--target-bpw/--candidate-bits/--calibration-mix/--n-calibration` (mixed precision); no `--dtype`/`--dequantize`/plain non-quantizing convert |
|
|
59
59
|
| `lora` | mlx_lm.lora + optiq lora | serve/hot-swap ✅, train ✅ | `--train/--test`; ⊕ `--rank-scaling by_bits\|by_kl`; `lora info` |
|
|
60
|
-
| `fuse` | mlx_lm.fuse |
|
|
60
|
+
| `fuse` | mlx_lm.fuse | ✅ `fuse` (`train/fuse.ts`) | --adapter-path/--save-path parity; preserves per-module quant layout; dequant/GGUF/upload not supported (says so, exits 1) |
|
|
61
61
|
| `cache_prompt` | mlx_lm.cache_prompt | runtime ✅, CLI ❌ | precompute + save reusable KV cache file |
|
|
62
62
|
| `benchmark` | mlx_lm.benchmark / optiq benchmark | ✅ `benchmark` | flag-name parity (`-p/-g/-b/-n`); ppl half ❌ |
|
|
63
63
|
| `evaluate` | mlx_lm.evaluate / optiq eval | ❌ (`evals` = our bench viewer) | task harness; ⊕ optiq task names |
|
|
64
|
-
| `perplexity` | mlx_lm.perplexity |
|
|
65
|
-
| `upload` | mlx_lm.upload |
|
|
64
|
+
| `perplexity` | mlx_lm.perplexity | ✅ `perplexity` (`eval/perplexity.ts`) | reference methodology (pack → non-overlapping rows → f32 CE, delta-method SE); data source is a LOCAL .txt/.jsonl, not an HF dataset |
|
|
65
|
+
| `upload` | mlx_lm.upload | ✅ `upload` (wraps `hf-push.ts`, the web push engine) | --path/--upload-repo parity; ⊕ `--private`; token from hf.json/$HF_TOKEN/hf auth login |
|
|
66
66
|
| `manage` | mlx_lm.manage | ✅ `get`/`scan`/`ls` | add `--delete/--pattern/--scan` parity |
|
|
67
67
|
| `kv-cache` | optiq kv-cache | runtime ✅, profiler ❌ | per-layer sensitivity → `kv_config.json` |
|
|
68
68
|
| `latency` | optiq latency | ✅ `fit` | alias + `--calibrate` |
|
|
@@ -146,19 +146,19 @@ column; the 🟥 column is the capability matrix that fills in behind it.
|
|
|
146
146
|
| Precompute prompt cache | `cache_prompt` | 🔌 | Phase 17 |
|
|
147
147
|
| LoRA serve / hot-swap | `lora` (serve) | ✅ | done (Phase 8) |
|
|
148
148
|
| `lora info` reporting | `lora info` | 🔌 | Phase 17 |
|
|
149
|
-
| **HF→MLX convert → new model artifact** | `convert` |
|
|
150
|
-
| **Sensitivity / mixed-precision weights** | `convert`, `dynamic_quant` |
|
|
149
|
+
| **HF→MLX convert → new model artifact** | `convert` | ✅ | done (CLI verb over the native quantize pipeline) |
|
|
150
|
+
| **Sensitivity / mixed-precision weights** | `convert`, `dynamic_quant` | ✅ (`convert --target-bpw`) | done via convert; `dynamic_quant` alias 🟥 |
|
|
151
151
|
| **AWQ / DWQ / GPTQ (weights)** | `awq`/`dwq`/`gptq` | 🟥 | Model-quant |
|
|
152
152
|
| KV-cache quant: bf16 / uniform / **mixed per-layer** (kv_config.json) | `server` (`off`/`N`/`config`) | ✅ | done (Phase 9/10; config.ts reads, generate.ts applies per-layer) |
|
|
153
153
|
| KV sensitivity *profiler* (authors a NEW kv_config.json) | `kv-cache` | 🟥 | model-prep, low pri (shipped artifacts already include one) |
|
|
154
154
|
| TurboQuant — optional extra quant *method* (rotation VQ) | scheme, not a verb | 🟥 | Phase 13 (KV plumbing already exists) |
|
|
155
155
|
| **LoRA / DoRA / full training** | `lora --train` | ✅ | Training (done: Phase A MiniCPM5 + Phase B e4b) |
|
|
156
156
|
| **Sensitivity-aware rank scaling** | `lora --rank-scaling` | 🟥 | Training |
|
|
157
|
-
| **Fuse adapters (+GGUF/upload)** | `fuse` | 🟥 |
|
|
157
|
+
| **Fuse adapters (+GGUF/upload)** | `fuse` | ✅ fuse; GGUF/dequant/upload 🟥 | done (fold-into-base, quant layout preserved) |
|
|
158
158
|
| **lm-eval-harness tasks** | `evaluate` | 🟥 | Eval (new phase) |
|
|
159
159
|
| **optiq eval suite (kl/gsm8k/ifeval/bfcl/hashhop…)** | `evaluate` | 🟥 | Eval |
|
|
160
|
-
| **Perplexity** | `perplexity` |
|
|
161
|
-
| **HF upload** | `upload` |
|
|
160
|
+
| **Perplexity** | `perplexity` | ✅ | done (local text/jsonl datasets) |
|
|
161
|
+
| **HF upload** | `upload` | ✅ | done (native `hf-push.ts`; also `convert --upload-repo`) |
|
|
162
162
|
| **Distributed share** | `share` | 🟥 | Distribution (lowest pri) |
|
|
163
163
|
| **Web UI: quantize** | `lab`/UI | 🟥 | gated on Quantize |
|
|
164
164
|
| **Web UI: fine-tune** | `lab`/UI | ✅ | done (SFT/DPO workflow, loss curve, merge, export) |
|
|
@@ -181,7 +181,7 @@ does. The real capability work splits along two axes:
|
|
|
181
181
|
(Phase 13).
|
|
182
182
|
- **Training**: LoRA/DoRA/full training ✅ (shipped); `fuse` verb (adapter merge, GGUF export, upload) 🟥 remaining.
|
|
183
183
|
- **Eval**: harness + `perplexity`.
|
|
184
|
-
- **Distribution**: `upload
|
|
184
|
+
- **Distribution**: `upload` ✅ (CLI verb + `convert --upload-repo`); `share` 🟥.
|
|
185
185
|
|
|
186
186
|
The OptIQ-Lab web-UI tiles gate on these: quantize ← model-quant,
|
|
187
187
|
fine-tune ← training, training-data ← independent, chat ✅.
|
|
@@ -225,5 +225,5 @@ correctness diffs need no clean machine.
|
|
|
225
225
|
to author new configs (low pri) + the TurboQuant method (Phase 13).
|
|
226
226
|
4. Training → ✅ `lora --train` + web-UI fine-tune tile (both shipped); remaining: `fuse` verb (adapter merge, GGUF export, upload).
|
|
227
227
|
5. Eval → `evaluate`/`perplexity`.
|
|
228
|
-
6. Distribution → `upload
|
|
228
|
+
6. Distribution → `upload` ✅ (2026-07-01); `share` remaining.
|
|
229
229
|
7. Web UI training-data tile (independent of engine work).
|
|
@@ -12,7 +12,7 @@ and need a test run, so deferred to their own pass). Authored 2026-06-14.
|
|
|
12
12
|
- P1 #5 — **DONE**: `STATUS.md` exists at root.
|
|
13
13
|
- P1 #6 — **DONE**: `docs/planning/` exists with `IDEAS.md`, `PRODUCT_ROADMAP.md`, `ResearchTopics.md`, `journal.md`.
|
|
14
14
|
- P1 #7 — open (optimization_plan.md still lives in docs/design/ as a standalone).
|
|
15
|
-
- P1 #8 —
|
|
15
|
+
- P1 #8 — **RESOLVED (2026-07-01)**: the duplicate `archive/pi-builtin-investigation.html` is already gone; `archive/` deliberately keeps 2 small demo artifacts (`mlx-bun-lab-report.html`, `wwdc-mlx-bun.html`, 76K total), tracked.
|
|
16
16
|
- P2 #9 — **NOT DONE**: `harness-pi.ts`, `pi-provider.ts`, `pi-terminal.ts`, `pi-web.ts` still live flat in `src/` root (no `src/pi/` subdirectory).
|
|
17
17
|
- P2 #10 — **PARTIALLY DONE**: ~57 one-off research/debug scripts moved to `scripts/experiments/`; production + tooling + bench-* + regen-* + parity-* + gen-model + eval/serve remain at `scripts/` root (the full bench/regen/build split was not applied).
|
|
18
18
|
- P2 #11 — **DONE**: CLI vocabulary reconciled.
|
|
@@ -0,0 +1,291 @@
|
|
|
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)
|
|
30
|
+
|
|
31
|
+
- **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
|
|
46
|
+
(`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
|
|
59
|
+
|
|
60
|
+
```
|
|
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
|
|
87
|
+
```
|
|
88
|
+
|
|
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 |
|
|
243
|
+
| --- | --- |
|
|
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
|
|
277
|
+
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?
|