mlx-bun 0.0.7 → 0.0.9
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-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/diffusion-gemma-port.md +602 -0
- 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/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/spec-decode-larger-targets.md +229 -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 +262 -0
- package/docs/investigations/dspark-handoff.md +58 -0
- package/docs/investigations/kernel-perf-review-2026-07.md +92 -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.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 +40 -9
- package/docs/reference/training.md +25 -11
- 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 +1029 -86
- package/src/config.ts +54 -3
- package/src/diffusion/diffusion-generate.ts +436 -0
- package/src/download.ts +99 -8
- package/src/embed.ts +77 -0
- package/src/eval/perplexity.ts +164 -0
- package/src/eval/runner.ts +19 -7
- package/src/eval/tasks/bfcl.ts +1 -1
- package/src/generate.ts +196 -19
- package/src/harness-pi.ts +43 -17
- package/src/index.ts +2 -0
- 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/ffi.ts +11 -0
- package/src/mlx/ops.ts +113 -0
- package/src/model/compiled-decode.ts +43 -8
- package/src/model/diffusion-gemma.ts +1026 -0
- package/src/model/factory.ts +35 -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 +62 -6
- package/src/model/gemma4.ts +33 -2
- package/src/model/minicpm5.ts +106 -8
- package/src/model/qwen3.ts +276 -0
- package/src/model/steel-linear-kernel.ts +96 -0
- package/src/model/support.ts +45 -7
- 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/quantize/config-writer.ts +1 -1
- package/src/registry.ts +215 -7
- package/src/responses.ts +23 -0
- package/src/sampler.ts +220 -9
- package/src/serve/batch-scheduler.ts +272 -80
- package/src/serve/generation-gateway.ts +90 -18
- package/src/server.ts +643 -50
- 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/status-page.html +1 -1
- package/src/tokenizer.ts +7 -0
- package/src/train/diffusion-lora.ts +216 -0
- package/src/train/flash-cce.ts +70 -15
- 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 +289 -30
- package/src/train/prefix-shared.ts +33 -5
- package/src/train/segmented.ts +67 -11
- package/src/train/trainer.ts +92 -28
- package/src/vision/diffusion-vision.ts +620 -0
- package/src/web/skills/memory/SKILL.md +40 -17
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# pi integration review — internal embeds + `harness pi` (2026-07-01)
|
|
2
|
+
|
|
3
|
+
Read-only audit of the pi surface: `mlx-bun pi` (src/pi-terminal.ts, in-process
|
|
4
|
+
InteractiveMode), the shared provider wiring (src/pi-provider.ts), the web
|
|
5
|
+
embed (src/pi-web.ts), `mlx-bun harness pi` (src/harness-pi.ts), and the docs
|
|
6
|
+
that describe them. Every claim below was verified in the current source
|
|
7
|
+
(post-8ee691d, working tree of 2026-07-01); the localhost/loopback behavior was
|
|
8
|
+
verified empirically with Bun on this machine.
|
|
9
|
+
|
|
10
|
+
Stale-memory corrections first (priors that are NO LONGER true):
|
|
11
|
+
- `harness pi` does **not** write `~/.pi/agent/models.json` — it generates a
|
|
12
|
+
self-contained extension at `~/.pi/agent/extensions/mlx-bun-provider.ts`
|
|
13
|
+
that calls `pi.registerProvider()` with live `/v1/models` discovery.
|
|
14
|
+
- Tool-call parsing is **not** Gemma-only anymore: src/tool-call.ts also parses
|
|
15
|
+
OpenAI-JSON `<tool_call>`, Qwen `<function=name><parameter=…>`, and MiniCPM5
|
|
16
|
+
`<function name=…><param name=…>` from decoded text (buffered-text mode).
|
|
17
|
+
- The old `by_bits`/bitsMap and double-BOS issues are fixed and stayed fixed.
|
|
18
|
+
|
|
19
|
+
## Ranked fix list
|
|
20
|
+
|
|
21
|
+
### 1. HIGH — Web chat advertises memory tools it filtered out
|
|
22
|
+
`src/pi-web.ts:568` passes `tools: [...WELCOME_TOOLS]` (= `["read","web_search"]`,
|
|
23
|
+
line 68) to `createAgentSessionFromServices`, but `src/pi-web.ts:534` appends
|
|
24
|
+
`surface.memoryHint` to the system prompt and `surface.skillPaths` includes the
|
|
25
|
+
bundled `memory` skill (surface built at :528 with memory auto-detect). pi
|
|
26
|
+
treats the `tools` list as an allowlist, so when a vault exists the prompt and
|
|
27
|
+
skill instruct the model to call `memory_resolve` / `memory_read` /
|
|
28
|
+
`memory_section` — tools pi filtered out of the session. Every such call fails.
|
|
29
|
+
The intent that they be reachable is visible at `src/pi-web.ts:57`
|
|
30
|
+
(`READ_ONLY_TOOLS` already auto-allows `MEMORY_TOOL_NAMES`/`REFERENCE_TOOL_NAMES`,
|
|
31
|
+
imported at :47). The terminal embed does this correctly
|
|
32
|
+
(`src/pi-terminal.ts:157` uses `surface.tools`).
|
|
33
|
+
**Fix (pi-web.ts:568):**
|
|
34
|
+
`tools: [...WELCOME_TOOLS, ...(surface.memoryEnabled ? [...MEMORY_TOOL_NAMES, ...REFERENCE_TOOL_NAMES] : [])]`
|
|
35
|
+
— or, if the 1B welcome assistant must stay two-tool, build the surface with
|
|
36
|
+
`memory: "off"` so the hint and skill disappear too. Add a pi-web test either
|
|
37
|
+
way (tests/pi-web.test.ts has no memory coverage today).
|
|
38
|
+
|
|
39
|
+
### 2. HIGH — `harness pi` maps EVERY /v1/models row to id `"local"` (duplicate models)
|
|
40
|
+
`/v1/models` now returns the served model FIRST **plus every other servable
|
|
41
|
+
registry model** (src/server.ts:1479–1499). Both consumers still assume the old
|
|
42
|
+
single-entry shape:
|
|
43
|
+
- `src/harness-pi.ts:76–81` (`fetchServerModels`) maps all rows;
|
|
44
|
+
- the generated extension (`renderPiExtension`, src/harness-pi.ts:116–125) and
|
|
45
|
+
the baked fallback (`modelEntry`, :140–151) register one pi model per row,
|
|
46
|
+
**all with id `local`** — N duplicate ids, N−1 of which name models the
|
|
47
|
+
single-model server is not actually serving.
|
|
48
|
+
Side effects: `installPiExtension`'s `bakedModels` and the CLI's
|
|
49
|
+
"server live · …" line print the whole registry; `src/cli.ts:1615`
|
|
50
|
+
("reusing running server at … (…)") lists every downloaded model as if served.
|
|
51
|
+
`tests/harness-pi.test.ts` stubs a single-row /v1/models, so this is untested.
|
|
52
|
+
**Fix:** in `fetchServerModels`, keep only the served entry — the first row is
|
|
53
|
+
the served model and is the only one carrying the capability extras
|
|
54
|
+
(`context_window`, `reasoning`); e.g. `body.data?.[0]` (or filter rows that
|
|
55
|
+
have `context_window`). Same filter inside the generated extension's live
|
|
56
|
+
fetch. Update cli.ts:1615/1668 to label `models[0]` as served. Extend the
|
|
57
|
+
harness test with a multi-row stub.
|
|
58
|
+
|
|
59
|
+
### 3. HIGH — Tier-0 generics (and plain Qwen3 chat) get Gemma's token sentinels
|
|
60
|
+
`toolStreamMode` (src/server.ts:1175–1183) returns `"gemma-sentinel"` for every
|
|
61
|
+
model that isn't MiniCPM5/Qwen3.5 — including the new universal-dense generics
|
|
62
|
+
(llama, qwen2, qwen3, gemma1/2, phi3, olmo2, glm4, granite, starcoder2,
|
|
63
|
+
smollm3 — src/model/universal/archs.ts, served via src/model/factory.ts:42–45)
|
|
64
|
+
and plain Qwen3. In that mode `ToolAwareStream.push` (src/server.ts:500–509)
|
|
65
|
+
unconditionally interprets token ids 48/49/100/101 as tool/channel sentinels.
|
|
66
|
+
Those ids are Gemma-4 special tokens (src/tool-call.ts:9–23); on any other
|
|
67
|
+
tokenizer they're ordinary low-id vocab entries, so a generic model emitting
|
|
68
|
+
one silently reroutes all subsequent output into a phantom tool segment or
|
|
69
|
+
"reasoning". It also means generics never reach `parseGeneratedToolCalls`
|
|
70
|
+
(buffered-text), so tool calling is broken for them — and pi always sends
|
|
71
|
+
tools, and `mlx-bun pi -q <generic>` is a supported path now.
|
|
72
|
+
**Fix (src/server.ts:1180–1182):** make gemma-sentinel the *family-gated*
|
|
73
|
+
branch, not the default:
|
|
74
|
+
```ts
|
|
75
|
+
if (ctx.model.config.modelType.startsWith("gemma4")) return "gemma-sentinel";
|
|
76
|
+
return tools?.length ? "buffered-text" : "plain";
|
|
77
|
+
```
|
|
78
|
+
(`ctx.template.thinkingFormat === "gemma-channel"` is an alternative key, but
|
|
79
|
+
DiffusionGemma forces it null while still being gemma-family, so prefer
|
|
80
|
+
modelType.) Buffered-text already handles the OpenAI-JSON `<tool_call>` shape
|
|
81
|
+
most generic templates emit; models whose format isn't covered fail soft
|
|
82
|
+
(markup stays in content) instead of corrupting the stream.
|
|
83
|
+
|
|
84
|
+
### 4. MEDIUM — vision capability never reaches the pi terminal or external pi
|
|
85
|
+
`/v1/models` doesn't advertise vision (src/server.ts:1479–1485: only
|
|
86
|
+
`context_window` + `reasoning`), `RunEmbeddedPiOptions` has no `vision` field
|
|
87
|
+
and `src/cli.ts:1670` passes none, so `mlx-bun pi` on gemma-4-e4b — the
|
|
88
|
+
*default* model, vision-capable — registers `input: ["text"]`
|
|
89
|
+
(src/pi-provider.ts:100) and images are unusable in the TUI. The harness
|
|
90
|
+
extension is hardcoded `input: ["text"]` too (src/harness-pi.ts:121, :146).
|
|
91
|
+
Only pi-web wires it (src/pi-web.ts:491–495, from `ctx.vision || ctx.loadVision`
|
|
92
|
+
via createServer). The `input` flag MUST match the real model (pi-ai
|
|
93
|
+
content-parts vs string serialization — pi-provider.ts:44–49), so this needs
|
|
94
|
+
the real signal, not a default.
|
|
95
|
+
**Fix:** add `vision: !!(ctx.vision || ctx.loadVision)` to the served entry in
|
|
96
|
+
/v1/models; carry it through `ServerModel`/`fetchServerModels`,
|
|
97
|
+
`renderPiExtension`/`modelEntry` (`input: m.vision ? ["text","image"] : ["text"]`),
|
|
98
|
+
`RunEmbeddedPiOptions`, and the cli `pi` case.
|
|
99
|
+
|
|
100
|
+
### 5. MEDIUM — harness extension drifted from buildPiProvider on compat flags
|
|
101
|
+
The internal provider sets `compat: { supportsDeveloperRole: false, … }`
|
|
102
|
+
(src/pi-provider.ts:89–92); the generated extension sets only `thinkingFormat`
|
|
103
|
+
for reasoning models (src/harness-pi.ts:120, :145). Currently masked because
|
|
104
|
+
the server remaps `developer` → `system` (src/server.ts:743–760), but the two
|
|
105
|
+
paths are documented as "kept aligned" (pi-provider.ts:20–22, harness-pi.ts
|
|
106
|
+
header) and have drifted. **Fix:** emit the same
|
|
107
|
+
`supportsDeveloperRole: false` block in `renderPiExtension`/`modelEntry`.
|
|
108
|
+
|
|
109
|
+
### 6. MEDIUM — `memory_status` tells the built-in agent synthesis doesn't exist
|
|
110
|
+
`src/memory/tools.ts:571–572` hardcodes
|
|
111
|
+
`"last synthesis: not available yet (M1 synthesis is stubbed)"` and
|
|
112
|
+
`"synthesis: M1 stub (manual/nightly writes are not implemented yet)"`.
|
|
113
|
+
Synthesis is live: `mlx-bun memory synthesize` runs the full DAG
|
|
114
|
+
(src/cli.ts:2508ff) and the nightly launchd job runs it
|
|
115
|
+
(src/memory/schedule.ts:66–72). The pi session will confidently tell the user
|
|
116
|
+
the feature doesn't exist. **Fix:** drop the stub lines; report real state
|
|
117
|
+
(e.g. last synthesis run from the synthesis DB / vault git log, and
|
|
118
|
+
"run: mlx-bun memory synthesize").
|
|
119
|
+
|
|
120
|
+
### 7. MEDIUM — docs/reference/memory.md contradicts itself
|
|
121
|
+
Line 9: "Synthesis is scaffolded but still a no-op M1 stub" vs line 41
|
|
122
|
+
("**`memory synthesize` writes to your vault.** It runs the full local
|
|
123
|
+
synthesis DAG …") and line 77 ("Synthesis is live today"). Fix line 9 (and the
|
|
124
|
+
line-53 "last-synthesis placeholder" wording once #6 lands).
|
|
125
|
+
|
|
126
|
+
### 8. LOW — server-api.md pi section omits `harness pi`
|
|
127
|
+
docs/reference/server-api.md:462–477 documents only the manual
|
|
128
|
+
`~/.pi/agent/models.json` route (which also bakes a concrete model id — the
|
|
129
|
+
staleness problem the `local` id exists to solve). Add: the supported
|
|
130
|
+
one-command path is `mlx-bun harness pi` (extension + live discovery +
|
|
131
|
+
`--remove` undo), manual models.json as the fallback.
|
|
132
|
+
|
|
133
|
+
### 9. LOW — three base-URL spellings for one loopback server
|
|
134
|
+
pi-web uses `http://127.0.0.1:${port}/v1` (src/pi-web.ts:487); the cli `pi`
|
|
135
|
+
probe/provider uses `http://localhost:${port}/v1` (src/cli.ts:1610, also the
|
|
136
|
+
serve preflight at :1215); harness `DEFAULT_BASE_URL` is
|
|
137
|
+
`http://localhost:8080/v1` (src/harness-pi.ts:25). Functionally coherent today
|
|
138
|
+
— verified empirically: Bun's fetch to `localhost` reaches both a
|
|
139
|
+
127.0.0.1-bound and a ::1-bound server — and the port sweep from 8ee691d is
|
|
140
|
+
clean (no live `8090` outside historical artifacts). But the harness extension
|
|
141
|
+
runs under the *user's pi* (node/jiti, not Bun), so standardize on
|
|
142
|
+
`127.0.0.1` (the actual default bind) everywhere to be resolver-proof.
|
|
143
|
+
|
|
144
|
+
### 10. LOW — `mlx-bun pi` arg edges (flag-strip itself is clean)
|
|
145
|
+
Verified complete: `OURS_VAL`/`OURS_BOOL` (src/cli.ts:1575–1582) cover every
|
|
146
|
+
flag read by `serverRuntimeFlags`/`applyDecodeRoute`/`mountStartupAdapter`/
|
|
147
|
+
`resolveModelAuto` (cross-checked flag-by-flag); `opt()` only supports
|
|
148
|
+
space-separated values repo-wide so there's no `--flag=value` bypass; the
|
|
149
|
+
"ignoring …" notice on server reuse works; `harness pi` passes nothing through
|
|
150
|
+
to pi (no leak path). Two edges remain:
|
|
151
|
+
- `parsePiArgs` (src/pi-terminal.ts:237–242) drops an unknown flag but keeps
|
|
152
|
+
its **value** as message text: `mlx-bun pi --resume abc123` starts a chat
|
|
153
|
+
with the message "abc123". Either swallow the next token for unknown
|
|
154
|
+
`--flags` or warn that the flag was ignored.
|
|
155
|
+
- `RunEmbeddedPiOptions.verbose` (src/pi-terminal.ts:97) is never settable —
|
|
156
|
+
parsePiArgs doesn't recognize `--verbose`. Wire it or delete it.
|
|
157
|
+
|
|
158
|
+
## Verified-good (no action)
|
|
159
|
+
- **Compat flags per model**: `buildPiProvider` derives `reasoning` →
|
|
160
|
+
`thinkingFormat: "qwen-chat-template"` from `ctx.template.supportsThinking`,
|
|
161
|
+
which is detected generically from the template source (`enable_thinking` +
|
|
162
|
+
`<think>`/`<|channel>`, src/chat-template.ts:143–159) — covers Gemma,
|
|
163
|
+
MiniCPM5, Qwen3.5, and Tier-0 generics with think-tag templates alike; the
|
|
164
|
+
server honors `chat_template_kwargs.enable_thinking` and `reasoning_effort`
|
|
165
|
+
with one precedence function (src/server.ts:1101–1108).
|
|
166
|
+
- **Wire contract**: pi's OpenAI `tool_calls` shape is produced for both the
|
|
167
|
+
sentinel and buffered-text parsers (src/server.ts:548–575, 1916–1923,
|
|
168
|
+
1989–2003), `finish_reason: "tool_calls"` included; `developer` role remapped.
|
|
169
|
+
- **Isolation**: both embeds use in-memory auth/registry (no ~/.pi cross-talk),
|
|
170
|
+
own agent dirs (`~/.mlx-bun/pi`, `~/.mlx-bun/pi-sessions`), `noExtensions/
|
|
171
|
+
noSkills/noPromptTemplates/noThemes`, curated skills only.
|
|
172
|
+
- **Server ownership**: `pi`-started servers are `owner: "pi-session"` and the
|
|
173
|
+
reuse path warns when attaching to another session's server (cli.ts:1624–1631).
|
|
174
|
+
- **Memory read tools vs current vault code**: all imports
|
|
175
|
+
(vault/query/article/schedule) exist and match; terminal surface exposes them
|
|
176
|
+
correctly; FIND→READ contract intact. Only the status text (#6) is stale.
|
|
177
|
+
- **contextWindow fallbacks**: missing `max_position_embeddings` degrades to
|
|
178
|
+
32768 at every consumer (`?? 32768` / `?? DEFAULT_CONTEXT_WINDOW`).
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# v0.0.9 release notes (DRAFT — Josh edits + publishes)
|
|
2
|
+
|
|
3
|
+
Everything since v0.0.8 (2026-06-24). The big themes: the mlx-lm drop-in
|
|
4
|
+
surface is now materially complete, ORPO is paper-faithful, mlx-bun runs its
|
|
5
|
+
first generically-supported architectures at verified bit-exact parity, and an
|
|
6
|
+
adversarial review swept the whole engine — fixing 12 confirmed bugs and
|
|
7
|
+
overturning a load-bearing performance assumption.
|
|
8
|
+
|
|
9
|
+
## Drop-in compatibility with mlx_lm.server (the headline)
|
|
10
|
+
|
|
11
|
+
- **Endpoints:** `POST /v1/completions` (raw text completion, stream +
|
|
12
|
+
non-stream), `GET /health` (byte-exact body), `GET /v1/models` now lists all
|
|
13
|
+
registry models (+ `/v1/models/<id>`).
|
|
14
|
+
- **Request fields:** `min_p`, `xtc_probability`/`xtc_threshold`, `logit_bias`,
|
|
15
|
+
`presence_penalty`/`frequency_penalty` (+ `*_context_size` windows),
|
|
16
|
+
`logprobs`/`top_logprobs` with mlx-lm's EXACT semantics (same distribution,
|
|
17
|
+
same response shape, same [0,11] validation). All L1-faithful ports of
|
|
18
|
+
`sample_utils.py`.
|
|
19
|
+
- **Flags/defaults:** port 8080 + loopback host (matching mlx_lm.server;
|
|
20
|
+
`--host 0.0.0.0` = LAN opt-in), `--temp` alias, `--max-tokens`,
|
|
21
|
+
serve `--adapter <dir>` mounts at startup.
|
|
22
|
+
- **New CLI verbs:** `fuse` (fold LoRA into base — mlx-lm math, untouched
|
|
23
|
+
modules bit-identical), `convert` (wraps the mixed-precision quantizer,
|
|
24
|
+
`--target-bpw` exposed), `perplexity` (mlx_lm.perplexity methodology),
|
|
25
|
+
`upload` (native HF push).
|
|
26
|
+
- Deliberately not ported: `role_mapping` (unreachable — every supported model
|
|
27
|
+
ships a chat template). Remaining: `cache_prompt`, `evaluate` (planned as an
|
|
28
|
+
lm-eval shim), `--draft-model`, awq/dwq/gptq (plan:
|
|
29
|
+
docs/design/mlx-lm-tool-parity-plan.md).
|
|
30
|
+
|
|
31
|
+
## ORPO: paper-faithful by default
|
|
32
|
+
|
|
33
|
+
- **`sft_scope: full | response`** (default `full`): the chosen-NLL is now the
|
|
34
|
+
token-mean CE over the full prompt+response — matching the ORPO paper, TRL,
|
|
35
|
+
and xfactlab/orpo — implemented across every path (naive/chunked/fused/
|
|
36
|
+
flash-CCE/prefix-shared/segmented). `response` reproduces old runs
|
|
37
|
+
BIT-EXACTLY (regression-pinned). The odds-ratio terms stay response-only in
|
|
38
|
+
both modes (matches TRL). CLI: `mlx-bun train --sft-scope`.
|
|
39
|
+
- The rest of the ORPO stack was adversarially verified correct against
|
|
40
|
+
primary sources (odds-ratio math, gradients vs finite differences,
|
|
41
|
+
prefix-sharing gradient flow, tokenization/label-shift).
|
|
42
|
+
|
|
43
|
+
## Generic model support (Tier-0)
|
|
44
|
+
|
|
45
|
+
- **New:** `src/model/universal/` — a config-driven UniversalDense module with
|
|
46
|
+
explicit descriptors for llama, qwen2, qwen3, gemma, gemma2, phi3, olmo2,
|
|
47
|
+
glm4, granite, starcoder2, smollm3 (+ mlx-lm's remapping: mistral etc.).
|
|
48
|
+
bf16 (unquantized) checkpoints now load. Rope factory (llama3/yarn/longrope)
|
|
49
|
+
verified bit-exact vs the oracle.
|
|
50
|
+
- **First three archs verified at L1 parity** (bit-exact per-step logits):
|
|
51
|
+
Llama-3.2-1B-Instruct-4bit, Qwen2.5-0.5B-Instruct-4bit, gemma-2-2b-it-4bit.
|
|
52
|
+
- Dispatch ladder: dedicated → generated → generic → reject-with-helpful-error.
|
|
53
|
+
Targeted models keep their optimized paths; a model can graduate.
|
|
54
|
+
Design: docs/design/generic-model-support.md.
|
|
55
|
+
|
|
56
|
+
## Fixed (the adversarial-review wave)
|
|
57
|
+
|
|
58
|
+
- **`--l2` tier contract restored:** the L2 preset was enabling an
|
|
59
|
+
envelope-gated perf kernel (not bit-exact vs optiq). Bare `--l2` is now
|
|
60
|
+
genuinely optiq-bit-exact; the perf kernel is `--l3`/explicit opt-in.
|
|
61
|
+
`generate --l2/--l3` no longer silently degrades to L1.
|
|
62
|
+
- Batched same-millisecond seed collision (identical concurrent completions);
|
|
63
|
+
streaming per-layer KV-quant conversion (the missing half of optiq's
|
|
64
|
+
tight-RAM fix); segmented compiled-decode mid-step failure KV corruption;
|
|
65
|
+
`MLX_BUN_TRAIN_ATTN=flash` now refused on Gemma (unrevalidated crash path);
|
|
66
|
+
eval-loss error swallowing; wrong tokens_per_sec metric; silent steel-kernel
|
|
67
|
+
fallbacks now warn; flash-attention tests extended (D=256, sliding-window,
|
|
68
|
+
non-tile T).
|
|
69
|
+
- Memory (the Dreaming): batching default flipped to serial (measured 1.7–1.9×
|
|
70
|
+
faster for the real workload); `memory status` truth; third-person voice fix
|
|
71
|
+
merged.
|
|
72
|
+
- CLI audit (14 findings): `fit --ctx` help matched code, `embed` auto-pick
|
|
73
|
+
fixed, pi flag leakage into first message fixed, `--l1/2/3` + `generate` +
|
|
74
|
+
`train-watch` documented, `setup` is a real alias, doc lies corrected.
|
|
75
|
+
|
|
76
|
+
## Performance truth (research, groundwork for the next release)
|
|
77
|
+
|
|
78
|
+
- The "decode is at the memory floor" claim was **overturned** with measured
|
|
79
|
+
rooflines: only the 12B is at the bandwidth wall (~92%); CPM5/e4b/26B sit at
|
|
80
|
+
58–70% with the host-side graph build as the top recoverable term. Ranked
|
|
81
|
+
fix plan in-repo; wins land next release.
|
|
82
|
+
- The batching engine was audited (numerics solid, engine naive) — hotfixes +
|
|
83
|
+
containment landing; plan: docs/design/batching-v2-plan.md.
|
|
84
|
+
- Registry duplicates root-caused (snapshot-per-commit accumulation);
|
|
85
|
+
dedupe + `gc` landing.
|
|
86
|
+
|
|
87
|
+
## Infra
|
|
88
|
+
|
|
89
|
+
- CI gate added (typecheck + model-free tests on push/PR) — previously nothing
|
|
90
|
+
ran on push.
|
|
91
|
+
- Repo hygiene: scratch artifacts untracked, megakernel research relocated,
|
|
92
|
+
STATUS.md rewritten as a true current-state front door, doc map regenerated.
|
|
93
|
+
|
|
94
|
+
## Late additions (landed after the first draft)
|
|
95
|
+
|
|
96
|
+
- **Batching engine v2 (steps 1–3):** Qwen3.5/SSM models no longer fail under
|
|
97
|
+
`--batch` (capability gate + serial drain); per-row failure containment; no
|
|
98
|
+
serial-lane starvation; pipelined batched decode + sane cache cadence; the
|
|
99
|
+
huge admit transient fixed; all GPU work under one lock.
|
|
100
|
+
- **Model management:** `ls` deduplicates to one row per repo (snapshot
|
|
101
|
+
history via `ls --all-revisions`); new `mlx-bun gc` reclaims superseded
|
|
102
|
+
snapshots (24.7 GB found on the dev machine) with a safety guard for
|
|
103
|
+
unique files; `get` accepts substring queries; download lockfile, streamed
|
|
104
|
+
resume verification, gated-repo auth hints; vision capability detected from
|
|
105
|
+
config (the unified-vision 12B now shows `vision`). New doc:
|
|
106
|
+
docs/reference/models.md.
|
|
107
|
+
- **Pi integration:** web-chat memory tools actually callable; `harness pi`
|
|
108
|
+
registers exactly the served model; tool-calling fixed for Tier-0 generic
|
|
109
|
+
models (Gemma sentinels no longer applied to non-Gemma tokenizers); vision
|
|
110
|
+
advertised to pi; `mlx-bun pi` no longer leaks unknown flags into the first
|
|
111
|
+
message.
|
|
112
|
+
- **Website + README:** six-goals restructure, drop-in guide, memory guide,
|
|
113
|
+
the lab page, full CLI reference from a tracked source; deploy now triggers
|
|
114
|
+
on reference-doc edits. NOTE the release sequence: push and publish in the
|
|
115
|
+
same sitting — the site deploys from the push.
|
|
116
|
+
- **Test infra:** batched-serving goldens are machine-keyed (the M1-Max
|
|
117
|
+
"failures" were M4-Pro fixtures; all code exonerated — mlx-lm reproduces
|
|
118
|
+
mlx-bun token-for-token per machine); CI gate (typecheck + model-free tests).
|
|
119
|
+
- **Research (the lab):** decode-roofline re-measurement overturned the "at
|
|
120
|
+
the floor" assumption (docs/investigations/decode-roofline-lookagain.md);
|
|
121
|
+
curve-sampler distinctness theorem + witness numbers + preregistered
|
|
122
|
+
protocol (docs/planning/curve-sampler-research-plan.md).
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# Web UI pass — ranked fix list (2026-07-01 audit)
|
|
2
|
+
|
|
3
|
+
Read-only audit of every web surface against the current server contract:
|
|
4
|
+
`src/web/app.html` (unified SPA: chat / quantize / finetune / dataset /
|
|
5
|
+
status+library / routes), `src/assets/curve-designer.html` (/curves),
|
|
6
|
+
`src/pi-web.ts` (WS chat bridge), `src/server.ts` routes, plus the dead
|
|
7
|
+
`src/status-page.html`. Method: diffed every fetch/WS consumer in the pages
|
|
8
|
+
against the actual handler shapes in `server.ts`, `src/jobs/types.ts`,
|
|
9
|
+
`src/train/{job,trainer}.ts`, `src/quantize/job.ts`, `src/download.ts`,
|
|
10
|
+
`src/registry.ts`, `src/model/support.ts`.
|
|
11
|
+
|
|
12
|
+
**Live click-through was skipped deliberately**: GPU-loading test suites
|
|
13
|
+
(`bun test tests/server.test.ts …`, loads MiniCPM5) were running for the whole
|
|
14
|
+
audit window, and starting a second model server would have contended with
|
|
15
|
+
them. Every finding below is source-verified on both sides of the contract.
|
|
16
|
+
|
|
17
|
+
Port sweep: no stale `8090` or hardcoded URLs remain in any html/js.
|
|
18
|
+
`curve-designer.html:92` falls back to `http://localhost:8080` (matches the
|
|
19
|
+
new default) and uses `location.origin` when served at `/curves`. ✓
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## WRONG (bugs — fix first)
|
|
24
|
+
|
|
25
|
+
1. **Fine-tune live stats never show LR or tok/s — metric field-name mismatch.**
|
|
26
|
+
`src/web/app.html:2317-2318` reads `e.lr` / `e.tps`, but the trainer emits
|
|
27
|
+
`learning_rate` / `tokens_per_sec` (`src/train/trainer.ts:559-560`, same at
|
|
28
|
+
:724-727 and :966-969; contract comment in `src/jobs/types.ts:23`). The
|
|
29
|
+
"learning rate" and "tok/s" chart stats stay `—` for the entire run.
|
|
30
|
+
Also update the protocol doc-comment at `app.html:1089` (`lr?`, `tps?`).
|
|
31
|
+
**Fix:** read `e.learning_rate` and `e.tokens_per_sec`.
|
|
32
|
+
|
|
33
|
+
2. **Chat queue bar renders arrays as strings and empty tags.**
|
|
34
|
+
`pi-web.ts:183` sends `queue_update` with `steering: readonly string[]` and
|
|
35
|
+
`followUp: readonly string[]` (arrays). `app.html:1683-1684` does
|
|
36
|
+
`if (q.steering) … esc(String(q.steering))` — an empty array is truthy, so
|
|
37
|
+
every `queue_update` renders empty `steering:`/`queued:` pills, and multiple
|
|
38
|
+
queued messages render comma-joined.
|
|
39
|
+
**Fix:** guard with `.length` and render `q.followUp.map(...)` (or
|
|
40
|
+
`q.steering[q.steering.length-1]`).
|
|
41
|
+
|
|
42
|
+
3. **Library table loads exactly once per app lifetime — new quants never appear.**
|
|
43
|
+
`app.html:2603` gates `loadLibrary()`/`loadFit()` behind `fitLoaded`, which
|
|
44
|
+
is never reset. The server explicitly invalidates its 30 s `/library` cache
|
|
45
|
+
when a quantize job completes so "the next poll rescans and shows it"
|
|
46
|
+
(`src/server.ts:2510-2513`) — but the UI never re-polls. A model quantized,
|
|
47
|
+
downloaded, or gc'd mid-session never shows until a full page reload.
|
|
48
|
+
**Fix:** refresh the library on its own interval (e.g. every 15 s inside
|
|
49
|
+
`tick()`), and force one refresh when a quantize job reaches `done`.
|
|
50
|
+
|
|
51
|
+
4. **Adapter mount failure toasts "[object Object]".**
|
|
52
|
+
`POST /v1/adapters` errors use the OpenAI envelope `{error:{message}}`
|
|
53
|
+
(`src/server.ts:1600`), and the `api()` helper (`app.html:1080`) keeps
|
|
54
|
+
`data.error` as that object; `onSelectAdapter` (`app.html:1908-1909`) then
|
|
55
|
+
string-concatenates it. Any mount failure reads
|
|
56
|
+
`adapter: [object Object]`.
|
|
57
|
+
**Fix:** in `api()`, unwrap
|
|
58
|
+
`data.error && typeof data.error === "object" ? data.error.message : …`.
|
|
59
|
+
|
|
60
|
+
5. **Quantize inspect result parsing targets a response shape the server never sends.**
|
|
61
|
+
`inspectModel` returns `support: boolean` (`src/quantize/job.ts:101`), but
|
|
62
|
+
`app.html:2135-2140` checks `d.support === "supported" | "ok" | "untested"`
|
|
63
|
+
(all dead branches) and, when `support` is false, prints
|
|
64
|
+
`esc(d.support || "unknown architecture")` → always "unknown architecture"
|
|
65
|
+
even when `arch` was resolved. `size_gb` is also rendered raw (unrounded
|
|
66
|
+
float, and `0` when a direct path bypasses the registry).
|
|
67
|
+
**Fix:** treat `support` as boolean; show `arch` + "not quantizable" on
|
|
68
|
+
false; `(+d.size_gb).toFixed(2)` and hide when 0.
|
|
69
|
+
|
|
70
|
+
6. **Stale vision claim in the chat attach path.**
|
|
71
|
+
`app.html:1848`: "This model can't see images — switch to the **12B** for
|
|
72
|
+
vision." SigLIP landed: e2b/e4b/26B/31B are vision-capable too
|
|
73
|
+
(`src/registry.ts:213-219` `visionCapable`), so the 12B-only advice is
|
|
74
|
+
wrong.
|
|
75
|
+
**Fix:** model-agnostic copy: "This model can't see images — serve a
|
|
76
|
+
vision-capable model (Gemma vision builds show a *vision* tag in the
|
|
77
|
+
Library)."
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## MISSING (current capabilities the UI doesn't expose)
|
|
82
|
+
|
|
83
|
+
7. **`sft_scope` absent from the fine-tune form.**
|
|
84
|
+
The finetune job accepts `sft_scope: "full" | "response"` and validates it
|
|
85
|
+
at submit (`src/train/job.ts:54,74-76`); default is now `"full"`
|
|
86
|
+
(paper/TRL-faithful, commit d32fe32). The ORPO panel
|
|
87
|
+
(`app.html:791-800`) has λ + schedule but no scope control, and
|
|
88
|
+
`collectHP()` (`app.html:2276-2289`) never sends it.
|
|
89
|
+
**Fix:** add a segmented control (full · response) to `#f-orpo-extra` with
|
|
90
|
+
a hint ("full = paper/TRL chosen-NLL over the whole sequence; response =
|
|
91
|
+
pre-2026-07 response-only"), include `sft_scope` in `collectHP()` when
|
|
92
|
+
method === "orpo".
|
|
93
|
+
|
|
94
|
+
8. **`min_p` / `xtc_probability` / `xtc_threshold` / `logprobs` not in any request-builder UI.**
|
|
95
|
+
Server supports them per-request (`src/server.ts:280-304`, wired at
|
|
96
|
+
:1138-1141). The chat sampling popover (`app.html:592-613`) only has
|
|
97
|
+
temperature/top_p/top_k — and the whole chain is width-limited: the
|
|
98
|
+
`set_sampling` WS frame (`pi-web.ts:160`), `SamplingOverrides`
|
|
99
|
+
(`pi-web.ts:408-412`), and `injectSampling` (`pi-web.ts:420-440`) all only
|
|
100
|
+
carry those three fields.
|
|
101
|
+
**Fix (scoped):** add `min_p` (slider 0–0.5) end-to-end: popover row →
|
|
102
|
+
`set_sampling` frame → `SamplingOverrides.min_p` → `injectSampling`. XTC and
|
|
103
|
+
logprobs are power-user/API features; document rather than add sliders.
|
|
104
|
+
|
|
105
|
+
9. **No gc/reclaim story in Library or Downloads.**
|
|
106
|
+
`mlx-bun gc` exists (CLI `src/cli.ts:1100`; planner
|
|
107
|
+
`src/registry.ts:375-455` `planGc`/`executeGc`) and the download path
|
|
108
|
+
prints "gc reclaims ~X GB" (`cli.ts:1015-1018`) — but the web Library shows
|
|
109
|
+
superseded revisions' disk cost with no affordance. **Needs a new server
|
|
110
|
+
endpoint first** (e.g. `GET /api/gc/plan`, `POST /api/gc/execute {yes}`),
|
|
111
|
+
then a Library footer line: "N superseded snapshots · reclaim X GB —
|
|
112
|
+
[Clean up]". (Separate from the listCanonical wiring below, but pairs
|
|
113
|
+
naturally with it: dedupe hides the stale revisions, gc deletes them.)
|
|
114
|
+
|
|
115
|
+
10. **Other accepted-but-hidden finetune/quantize knobs** (lower priority):
|
|
116
|
+
`grad_accumulation_steps`, `warm_start_adapter`, adapter naming (submit
|
|
117
|
+
always writes `adapter-<timestamp>`, `src/server.ts:2529-2530`) on the
|
|
118
|
+
finetune form; `reference`, `calibration_mix`, `n_calibration`
|
|
119
|
+
(`src/server.ts:2460-2461`) on the mixed-bpw quantize step. Add an
|
|
120
|
+
"Advanced" disclosure rather than more top-level fields.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## BLOCKED on the queued server.ts listCanonical / visionCapable / tier wiring
|
|
125
|
+
|
|
126
|
+
The functions exist and are tested (`Registry.listCanonical`
|
|
127
|
+
`src/registry.ts:181`, `visionCapable` :217, `supportTier`
|
|
128
|
+
`src/model/support.ts:53`) but the server still serves the old view. UI
|
|
129
|
+
consumption points to touch when the wiring lands:
|
|
130
|
+
|
|
131
|
+
11. **Revision duplicates in /library and /v1/models.**
|
|
132
|
+
`src/server.ts:1263` (`/library`) and :1491 (`/v1/models`) iterate
|
|
133
|
+
`reg.list()` — one row per cached snapshot revision. Both should use
|
|
134
|
+
`reg.listCanonical()`. UI needs no change; the Library table and every
|
|
135
|
+
/v1/models-driven picker (external pi, CLI) currently show dupes.
|
|
136
|
+
|
|
137
|
+
12. **`vision` flag understates capability.**
|
|
138
|
+
`src/server.ts:1278` sends `vision: m.hasVisionSidecar`, which misses the
|
|
139
|
+
12B's encoder-free `gemma4_unified_vision` (config-declared, no sidecar
|
|
140
|
+
file). Should be `visionCapable(m)`. UI consumption at `app.html:2663`
|
|
141
|
+
then needs no change.
|
|
142
|
+
|
|
143
|
+
13. **`supported` is a boolean; tiers are invisible.**
|
|
144
|
+
`src/server.ts:1264` sends `supported: isSupportedModelRecord(...)`.
|
|
145
|
+
Generic Tier-0 models (universal-dense descriptors) now count as
|
|
146
|
+
supported — correct — but the Library can't distinguish "targeted
|
|
147
|
+
(L2/L3 paths)" from "generic (L1 monolith only)". When the server sends
|
|
148
|
+
`tier: "targeted" | "generic" | null` (from `supportTier`), update:
|
|
149
|
+
- `app.html:2659` status cell: keep `unsupported (model_type)` for null;
|
|
150
|
+
add a dim "generic" badge for Tier-0 rows.
|
|
151
|
+
- Keep `supported` as a boolean alongside `tier` for one release so the
|
|
152
|
+
UI change can land independently.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## CONFUSING / polish
|
|
157
|
+
|
|
158
|
+
14. **Sampling popover "recommended" values are hardcoded to MiniCPM5.**
|
|
159
|
+
`app.html:1824-1825` (`SAMP_REC {topP:.95, topK:0}`, `recTemp()` 0.9/0.7)
|
|
160
|
+
claims to "mirror server toOptions defaultTemp", but the server resolves
|
|
161
|
+
defaults from each model's `generation_config.json`
|
|
162
|
+
(`src/server.ts:1120-1132`). For any other served model the dimmed "auto"
|
|
163
|
+
readouts lie. Fix: surface `genDefaults` (e.g. on the `ready` frame or
|
|
164
|
+
`/v1/models` extras) and seed the popover from it.
|
|
165
|
+
|
|
166
|
+
15. **Adapter dropdown goes stale after training.**
|
|
167
|
+
`refreshAdapters()` runs only at chat init and on `ready` frames
|
|
168
|
+
(`app.html:1789-1792, 2037`). An adapter trained in the Fine-tune tab
|
|
169
|
+
doesn't appear until reconnect/reload. Refresh on finetune `done` and on
|
|
170
|
+
chat `enter()`. Also escape the interpolation at `app.html:1897-1898`
|
|
171
|
+
(`a.id`/`a.path` go into HTML unescaped).
|
|
172
|
+
|
|
173
|
+
16. **Dead file: `src/status-page.html` (497 lines) + `scripts/status-page-stub.ts`.**
|
|
174
|
+
Nothing imports it since the SPA absorbed status (only
|
|
175
|
+
`scripts/status-page-stub.ts` references it). Delete both, or move the
|
|
176
|
+
stub note into the SPA docs.
|
|
177
|
+
|
|
178
|
+
17. **Routes tab breaks in the compiled binary.**
|
|
179
|
+
`#/routes` iframes `/dag`, which `readFileSync`s
|
|
180
|
+
`../docs/dag/training-inference-map.html` (`src/server.ts:1231`) — absent
|
|
181
|
+
outside the repo checkout, so the tab shows a bare 404 string. Either
|
|
182
|
+
embed the artifact like app.html, or hide the tab when `/dag` 404s.
|
|
183
|
+
|
|
184
|
+
18. **CLI banner vs landing route.** `src/cli.ts:64` says "status page at
|
|
185
|
+
http://localhost:8080/", but `/` lands on `#/chat`. Say "web app" or point
|
|
186
|
+
at `/#/status`.
|
|
187
|
+
|
|
188
|
+
19. **Curves nav tab never highlights.** It's a full-page `href="/curves"`
|
|
189
|
+
(`app.html:520`), outside the hash router, so the active-tab logic
|
|
190
|
+
(`app.html:1390`) can't mark it. Cosmetic; acceptable, but a
|
|
191
|
+
`aria-current` on the curve page's back-link side would be tidier.
|
|
192
|
+
|
|
193
|
+
20. **Library "too big" rows hide the useful number.** `app.html:2665-2666`
|
|
194
|
+
shows max context/decode only when `a.fits` (fit solved at 8192); a model
|
|
195
|
+
that fits at a smaller context shows `—`. Show `max_safe_context`
|
|
196
|
+
whenever it's > 0.
|
|
197
|
+
|
|
198
|
+
21. **Downloads visibility is per-process.** `/downloads` reads a
|
|
199
|
+
process-local tracker (`src/download.ts:276-279`) — a `mlx-bun get` in
|
|
200
|
+
another terminal is invisible to the nav pill/status card. Note in the UI
|
|
201
|
+
copy or leave; fixing needs a shared store.
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Suggested landing order
|
|
206
|
+
|
|
207
|
+
1. Bugs #1–#6 (one UI-only PR; no server changes).
|
|
208
|
+
2. #7 sft_scope + #15 adapter refresh (small, independent).
|
|
209
|
+
3. The queued server wiring (#11–#13) as its own PR: `listCanonical` +
|
|
210
|
+
`visionCapable` + `tier` in `/library` and `/v1/models`, then the
|
|
211
|
+
one-line UI badge.
|
|
212
|
+
4. #8 min_p end-to-end, #9 gc endpoint + Library affordance.
|
|
213
|
+
5. Cleanup: #16 dead files, #17 routes tab, #14 genDefaults surfacing.
|