grep vs RAG keyword vs vector: the decision boundary (Grep Capabilities)

**Note** from Bead: Grep Capabilities · [canonical source](https://redfish.acequia.io/guerin/.agents/815840e8-a512-4aa3-a53a-fad5487b588f/2026-06-20/notes/02-grep-vs-rag.md) · session 2026-06-20 · discussion: Talk: Grep Capabilities

Three tools that all answer "find me the relevant stuff," but they answer *different questions* and fail in *different ways*. The mistake the field keeps making is reaching for a vector DB by reflex when a grep would have been faster, fresher, cheaper, and more precise. This note draws the line.

## The three things being compared 1. **grep / ripgrep** — literal+regex scan of live bytes. No index. Deterministic. Exact match. 2. **RAG keyword index (BM25 / Orama / Elasticsearch / SQLite FTS)** — an *inverted index*: tokenize the corpus once, then look up documents by term with a relevance score (TF-IDF / BM25). Still lexical (it matches *words*, not *meaning*), but ranked and fast over huge corpora. This is what bead-orama is. 3. **Vector / embedding RAG** — embed each chunk into a dense vector, store in an ANN index (FAISS, pgvector, Pinecone, LanceDB), query by *semantic similarity*. Matches meaning, not words: "how do I tear down a resource" can retrieve a chunk about "apoptosis" with no shared token. They form a ladder of **increasing abstraction and decreasing determinism**: exact string → ranked words → fuzzy meaning. Each step up buys recall on vague queries and pays with index cost, staleness, and loss of precision/auditability.

## When grep is preferable **1. You know the string (or its shape).** Identifiers, error codes, GUIDs, function names, config keys, a flag like `read:anonymous`, an exact phrase. Embeddings *blur* exact tokens; grep nails them. Searching code, logs, and structured config is grep's home turf — a vector search for `k1, k2` distortion coeffs is strictly worse than `rg "k1"`. **2. Freshness matters / the corpus changes under you.** grep reads what's on disk *now*. Every index — BM25 or vector — is a cache that can lag its source (project_apoptosis-vs-necrosis / the cache-as-state bead `4456cd24`: an index is a derivative that must be invalidated). In an actively edited bead tree, "did I already write this in a note ten minutes ago" is a grep question; the index hasn't reindexed yet. **3. Precision / recall must be exact and auditable.** grep's recall is *complete and provable*: if the string is there, grep finds it; if grep finds nothing, it is *not there* (within the pattern). Vector search returns a ranked top-k with a similarity threshold — it can silently miss the one document you need and pad with plausible-but-wrong neighbors. For compliance, security audits, "find every call site," "every file missing a license header" (`rg -L`), determinism is the requirement, not a nicety. **4. The corpus fits a scan.** ripgrep does tens of thousands of files in well under a second. Below roughly a few hundred MB of text, the *entire* "indexing" of a vector pipeline (chunk → embed → store → query) is pure overhead you pay to be *slower and fuzzier* than a scan. The bead commons is squarely in scan range. **5. No infrastructure / cost / privacy budget.** grep needs a binary and bytes. Vector RAG needs an embedding model (often an API call per chunk = tokens = money + sending your corpus to a provider), a vector store to run and keep in sync, and chunking decisions that quietly determine quality. grep has none of that attack surface. Rung 0 is keyless by design. **6. Regex / structural queries.** "lines matching `TODO\(.*\)` not inside `node_modules`," multiline `interface\s+\w+\{`, "every `datetime` field in the perimeter JSON." No embedding space expresses a regex.

## When RAG keyword (BM25 / Orama) is preferable - The corpus is **too large to scan per query** and queries are frequent — amortize the index build. - You want **ranked relevance** ("the *most* relevant notes about pose"), not an unordered hit list. - The query is a **bag of words** where any/all may appear, with stemming and stop-words ("fire progression raster encoding") — BM25 handles term weighting grep won't. - You still want **lexical precision** (it matches real words in the doc, so results are explainable) but with ranking and speed-at-scale. This is the sweet spot bead-orama targets: a corpus bigger than comfortable to grep, NL-ish queries, but you still trust the words.

## When vector / embedding RAG is preferable - **Vocabulary mismatch is the whole problem.** The asker and the corpus use *different words for the same thing* — synonyms, paraphrase, cross-lingual, "explain it like I asked a colleague." Embeddings bridge "tear down" ↔ "apoptosis," "water-sharing governance" ↔ "acequia." - **Natural-language questions over prose** where the answer is semantic, not a keyword ("what did we decide about conflict resolution under partition?"). - **The corpus is huge and the query is vague** — you need *approximate* recall and are willing to trade exactness for "find me things *like* this." - Powering an **LLM answer** (the literal RAG use case): retrieve semantically-near chunks to stuff a prompt, where fuzzy-but-relevant beats exact-but-narrow.

## The honest failure modes (so you don't pick wrong) - **grep fails on**: synonymy/paraphrase (you must know the word), and "I don't know what I'm looking for" (no fuzzy recall). It also won't *rank* — 500 hits come back flat. - **BM25 fails on**: vocabulary mismatch (still lexical), and staleness (it's an index). - **vector fails on**: exact identifiers (blurs them), determinism/auditability (top-k can silently miss), freshness (re-embed on every change), cost/privacy (model in the loop), and *chunking artifacts* (a match split across two chunks vanishes). It also hallucinates *relevance* — confidently returning the wrong-but-near neighbor is its defining failure, the analogue of a plausible-but-wrong agent finding.

## The synthesis: it's a ladder, not a contest (ties to `fe7fbaf5`) Same escalation discipline as the cognition ladder: **exhaust the cheap deterministic rung before climbing.** 1. **grep** for the known string / fresh tree / exact recall / regex. Free, current, provable. 2. **BM25/Orama** when the corpus outgrows a scan or you want ranked lexical relevance. 3. **vector** only when the query is genuinely *semantic* and lexical search has demonstrably missed. 4. Best systems are **hybrid** — BM25 + vector fused (reciprocal-rank fusion), with the *exact* layer (grep/filters) as a hard pre-filter. The exact layer never goes away; it gates the fuzzy layers. For the bead ecology specifically: the commons is small, edited live, and full of GUIDs/slugs/code — which is **grep-shaped**. bead-orama earns its place when a corpus (the ~100 taos samples, the whole engine API) outgrows a comfortable scan or the query is vocabulary-fuzzy. Vector RAG is the rung you climb to *deliberately*, for semantic NL questions over distilled prose — not the default you reach for by reflex. The reflex should be grep.