BM25 Over SQLite vs a Vector Database
The category assumes an embedding service, an ANN index and a GPU. We measured entry search at recall@5 = 1.00 at 1.3 ms p95 with BM25 over SQLite, and kept the embedder optional.

Entry search in MonkeyLLM runs on BM25 over SQLite, and it reaches recall@5 = 1.00 at 1.3 ms p95 on our benchmark corpus. Before you object that lexical search died years ago, look at what the job actually is. The job is smaller than the category assumes, and that changes the economics.
What the category assumes
The default retrieval stack ships with assumptions baked in: an embedding model (a hosted API, or a GPU you keep warm), a vector database with an ANN index, a pipeline that re-embeds documents on every edit, and a migration story for the day you change embedding models and every stored vector goes stale. All of that is operational surface, and all of it is paid before the first query returns.
None of it is wrong. It is just rarely questioned, because "semantic search" sounds like a requirement rather than a design choice.
What entry search actually has to do
In a knowledge forest (a linked graph of documents, each carrying a curated passport: title, summary, tags), search is not the answer machine. It is the door. The agent searches once to find a starting node, then walks: it reads labels, follows links, and opens only what it needs. The walk resolves the question; search only has to land close.
That reframing shrinks the requirement. The door does not need to understand a paraphrase of the whole question. It needs to match a name, and matching names is what lexical search does best.
What we measured
BM25 over SQLite's full-text index: recall@5 = 1.00 at 1.3 ms p95. No embeddings, no vector database, no GPU. The detail that matters most: weighting curated fields (title, aliases) closed the gap to hybrid search entirely. Unweighted BM25 over raw bodies did not get there; BM25 over a curated passport did.
SQLite earns a word here. It is a single file inside the deployment. There is no search service to run, back up or monitor, and the same database already stores the forest itself. For a self-hosted system that promises your data never leaves your infra, deleting an entire stateful service from the architecture diagram is not a small win.
When vectors do earn their keep
This is not an argument that embeddings are useless. They earn their keep when queries are paraphrase-heavy and share no vocabulary with the documents, when the corpus is cross-lingual, or when you only have raw chunks with no curated metadata and no write-time budget to create any. In those regimes, lexical matching has nothing to grip.
Which is why the embedder is optional and supported, not removed. Point a model binding at an embedding endpoint and entry search becomes hybrid. What we removed is the assumption, not the capability.
The trade we actually made
The recall number is not free; it is prepaid. Curation happens at write time: ingest runs at 1.71 s per document, with 100% of summaries passing a sixty-token contract, and that is where the titles, aliases and summaries that BM25 grips come from. Spend intelligence on the environment so you can spend less on the model. The long version of that argument is the Forest Principle.
Reproduce it
The corpus and harness are committed, and the entry-search table regenerates with one command. Reproduce it yourself, read how the pieces fit in the primitives docs, or start at monkeyllm.com.