Part 2 of 3

Wax vs qmd

and the case for combining them

qmd (“Query Markup Documents”) and Wax look like twins — local-first, SQLite-backed, BM25+vector hybrid, MCP servers. They’re not. They solve adjacent but different problems, and that difference is exactly why one can sit on top of the other.qmd · TypeScript Wax · Swift
qmd claims carry file:line citations into its source; Wax claims keep the amber cites from Part 1.

Two axioms that decide everything

Almost every trade-off below follows from two structural facts.

① Index-as-cache vs store-of-record. qmd’s SQLite (~/.cache/qmd/index.sqlite) is a disposable index over files-on-disk: collections are dirs+globs; update --pull does a git pull then re-indexes; content dedups by hash. The files are the truth, the DB is rebuildable. Wax’s .wax is the truth — a transactional, crash-safe store (WAL, dual headers, Merkle).

② TypeScript + llama.cpp vs Swift + CoreML. qmd’s value-add is ~2 GB of GGUF models driven from TS via node-llama-cpp: embeddinggemma-300M, qwen3-reranker-0.6B, a finetuned 1.7B query-expander.llm.ts:252–255 Wax stays CoreML-light (MiniLM-384) with no LLM in the loop.

So “qmd on Wax” really means…

keep qmd’s brain (chunking, expansion, rerank) and swap its storage/retrieval for Wax. Whether that’s wise depends entirely on which feature and which build strategy — the rest of this page.

Side by side

Grounded in qmd’s source, not its README.

DimensionqmdWax
Language / runtimeTypeScript · Node ≥22 / BunSwift 6 · Apple-Silicon-native
StoragePlain SQLite + FTS5 + sqlite-vec db.tsCustom .wax container; SQLite embedded in a frame
Source of truthThe files on disk (index is a cache)The .wax file itself
Text searchFTS5 / BM25 (+ CJK normalization) store.ts:763FTS5 / BM25
Vector searchvec0 cosine, embeddinggemma-300M GGUF store.ts:1169Metal brute-force cosine, MiniLM-384
FusionRRF k=60 + 2× original + rank bonuses store.ts:3807RRF k=60 + query-type adaptive weights
LLM in loopYes — query expansion + rerankerNo
Structured / EAVNone — flat documents+chunksBitemporal EAV graph
Sync modelSync the files, rebuild cache (git-native)Sync binary (risky) or the Markdown projection
Transparencysqlite3-openable; --explain tracesOpaque (DB inside a frame); stats + query tools

Shared DNA: local-first, single-SQLite-backed, BM25+vector hybrid with the same RRF constant (k=60), and an MCP server with stdio+HTTP. The divergence is everything in the bottom four rows.

qmd’s retrieval pipeline

The real hybridQuery store.ts:4496 — the part worth borrowing. Click a stage.

Wax natively does only stages 3–4 (hybrid + RRF). Stages 2 (expand) and 5–7 (rerank+blend) are pure pre/post-retrieval — which is precisely why they port onto any backend.

Which qmd features port onto Wax?

Tap any feature to expand it — how cleanly it sits on Wax-as-storage, and what you inherit free vs. build yourself.

✓ clean (no Wax change) · ⚒ workable (you own glue) · △ redundant/partial · ✗ fights Wax

Two build strategies

The embedder swap is the fork in the road.

The deciding constraint

Wax’s daemon/MCP search uses Wax’s own MiniLM embedder — there’s no exposed “search with my precomputed vector.” So if you want qmd’s multilingual embeddinggemma, you must go native Swift — which means rewriting qmd’s ~15k-line pipeline. Stay in TS to reuse qmd’s code and you inherit MiniLM-384-en. There’s no cheap middle.

The verdict

Real benefits
  • Inherit a native Metal hybrid index + crash-safe single file.
  • Sidestep qmd’s biggest wart: on macOS qmd must setCustomSQLite() to Homebrew’s libsqlite3 (Apple’s is built OMIT_LOAD_EXTENSION) db.ts:25–100 — Wax bundles its engines, so that failure class vanishes.
  • The prize: Wax’s superset — turn retrievals into durable, bitemporal, citable EAV facts + sessions. qmd can’t.
Real costs
  • You re-add the ~2 GB of models + llama.cpp weight that Wax was designed to avoid.
  • You lose qmd’s transparency: arbitrary SQL, --explain raw scores, llm_cache as a table.
  • You lose qmd’s elegant “sync files, rebuild cache” model — or you fight Wax’s store-of-record nature.
  • Strategy B = a big rewrite; Strategy A = embedder/IPC compromise.
My take

“qmd, but native” is a poor trade — qmd’s architecture already fits its job. The one combination worth building is the inverse: not “qmd on Wax storage” but qmd’s retrieval feeding Wax’s structured memory. Use qmd-style smart chunking + rerank to find the right passages, then distill them into Wax EAV facts (with sm_evidence pointing back at the source span). The backend-agnostic pieces — rerank and query expansion — drop in cleanly and are the layer to build first.

Grounded in the cloned qmd source and the Wax source tree, 2026-05-31.

qmd/db.ts:14–103 qmd/store.ts:135–325 qmd/store.ts:3807–3850 qmd/store.ts:4496–4760 qmd/llm.ts:213–296 Wax/HybridSearch.swift Wax/MetalVectorEngine.swift:6 Wax/StructuredMemorySchema.swift