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
@@ -1,18 +1,68 @@
1
- // mlx-bun memory — synthesis pipeline orchestrator. ⚠️ M1 STUB.
1
+ // mlx-bun memory — synthesis pipeline orchestrator (P6-T4).
2
2
  //
3
- // Ties the stages together: ingest → chunk → cluster → synthesize → wikify →
4
- // changelog (the lucien nightly chain, reimplemented on the local model). What
5
- // `mlx-bun memory synthesize` and the nightly launchd job invoke.
3
+ // Ties the stage modules into the cold-start DAG:
4
+ // ingest → SEGMENT → ENTITY-EXTRACT → ROUTE → CREATE → NORMALIZE → commitVault.
6
5
  //
7
- // CURRENTLY A NO-OP: it logs each stage as stubbed and writes nothing, so the
8
- // scheduled job runs cleanly tonight and every night without touching your
9
- // vault. When M1 lands, flip runSynthesis to actually call the stage modules
10
- // (each currently throws "not implemented") and move it onto the src/jobs/*
11
- // background runner with SSE progress, per the design doc.
6
+ // `ingest` + `SEGMENT` already populated the corpus (conversations / messages /
7
+ // chunks live in the MemoryStore), so for an already-chunked conversation
8
+ // SEGMENT is a no-op and the pipeline operates on the existing chunks. The model
9
+ // stages reuse the existing modules verbatim:
10
+ // - ENTITY-EXTRACT — entity.ts `extractEntities` (one chunk per call, persists
11
+ // chunk_entities + a candidate entities/aliases row via the variant resolver).
12
+ // - ROUTE — route.ts `RouteAccumulator` over the persisted chunk_entities
13
+ // (the deterministic canonical join + the B8/P1-T5 CREATE gate); the optional
14
+ // model substantive-engagement gate promotes thin-but-owned candidates.
15
+ // - CREATE/NORMALIZE/commit — synthesize.ts `synthesizeCreate` (sub-cluster →
16
+ // OUTLINE → per-section DRAFT → INFOBOX → assemble → NORMALIZE → gate → write
17
+ // + ledger), with a single `commitVault` at the end of the run.
12
18
  //
13
- // See docs/design/memory-system.md → "The nightly pipeline".
19
+ // `runSynthesis` keeps the M1 event/summary contract `src/cli.ts` drives; it now
20
+ // runs the real DAG over the default store + vault. The FULL-corpus bootstrap is
21
+ // USER-ACTION (P6-T5) — the agent never starts it; `scripts/experiments`
22
+ // drives a bounded handful of conversations through `runPipeline` directly.
14
23
 
15
- export type SynthesisStage = "ingest" | "chunk" | "cluster" | "synthesize" | "wikify" | "changelog";
24
+ import type { ChunkCall } from "./chunk";
25
+ import type { SectionCall } from "./cluster";
26
+ import { runLinkStage } from "./crosslink";
27
+ import { MemoryStore } from "./db";
28
+ import type { ExtractCall } from "./entity";
29
+ import {
30
+ buildEntityMeta,
31
+ chronoChunkIds,
32
+ runExtractStage,
33
+ runRouteStage,
34
+ runSegmentStage,
35
+ runSynthesizeStage,
36
+ selectCreateTargets,
37
+ type EntityMeta,
38
+ type PipelineCreated,
39
+ type PipelinePatched,
40
+ type RouteDecision,
41
+ } from "./stages";
42
+ import type { SynthesisCall } from "./synthesize";
43
+ import { vaultRoot } from "./vault";
44
+ import { wikifyVault } from "./wikify";
45
+
46
+ // Re-export the factored-out pure helpers + types so existing importers (tests,
47
+ // scripts) keep their `from "./pipeline"` entry points.
48
+ export { buildEntityMeta, selectCreateTargets };
49
+ export type { EntityMeta, PipelineCreated, PipelinePatched, RouteDecision };
50
+
51
+ // ---- stage / event contract (consumed by src/cli.ts) -----------------------
52
+
53
+ export type SynthesisStage =
54
+ | "ingest"
55
+ | "segment"
56
+ | "chunk"
57
+ | "extract"
58
+ | "route"
59
+ | "create"
60
+ | "section-route"
61
+ | "patch"
62
+ | "reconcile"
63
+ | "link"
64
+ | "wikify"
65
+ | "commit";
16
66
 
17
67
  export interface SynthesisEvent {
18
68
  type: "stage" | "log" | "done";
@@ -23,41 +73,203 @@ export interface SynthesisEvent {
23
73
  export interface SynthesisOptions {
24
74
  /** Only synthesize conversations newer than this (ISO date / conv cursor). */
25
75
  since?: string;
26
- /** Override the synthesis model (default e4b). */
76
+ /** Override the synthesis model (default e4b). Reserved — the stages resolve
77
+ * their own per-stage adapter via model.ts. */
27
78
  model?: string;
28
- /** Plan only — never write the vault. */
79
+ /** Plan only — never write the vault (deterministic, no model). */
29
80
  dryRun?: boolean;
30
81
  }
31
82
 
32
83
  export interface SynthesisSummary {
33
- /** False while stubbed; flip to true when the stages are implemented. */
34
84
  implemented: boolean;
35
85
  stages: SynthesisStage[];
36
86
  note: string;
37
87
  }
38
88
 
39
- const STAGES: SynthesisStage[] = ["ingest", "chunk", "cluster", "synthesize", "wikify", "changelog"];
89
+ const STAGES: SynthesisStage[] = [
90
+ "ingest",
91
+ "segment",
92
+ "extract",
93
+ "route",
94
+ "create",
95
+ "section-route",
96
+ "patch",
97
+ "link",
98
+ "wikify",
99
+ "commit",
100
+ ];
101
+
102
+ // ---- pipeline run ----------------------------------------------------------
103
+
104
+ export interface PipelineOptions {
105
+ /** Restrict the run to these conversation ids; default every conversation. */
106
+ convIds?: string[];
107
+ /** Vault root (honors MLX_BUN_WIKI); defaults to `vaultRoot()`. */
108
+ root?: string;
109
+ /** Max articles to CREATE this run (bounds a smoke's GPU cost). */
110
+ articleCap?: number;
111
+ /** Entities to force into the CREATE set when they earn a `create`. */
112
+ mustCreate?: string[];
113
+ /** Model-call seam for SEGMENT (the `chunk` stage — our e4b-chunk-300 /
114
+ * `memory-chunk` adapter). Defaults to `callLocal("chunk", …)` inside
115
+ * chunkConversations; tests inject a fake to avoid a real model load. */
116
+ segmentCall?: ChunkCall;
117
+ /** Model-call seam for the CREATE drafting AND the steady-state PATCH (both run
118
+ * the `synthesis` stage). Tests inject a fake. */
119
+ call?: SynthesisCall;
120
+ /** Model-call seam for ENTITY-EXTRACT (the `entity` stage). Tests inject a fake. */
121
+ extractCall?: ExtractCall;
122
+ /** Model-call seam for SECTION-ROUTE (the `section` stage binary/name calls).
123
+ * Tests inject a fake. */
124
+ sectionCall?: SectionCall;
125
+ /** Run the subject-engagement model gate to promote a thin (single-chunk)
126
+ * genuine-subject candidate to a `create` stub. Off by default (a recurring
127
+ * subject already creates). */
128
+ useSubjectGate?: boolean;
129
+ /** Skip the final git commit (tests). */
130
+ commit?: boolean;
131
+ now?: number;
132
+ onEvent?: (e: SynthesisEvent) => void;
133
+ }
134
+
135
+ export interface PipelineResult {
136
+ convs: number;
137
+ chunks: number;
138
+ /** Distinct resolved entities seen across the routed chunks. */
139
+ entities: number;
140
+ created: PipelineCreated[];
141
+ /** Sections of existing articles patched this run (the self-healing folds). */
142
+ patched: PipelinePatched[];
143
+ skippedByGate: string[];
144
+ /** Subjects captured (not yet articled) — still retrievable by search. */
145
+ captured: string[];
146
+ }
147
+
148
+ /**
149
+ * Run the cold-start synthesis DAG over a bounded set of conversations.
150
+ *
151
+ * This is now a thin ORCHESTRATOR: it drives the four INDEPENDENT, chronological,
152
+ * resumable stage workers (`stages.ts`) in sequence — SEGMENT → ENTITY-EXTRACT →
153
+ * ROUTE → SYNTHESIZE (CREATE + SECTION-ROUTE/PATCH) — so the full DAG behaves as
154
+ * before while each stage is also separately runnable (`mlx-bun memory
155
+ * segment|extract|route|synthesize`). Returns a structured report; SYNTHESIZE
156
+ * commits once at the end via `commitVault`.
157
+ */
158
+ export async function runPipeline(
159
+ store: MemoryStore,
160
+ opts: PipelineOptions = {},
161
+ ): Promise<PipelineResult> {
162
+ const root = opts.root ?? vaultRoot();
163
+ const cap = opts.articleCap ?? 20;
164
+ const emit = opts.onEvent ?? (() => {});
165
+
166
+ emit({ type: "stage", stage: "ingest", message: `ingest: corpus already in the store` });
167
+
168
+ // SEGMENT — (re)chunk every in-scope conversation whose chunked_at is NULL or
169
+ // stale, oldest-first, then proceed on the fresh chunks. A re-run is a no-op
170
+ // (watermark). We do NOT reuse any external chunks.
171
+ await runSegmentStage(store, { convIds: opts.convIds, call: opts.segmentCall, onEvent: opts.onEvent });
172
+
173
+ // Chronological run scope (oldest conversation first) for the result counts.
174
+ const { convs, chunkIds } = chronoChunkIds(store, opts.convIds);
175
+ emit({ type: "log", message: `operating on ${convs} conversation(s), ${chunkIds.length} chunk(s)` });
176
+
177
+ // ENTITY-EXTRACT — every not-yet-extracted chunk, chronological + resumable.
178
+ await runExtractStage(store, { convIds: opts.convIds, call: opts.extractCall, onEvent: opts.onEvent });
179
+
180
+ // ROUTE — deterministic create/capture over chunk_entities + CREATE gate;
181
+ // persists notable + captures thin subjects into _captured (still searchable)
182
+ // so SYNTHESIZE reads the decision back.
183
+ const route = await runRouteStage(store, {
184
+ convIds: opts.convIds,
185
+ useSubjectGate: opts.useSubjectGate,
186
+ onEvent: opts.onEvent,
187
+ });
188
+
189
+ // SYNTHESIZE — chronological CREATE (oldest entity first, oldest chunk first so
190
+ // the latest statement dominates) + SECTION-ROUTE → PATCH self-healing fold.
191
+ const synth = await runSynthesizeStage(store, {
192
+ root,
193
+ limit: cap,
194
+ convIds: opts.convIds,
195
+ mustCreate: opts.mustCreate,
196
+ call: opts.call,
197
+ sectionCall: opts.sectionCall,
198
+ commit: opts.commit,
199
+ now: opts.now,
200
+ onEvent: opts.onEvent,
201
+ });
202
+
203
+ // CROSS-LINK — build the EDGES synthesis (bounded to one article) cannot:
204
+ // inline-link first mentions of other articles + rebuild each ## See also from
205
+ // mentions + co-occurrence. Deterministic (no model) and idempotent.
206
+ await runLinkStage(store, { root, commit: opts.commit, onEvent: (e) => emit({ type: e.type, message: e.message }) });
207
+
208
+ emit({
209
+ type: "done",
210
+ message: `synthesis done — ${synth.created.length} created, ${synth.patched.length} patched, ${synth.skippedByGate.length} gated, ${route.captured.length} captured.`,
211
+ });
212
+
213
+ return {
214
+ convs,
215
+ chunks: chunkIds.length,
216
+ entities: route.decisions.length,
217
+ created: synth.created,
218
+ patched: synth.patched,
219
+ skippedByGate: synth.skippedByGate,
220
+ captured: route.captured,
221
+ };
222
+ }
223
+
224
+ // ---- cli entry: the M1 contract src/cli.ts drives --------------------------
40
225
 
41
226
  /**
42
- * Run the synthesis pipeline. STUB: walks the stage list, emits a `stage` event
43
- * for each (marked skipped), and returns implemented:false. Deterministic and
44
- * side-effect-free, so it's safe for the nightly job to call today.
227
+ * Run the synthesis pipeline over the default store + vault — the `mlx-bun
228
+ * memory synthesize` path. Keeps the {@link SynthesisSummary} shape the CLI
229
+ * prints. The FULL-corpus run is a long GPU job (USER-ACTION, P6-T5); this entry
230
+ * exists so the wiring is complete — the agent never starts it.
45
231
  */
46
232
  export async function runSynthesis(
47
233
  opts: SynthesisOptions = {},
48
234
  onEvent?: (e: SynthesisEvent) => void,
49
235
  ): Promise<SynthesisSummary> {
50
- onEvent?.({
51
- type: "log",
52
- message: `memory synthesis is not implemented yet (M1)${opts.dryRun ? " · dry-run" : ""} — running as a no-op, nothing will be written.`,
53
- });
54
- for (const stage of STAGES) {
55
- onEvent?.({ type: "stage", stage, message: `${stage}: stubbed (skipped)` });
236
+ if (opts.dryRun) {
237
+ onEvent?.({ type: "log", message: "dry-run — no model calls, nothing will be written." });
238
+ for (const stage of STAGES) onEvent?.({ type: "stage", stage, message: `${stage}: planned (dry-run)` });
239
+ onEvent?.({ type: "done", message: "dry-run complete — no articles written." });
240
+ return { implemented: true, stages: STAGES, note: "dry-run: DAG wired; no model calls made." };
241
+ }
242
+
243
+ const store = new MemoryStore();
244
+ try {
245
+ // WRITE branch: ingest → segment → extract → route → create → commit.
246
+ const result = await runPipeline(store, { onEvent });
247
+
248
+ // WIKIFY branch: the periodic editorial sweep over EVERY article (P8). Runs
249
+ // after the write branch so freshly-created and patched articles are tightened
250
+ // + infobox-refreshed in the same nightly pass; each article commits its own
251
+ // change (or NO-OPs) behind the same conservative gate. Best-effort: a wikify
252
+ // defect must not lose the create-branch work already committed above.
253
+ onEvent?.({ type: "stage", stage: "wikify", message: "wikify: editorial sweep over every article" });
254
+ let editedCount = 0;
255
+ try {
256
+ const sweep = await wikifyVault({ root: vaultRoot() });
257
+ editedCount = sweep.edited.length;
258
+ onEvent?.({ type: "log", message: ` wikify edited ${editedCount}/${sweep.results.length} article(s)` });
259
+ } catch (err) {
260
+ onEvent?.({ type: "log", message: ` wikify sweep skipped (error: ${String(err)})` });
261
+ }
262
+ onEvent?.({
263
+ type: "done",
264
+ message: `synthesis done — ${result.created.length} created, ${result.patched.length} patched, ${editedCount} wikified.`,
265
+ });
266
+
267
+ return {
268
+ implemented: true,
269
+ stages: STAGES,
270
+ note: `synthesis wired — ${result.created.length} created, ${result.patched.length} patched, ${editedCount} wikified from ${result.convs} conversations.`,
271
+ };
272
+ } finally {
273
+ store.close();
56
274
  }
57
- onEvent?.({ type: "done", message: "no articles were written (synthesis is stubbed)." });
58
- return {
59
- implemented: false,
60
- stages: STAGES,
61
- note: "M1 stub — pipeline + scheduling wired; stage modules not implemented.",
62
- };
63
275
  }
@@ -9,6 +9,11 @@
9
9
  //
10
10
  // See docs/design/memory-system.md → "Synthesis runs on the local model".
11
11
 
12
+ import { readFileSync } from "node:fs";
13
+ import { join } from "node:path";
14
+
15
+ import { vaultRoot } from "./vault";
16
+
12
17
  export const CHUNK_PROMPT = `TODO(M1): segment a conversation transcript into topic-coherent chunks.`;
13
18
 
14
19
  export const CLUSTER_PROMPT = `TODO(M1): assign a chunk to an existing bucket from Meta/Buckets.md, or propose a new bucket.`;
@@ -21,3 +26,40 @@ export const STAGE_PROMPTS: Record<string, string> = {
21
26
  cluster: CLUSTER_PROMPT,
22
27
  synthesize: SYNTHESIZE_PROMPT,
23
28
  };
29
+
30
+ // ---- meta-policy inlining (ported from lucien scripts/meta-inline.ts) ------
31
+ //
32
+ // Editorial policy lives in the vault's Meta/ pages, not in code. Rather than
33
+ // telling the model to Read those pages at synthesis time (which turns every
34
+ // pipeline call into an agent loop whose context accumulates — a 34k-char
35
+ // conversation was observed ballooning to a 105k-token prefill on the local
36
+ // server), we inline the requested pages directly into the prompt. Calls stay
37
+ // single-turn: one bounded prefill, one bounded generation. The policy is
38
+ // edit-in-the-vault: change a Meta page on disk and the next run reflects it
39
+ // with zero code change.
40
+
41
+ /**
42
+ * Read the named Meta policy pages from the vault's Meta/ dir and concatenate
43
+ * them for inlining into a stage prompt. Each `name` addresses
44
+ * `<vault>/Meta/<name>.md` (a trailing `.md` is accepted and normalized).
45
+ *
46
+ * The vault root honors the MLX_BUN_WIKI override (see {@link vaultRoot}).
47
+ *
48
+ * @throws {Error} If a requested Meta page does not exist on disk, naming it.
49
+ */
50
+ export function loadMetaPolicy(names: string[]): string {
51
+ const metaDir = join(vaultRoot(), "Meta");
52
+ const sections: string[] = [];
53
+ for (const name of names) {
54
+ const stem = name.trim().endsWith(".md") ? name.trim().slice(0, -3) : name.trim();
55
+ const path = join(metaDir, `${stem}.md`);
56
+ let text: string;
57
+ try {
58
+ text = readFileSync(path, "utf8");
59
+ } catch {
60
+ throw new Error(`Meta policy page not found: ${path}`);
61
+ }
62
+ sections.push(`--- Meta/${stem}.md ---\n\n${text.trim()}`);
63
+ }
64
+ return sections.join("\n\n");
65
+ }