Originally created by: Akarsh-Hegde
The end-to-end pipeline (classification → worklog update) runs three on-device models, but onboarding only provisioned one:
Qwen3.5-2B — also does classification/matching (there is no separate classifier model).Qwen3-Reranker-0.6B — downloaded lazily on the first worklog run.Qwen3-Embedding-0.6B — downloaded lazily and its library (sentence-transformers/torch) was never shipped in the runtime, so /distill_hour would ImportError and crash the worklog pipeline at stage 1 on a fresh install.Result: a fresh DMG could reach the dashboard before any model was ready, then stall or crash on its first worklog cycle.
Single source of truth + provision all three
services/agents/model_registry.py — declares the three models with env-overridable ids (MERIDIAN_LLM_ID / WORKLOG_RERANKER_ID / MERIDIAN_EMBEDDER_ID) and per-model HF download filesets. mlx_classifier, reranker, and session_distiller now read from it.routes/prefetch.py + _state.py download the whole set with aggregate progress. The wire contract the tray decodes (state/received/total/error) is unchanged, so no Rust decode changes were needed.MLX-native embedder (no torch)
session_distiller.py swapped sentence-transformers/torch for mlx_embeddings — keeps the runtime lean (no ~2.5 GB torch) and on the single MLX backend. Added to the [mlx] extra. Pooling verified at source: mlx_embeddings/models/qwen3.py does last-token pooling + L2 normalize, matching the previous ST behaviour.Onboarding UX (the ask)
modelReady so the user can't reach the dashboard until all three are on disk. A Retry button appears on error so the gate is never a dead end.com.meridiona.mlx-server.plist pins HF_HOME so the wizard's eager prefetch and the runtime's lazy loads resolve the same cache; install.sh's first-run check now covers all three models.torch/sentence-transformers refs.cargo fmt + cargo clippy + cargo check pass (workspace incl. tray).mlx_embeddings source level.A runtime cosine sanity check on the embedder (norms ≈ 1; related ≫ unrelated) is pending — the 8-bit shard repeatedly stalled on this machine's HF connection (the same download the product does on first run). Run on a good connection:
services/.venv/bin/python -c "from huggingface_hub import snapshot_download; snapshot_download('mlx-community/Qwen3-Embedding-0.6B-8bit')"
then exercise agents.session_distiller._embed.
🤖 Generated with Claude Code
Originally posted by: coderabbitai[bot]
✨ Finishing Touches
🧪 Generate unit tests (beta)
- [ ] Create PR with unit tests - [ ] Commit unit tests in branch `feat/onboarding-model-provisioning`Comment
@coderabbitai helpto get the list of available commands.Originally posted by: Akarsh-Hegde
Fix summary — all review findings addressed
Commit:
07f3514fpushed tofeat/onboarding-model-provisioning.Findings addressed (code-review workflow + @adityaharishch)
start_mlx_server_cmdfailure swallowed (.catch(() => {})).catch((e) => setMlxErr(String(e)))canNext: (s) => s.modelReadygatesnapshot_downloadno stall guardprefetch_model()if app_state.get("mlx_module") is None: return {"state": "done", ...}_embed()loads embedder for empty input_get_embedder()to skip model load whentexts = []model_prefetchOTEL span missing ERROR statustry/exceptinside span:span.set_status(ERROR)+span.record_exception(exc)+models[i]["state"] = "error"pm_worklog_update/config.pyhardcoded LLM defaultos.environ.get("MLX_SERVER_MODEL") or model_registry.llm_id()mlxErrstate for provisioning errors; onlyerr(step errors) goes to the Footerspeeduses cumulative averagemlx_classifier._get_model()readsMODEL_IDmodule-constantmodel_registry.llm_id()per-call; same for_get_tokenizer()mlx_server.rsreturnsOk(())on any non-2xx from/prefetch_modelErrso wizard surfaces Retrystatestays"downloading"after failureasyncio.gatherstate="downloading"snapshot inprefetch_status()prefetch_state["state"]under lock after the rglob walk_MLX_ALLOW_PATTERNSlist across all threeModelSpecinstancestuple[str, ...]— immutable, each spec independently safe_embed()returns(0, 0)for empty inputMERIDIAN_LLM_IDruntime change is an edge case with no user-facing pathAll pre-push checks passed (cargo fmt, cargo clippy, cargo test, UI build, UI tests, security audit). PR is ready for re-review.
Ticket changed by: Akarsh-Hegde