# Lexical-first retrieval — why the cheap path is the default

**What this is:** the rationale for arborist's retrieval philosophy —
lexical-first (FTS5 BM25 + content-addressed Merkle proofs) as the
default, with dense-vector semantic retrieval as an *optional,
additive* layer (#000039). Not a ticket; a positioning/architecture
reference. The headline: arborist's ingest is **10-100× cheaper per
document** than building a vector-DB representation, on the same
SQLite substrate — which is the difference between "ingest + search
runs on a phone" and "the NPU is now a hand-warmer."

---

## 1. The cost asymmetry, measured

| step | per-chunk cost (healthy CPU core) |
|---|---|
| **bge-small-en-v1.5 ONNX inference** (a vector-DB-style embed) | ~5-30 ms — chunk-length-dependent, 512-token cap; *worse* under load (~250 ms/chunk ≈ 4 chunks/s measured on a contended dev box at load 11/8 cores) |
| arborist's ingest pipeline: chunker (`tok-512-v1`) + SHA-256 leaf hash + Merkle commit + SQLite INSERTs (`documents` / `chunks` / `edges` / `audit_events`) + zstd content compression + FTS5 inverted-index update | **well under 1 ms total** |

So adding embeddings multiplies ingest time by **~10-100×**, entirely
in the ONNX matmuls. The non-vec ingest path runs at hundreds of
chunks/s; a corpus-wide embed of arborist's 6.24 M-chunk wiki shard
set is a one-time hours-on-idle / days-on-contended *batch job*
(#000039 §14.6) — not something you'd do on a phone, ever.

**And the query side is asymmetric too.** An FTS5 query: tokenize,
look up posting lists in the inverted-index B-tree per term, BM25-rank
— a handful of B-tree traversals, microseconds to low-milliseconds. A
vector query: tokenize, run the embedder (**one transformer forward
pass — the same ~5-30 ms as embedding a document chunk**), *then* the
ANN scan. On a phone, every search would pay a transformer forward
pass before it even touches the index.

## 2. Same SQLite file, different philosophy

Both approaches put a `.db` file on disk. The split is in *what's in
it* and *how retrieval works*:

| | lexical-first (arborist) | embedding-first (a vector DB) |
|---|---|---|
| index | FTS5 inverted index (term → posting list) | dense vectors + ANN index (IVF / HNSW / flat) |
| build cost | ~free (sort tokens into B-trees) | a transformer forward pass per chunk |
| query cost | B-tree lookups + BM25 | embed the query (transformer) + ANN scan |
| matching | exact term / phrase / stem | semantic similarity |
| proof-bearing? | **yes** — chunks are content-addressed (`leaf_hash`), Merkle-rooted, the retrieved span is inclusion-provable against `C(M)` | **no** — embeddings are lossy, non-canonical; they can't enter a proof path (CLAUDE.md "soft hash vs hard hash") |

That last row is the deep version of the point. arborist *is* the
Merkle Providence model — content-addressed chunks, Merkle inclusion
proofs, the audit chain — and that model is **cheap by construction**:
the proof-bearing layer (hard hashes, FTS5-retrievable chunks) costs
~nothing to build. Embeddings are a *soft* signal — they help recall,
they never enter a proof — and they're the expensive bolt-on. The
"soft hash vs hard hash" discipline (hard = SHA-256 commitments /
proofs / cache_key; soft = embeddings / TF-IDF / similarity, never in
the proof path) maps directly onto "cheap-and-mandatory vs
expensive-and-optional."

## 3. The consequence: edge / mobile viability

A phone (or a Raspberry Pi, or a permacomputer node) can:

- **Ingest** documents at arborist's full rate — sorting tokens into
  B-trees, hashing, writing SQLite rows is what mobile CPUs do well.
- **Query** the lexical index essentially for free — B-tree lookups,
  no neural net in the path.
- **Use** a precomputed int8 `chunk_vecs` someone else built
  off-device, if a semantic layer is wanted — querying it still costs
  a query-embedding forward pass per search, but the *build* cost
  (the days-long batch job) was paid elsewhere.

A vector-DB-first design can't do the first two cheaply: ingest pegs
the CPU/NPU per chunk, and even browsing pays a forward pass per
query. On battery, that's the difference between a usable offline
knowledge store and a hand-warmer that's flat by lunch.

## 4. The honest caveat

Lexical-first **trades** the *semantic allusion gap* — the case where
a query and the target chunk share zero stems but mean the same thing
("what did Orwell call the country at war with Oceania?" → an article
that says "Eastasia" / "Eurasia" but never the query's words). FTS5's
four routes (body BM25, title-LIKE, core-keyword TF-IDF, phrase-
pattern) close the *lexical* allusion gap, not the semantic one. Dense
vectors close the semantic gap.

That's exactly why arborist's answer isn't "we don't do vectors" —
it's: **vectors are an opt-in, additive 5th retrieval route, never the
default**, and the embed pass is lazy / out-of-band (a cron, or a
Prometheus-Σ unconscious-sweep task per #000037 §3.1) so the heavy
transformer work runs off-device / off-peak. The cheap, mobile-viable,
proof-bearing path is what ships by default; the semantic layer is
there when you want it and can afford it, and it doesn't compromise
the rest. (`int8` quantization keeps the storage tax at +6% over the
shards — vs +25% at float32 — and the embeddings still never enter the
proof path.)

## 5. References

- `docs/tickets/ticket-000039-sqlite-vec-optional-backend.md` — the
  optional vec backend; §3 (storage), §4 (additive not replacement),
  §14 (ingest integration + idempotency), §14.6 (the embed-throughput
  measurement these numbers come from).
- `docs/tickets/ticket-000050-vec-rrf-hybrid-fusion.md` — Phase 2:
  wiring vec as a 5th retrieval route (gated on a corpus backfill + a
  recall bench).
- CLAUDE.md "Retrieval pipeline" — the four FTS5 routes; "soft hash vs
  hard hash" — the discipline this rationale generalizes.
- `~/git/unfirehose-nextjs-logger/whitepaper/merkle-providence-reverse-rag-whitepaper.rst`
  — the canonical Merkle Providence Reverse RAG paper (this doc is the
  arborist-side positioning note; folding the mobile-viability argument
  into the paper proper is a possible follow-up).
- #000037 §3.1 — the Prometheus-Σ unconscious-sweep task that's the
  natural home for the lazy embed pass.
