<?xml version="1.0" encoding="UTF-8"?>

<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>Parker Jones Dev Blog - retrieval</title>
    <subtitle>Local-first, reproducible systems in Rust and Nix — and using AI as a power tool without getting credulous about it. I build the unglamorous plumbing and write up what actually happened, including what broke.</subtitle>
    <link rel="self" type="application/atom+xml" href="https://parkerjones.dev/tags/retrieval/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://parkerjones.dev"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-07-09T00:00:00+00:00</updated>
    <id>https://parkerjones.dev/tags/retrieval/atom.xml</id>
    <entry xml:lang="en">
        <title>Half of My Hybrid Search Was Silently Off</title>
        <published>2026-07-09T00:00:00+00:00</published>
        <updated>2026-07-09T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://parkerjones.dev/posts/building-the-harness-retrieval/"/>
        <id>https://parkerjones.dev/posts/building-the-harness-retrieval/</id>
        <content type="html" xml:base="https://parkerjones.dev/posts/building-the-harness-retrieval/">&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;&#x2F;strong&gt; — I pointed my own retrieval engine at my own blog and it ranked the wrong post
first. Not dirty data, not bad embeddings: one word of Rust. Search terms were joined with a
space, which SQLite&#x27;s full-text search reads as &lt;em&gt;AND&lt;&#x2F;em&gt;, so every natural-language question
silently zeroed out the keyword half of &quot;hybrid&quot; search and left it running on embeddings
alone. Swapping the space for &lt;code&gt;OR&lt;&#x2F;code&gt; moved the right answer from #3 to #1.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Takeaway:&lt;&#x2F;strong&gt; retrieval is an engineering surface with its own silent failures — so don&#x27;t
reach for it until your context genuinely won&#x27;t fit in the model&#x27;s window. Cache-and-dump
(paste it all in, let caching pay for it) is the boring default that wins until it
physically can&#x27;t.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;I asked my own search engine for the one post I&#x27;ve written about saving tokens with a Rust
CLI. I&#x27;ve written exactly one. It came back third.&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;text&quot;&gt;❯ ctx search &amp;quot;the post about saving tokens with a rust cli&amp;quot; --mode hybrid --explain
Search: mode=hybrid, alpha=0.60, candidates: 0 keyword + 80 vector

1. [0.60] filesystem:blog &#x2F; rust-memory-cheatsheet.md
    scoring: keyword=0.000  semantic=1.000  → hybrid=0.600
2. [0.56] filesystem:blog &#x2F; nes1.md
    scoring: keyword=0.000  semantic=0.927  → hybrid=0.556
3. [0.45] filesystem:blog &#x2F; rtk-token-killer.md
    scoring: keyword=0.000  semantic=0.745  → hybrid=0.447
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Ahead of the right answer: a Rust &lt;em&gt;memory&lt;&#x2F;em&gt; cheatsheet, and an emulator for the &lt;em&gt;NES&lt;&#x2F;em&gt;. The
post it should have handed me — &lt;a href=&quot;https:&#x2F;&#x2F;parkerjones.dev&#x2F;posts&#x2F;rtk-token-killer&#x2F;&quot;&gt;&quot;How I Cut My Claude Code Token Usage by 90% with
RTK&quot;&lt;&#x2F;a&gt;, tagged &lt;code&gt;tokens&lt;&#x2F;code&gt;, &lt;code&gt;rust&lt;&#x2F;code&gt;, &lt;code&gt;cli&lt;&#x2F;code&gt;, transparently about saving
tokens with a Rust CLI — lost to two posts whose main qualification is that they also say
&quot;Rust&quot; a lot.&lt;&#x2F;p&gt;
&lt;p&gt;Nothing here is dirty. The corpus is 40 blog posts I wrote and proofread. The right document
is in the index. The query is a plain English question. And &lt;a href=&quot;https:&#x2F;&#x2F;parkerjones.dev&#x2F;posts&#x2F;context-harness-rag-rust&#x2F;&quot;&gt;Context
Harness&lt;&#x2F;a&gt; — the retrieval engine I built and have been
&lt;a href=&quot;https:&#x2F;&#x2F;parkerjones.dev&#x2F;posts&#x2F;building-the-harness-theater&#x2F;&quot;&gt;writing this series about&lt;&#x2F;a&gt; — confidently ranked a
cheatsheet about smart pointers above it.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;parkerjones.dev&#x2F;posts&#x2F;building-the-harness-theater&#x2F;&quot;&gt;Last post&lt;&#x2F;a&gt; I promised this one would be &quot;what
retrieval actually buys you, and the specific moments it&#x27;s slop in, slop out.&quot; I want to
correct that framing before I use it, because &quot;slop in, slop out&quot; is garbage-in-garbage-out,
and GIGO is the most-written and least-interesting take in all of RAG: clean your chunks, tune
your overlap, and so on. My chunks are clean. What bit me was something else, and — I&#x27;ll spoil
the ending — it turned out to be one word of my own code. So this is two posts in one: the bug
and the fix, which is the honest shape of building anything; and then the larger question the
bug throws into relief, which is whether you should be reaching for retrieval at all.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-take-i-m-not-writing&quot;&gt;The take I&#x27;m not writing&lt;&#x2F;h2&gt;
&lt;p&gt;The failure above is not dirty data. It&#x27;s structural, and it hides inside the word everyone
reaches for when they want retrieval to sound trustworthy: &lt;strong&gt;hybrid.&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The pitch for hybrid search is genuinely good, which is why everyone repeats it. Keyword search
— &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Okapi_BM25&quot;&gt;BM25&lt;&#x2F;a&gt; over an inverted index — nails exact terms
and whiffs on paraphrase: it&#x27;ll find &quot;BM25&quot; and miss &quot;that ranking function with term-frequency
saturation.&quot; Semantic search — cosine similarity over embeddings — is the mirror image: catches
the paraphrase, misses the exact string. So you run both and blend the scores. Keyword covers
semantic&#x27;s blind spot; semantic covers keyword&#x27;s. A safety net with two independent ropes.&lt;&#x2F;p&gt;
&lt;p&gt;Context Harness does exactly this, on purpose. Keyword candidates come from SQLite&#x27;s FTS5
&lt;code&gt;bm25()&lt;&#x2F;code&gt;. Vector candidates come from &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;parallax-labs&#x2F;context-harness&#x2F;blob&#x2F;main&#x2F;docs&#x2F;adr&#x2F;0004-brute-force-vector-search.md&quot;&gt;brute-force cosine
similarity&lt;&#x2F;a&gt;
over every chunk — no approximate index, just the dot products. Each side is &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;parallax-labs&#x2F;context-harness&#x2F;blob&#x2F;main&#x2F;docs&#x2F;adr&#x2F;0005-hybrid-scoring-with-min-max-normalization.md&quot;&gt;min-max
normalized per
query&lt;&#x2F;a&gt;,
then blended:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;text&quot;&gt;hybrid = (1 - alpha) * keyword_norm + alpha * semantic_norm
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;with &lt;code&gt;alpha = 0.6&lt;&#x2F;code&gt;, leaning slightly toward semantics. That&#x27;s the whole scoring model. Two
ropes, one knot. So why did a post that&#x27;s &lt;em&gt;about&lt;&#x2F;em&gt; saving tokens with a Rust CLI lose to a
memory cheatsheet?&lt;&#x2F;p&gt;
&lt;h2 id=&quot;read-the-first-line-of-the-output&quot;&gt;Read the first line of the output&lt;&#x2F;h2&gt;
&lt;pre&gt;&lt;code data-lang=&quot;text&quot;&gt;Search: mode=hybrid, alpha=0.60, candidates: 0 keyword + 80 vector
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;Zero keyword candidates.&lt;&#x2F;strong&gt; One rope wasn&#x27;t there. &quot;Hybrid&quot; quietly degraded to pure semantic
search, and pure semantics decided &quot;rust cli tokens&quot; was closest to a cheatsheet about Rust
memory. I only know one rope was missing because I passed &lt;code&gt;--explain&lt;&#x2F;code&gt;; without it I&#x27;d have
gotten a clean-looking ranked list and no hint that half the machinery had switched off.&lt;&#x2F;p&gt;
&lt;p&gt;Why zero? Watch it flip on a single word:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;text&quot;&gt;❯ ctx search &amp;quot;tokens rust cli&amp;quot; --mode keyword
1. [1.00] filesystem:blog &#x2F; rtk-token-killer.md

❯ ctx search &amp;quot;saving tokens rust cli&amp;quot; --mode keyword
(no results)
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;tokens rust cli&lt;&#x2F;code&gt; finds the right post instantly, perfect score. Add &lt;code&gt;saving&lt;&#x2F;code&gt; — a word that
isn&#x27;t in that post — and the entire result set collapses to nothing. That is the behavior of an
&lt;em&gt;AND&lt;&#x2F;em&gt;: every term required, one miss and you get zero. And a natural-language question is a pile
of words your target document mostly doesn&#x27;t contain (&lt;code&gt;the&lt;&#x2F;code&gt;, &lt;code&gt;post&lt;&#x2F;code&gt;, &lt;code&gt;about&lt;&#x2F;code&gt;, &lt;code&gt;saving&lt;&#x2F;code&gt;, &lt;code&gt;with&lt;&#x2F;code&gt;,
&lt;code&gt;a&lt;&#x2F;code&gt;), so under AND the keyword rope goes slack on exactly the queries a human or a model
actually types. It only holds for keyword-salad input that nobody writes at a chat box.&lt;&#x2F;p&gt;
&lt;p&gt;I didn&#x27;t design an AND. So I opened the file.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;one-word&quot;&gt;One word&lt;&#x2F;h2&gt;
&lt;p&gt;The query text becomes an FTS5 &lt;code&gt;MATCH&lt;&#x2F;code&gt; string in one small function
(&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;parallax-labs&#x2F;context-harness&#x2F;blob&#x2F;main&#x2F;crates&#x2F;context-harness&#x2F;src&#x2F;sqlite_store.rs&quot;&gt;&lt;code&gt;sqlite_store.rs&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;):&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;rust&quot;&gt;fn fts_query_from_user_text(query: &amp;amp;str) -&amp;gt; String {
    query
        .split(|c: char| !(c.is_alphanumeric() || c == &amp;#39;_&amp;#39;))
        .filter(|term| !term.is_empty())
        .collect::&amp;lt;Vec&amp;lt;_&amp;gt;&amp;gt;()
        .join(&amp;quot; &amp;quot;)          &#x2F;&#x2F; ← the bug
}
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It splits the query into words and joins them with a space. And in FTS5, a space between terms
&lt;em&gt;is&lt;&#x2F;em&gt; an implicit AND. So &lt;code&gt;the post about saving tokens with a rust cli&lt;&#x2F;code&gt; becomes a query that
means &lt;em&gt;every one of those words must appear in a single document&lt;&#x2F;em&gt; — which nothing does. The
keyword arm wasn&#x27;t broken. It was doing precisely what I told it to, and what I told it was
wrong.&lt;&#x2F;p&gt;
&lt;p&gt;You can watch the fix work before changing any Rust, straight against the FTS index — space
(AND) versus &lt;code&gt;OR&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;text&quot;&gt;-- implicit AND (space-joined): the current behavior
sqlite&amp;gt; SELECT bm25(chunks_fts), title FROM chunks_fts ...
        WHERE chunks_fts MATCH &amp;#39;the post about saving tokens with a rust cli&amp;#39; ...;
-- (no rows)

-- OR-joined
sqlite&amp;gt; ... WHERE chunks_fts MATCH &amp;#39;the OR post OR about OR saving OR tokens OR ... OR cli&amp;#39; ...;
-11.43  rtk-token-killer.md
 -9.01  context-harness-rag-rust.md
 -7.65  rtk-token-killer.md
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;OR lets a document match on &lt;em&gt;any&lt;&#x2F;em&gt; term, and BM25 does the rest: common words like &lt;code&gt;the&lt;&#x2F;code&gt; and
&lt;code&gt;with&lt;&#x2F;code&gt; appear everywhere, so their inverse-document-frequency weight is near zero and they
contribute almost nothing; rare, load-bearing words like &lt;code&gt;tokens&lt;&#x2F;code&gt; and &lt;code&gt;rust&lt;&#x2F;code&gt; and &lt;code&gt;cli&lt;&#x2F;code&gt; drive the
score. The right post wins on the terms that matter. So the fix is one word:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;diff&quot;&gt;-        .join(&amp;quot; &amp;quot;)
+        .join(&amp;quot; OR &amp;quot;)
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h2 id=&quot;the-after&quot;&gt;The after&lt;&#x2F;h2&gt;
&lt;p&gt;Rebuild &lt;code&gt;ctx&lt;&#x2F;code&gt;, re-run the exact query that started this — same corpus, same index, nothing
re-embedded, only the query construction changed:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;text&quot;&gt;❯ ctx search &amp;quot;the post about saving tokens with a rust cli&amp;quot; --mode hybrid --explain
Search: mode=hybrid, alpha=0.60, candidates: 80 keyword + 80 vector

1. [0.85] filesystem:blog &#x2F; rtk-token-killer.md
    scoring: keyword=1.000  semantic=0.745  → hybrid=0.847
2. [0.69] filesystem:blog &#x2F; rust-memory-cheatsheet.md
    scoring: keyword=0.224  semantic=1.000  → hybrid=0.690
3. [0.67] filesystem:blog &#x2F; nes1.md
    scoring: keyword=0.364  semantic=0.878  → hybrid=0.673
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;candidates: 80 keyword + 80 vector&lt;&#x2F;code&gt; — both ropes taut. The token-killer post goes from third to
first; the cheatsheet and the emulator, which only ever had the semantic rope, drop below it.&lt;&#x2F;p&gt;


&lt;figure class=&quot;post-image&quot;&gt;
  &lt;img src=&quot;https:&#x2F;&#x2F;parkerjones.dev&#x2F;processed_images&#x2F;retrieval-before-after.17baee1f49fc6711.webp&quot; width=&quot;1600&quot; height=&quot;840&quot;
       alt=&quot;Side-by-side terminal output of the same ctx search query before and after the one-word fix. Before, with terms joined by a space, the keyword arm returns 0 candidates and rtk-token-killer.md ranks third, behind a Rust memory cheatsheet and a NES emulator. After, with terms joined by OR, the keyword arm returns 80 candidates and rtk-token-killer.md ranks first.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
  &lt;figcaption&gt;The same query, before and after the one-word change. Left: `join(&amp;#x27; &amp;#x27;)` — 0 keyword candidates, the right post buried at #3. Right: `join(&amp;#x27; OR &amp;#x27;)` — 80 keyword candidates, the right post first.&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;That&#x27;s the entire fix, and it&#x27;s the whole reason to instrument retrieval in the first place: the
&lt;code&gt;--explain&lt;&#x2F;code&gt; line is what turned &quot;the results feel off&quot; into &quot;one arm reports zero candidates,&quot;
which is a bug you can find in a file instead of a vibe you argue about.&lt;&#x2F;p&gt;
&lt;p&gt;It wasn&#x27;t a one-off, either. Earlier I&#x27;d asked a four-repo index — 446 documents, roughly 700,000
tokens — how its &lt;em&gt;own&lt;&#x2F;em&gt; hybrid scoring normalizes, and the document literally titled
&lt;code&gt;hybrid-scoring-with-min-max-normalization&lt;&#x2F;code&gt; came in &lt;strong&gt;second&lt;&#x2F;strong&gt;, beaten by fifteen thousandths of
a point by a vaguer doc that caught the one stray keyword hit. After the fix:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;text&quot;&gt;❯ ctx search &amp;quot;how does hybrid scoring normalize keyword and semantic scores&amp;quot; --mode hybrid --explain
1. [1.00] filesystem:ch &#x2F; 0005-hybrid-scoring-with-min-max-normalization.md
    scoring: keyword=1.000  semantic=1.000  → hybrid=1.000
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;First, perfect score. The engine can now find the spec that describes the engine. And the query
that already worked — a keyword-rich &lt;code&gt;set up remote build machines for nix&lt;&#x2F;code&gt;, which was pulling
its right answer at a clean &lt;code&gt;1.000&lt;&#x2F;code&gt; before — still sits at &lt;code&gt;1.000&lt;&#x2F;code&gt; after, with the cross-repo
runbook it surfaced from a &lt;em&gt;different&lt;&#x2F;em&gt; repo actually scoring higher than before. On the three
queries that defined the problem, the right answer is now first, and the control didn&#x27;t regress.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;m being careful with that sentence, because OR is not free — it broadens the candidate pool,
so somewhere it has to cost me. So I ran a bigger check: fifteen queries by hand across both
corpora, each with the answer I expected written down first. Twelve came back identical, two
improved, and one regressed — &lt;em&gt;how do I run a database in the browser&lt;&#x2F;em&gt; used to put my
&lt;a href=&quot;https:&#x2F;&#x2F;parkerjones.dev&#x2F;posts&#x2F;databses-in-the-browser&#x2F;&quot;&gt;database-in-the-browser post&lt;&#x2F;a&gt; first, and now ranks a
closely related SurrealDB-caching post there instead, with the on-the-nose post bumped to
second. OR pulled in a neighbor and the blend preferred it: a real, if small, tax, landing
exactly where I&#x27;d have guessed — a keyword-shaped query where AND&#x27;s precision was quietly doing
work. Better than three cherry-picked wins. Still fifteen queries I chose, graded against
answers I decided were right, which is not a benchmark — it&#x27;s marking my own homework, and it&#x27;s
the next post&#x27;s problem. For now the honest claim is narrow and true: a one-word bug was
zeroing out half of hybrid search on every natural-language query, and now it isn&#x27;t.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-bug-is-the-argument&quot;&gt;The bug is the argument&lt;&#x2F;h2&gt;
&lt;p&gt;Here&#x27;s the part that outlasts the fix. A single word — a space where an &lt;code&gt;OR&lt;&#x2F;code&gt; belonged — silently
disabled one of the two retrieval strategies I built the whole engine around, on exactly the
inputs it would see in real use, and it produced &lt;em&gt;confident, plausible, cited&lt;&#x2F;em&gt; wrong answers the
entire time. No crash. No error. A ranked list with scores, every one of them a lie of omission.
I&#x27;d have kept trusting it if I hadn&#x27;t gone looking.&lt;&#x2F;p&gt;
&lt;p&gt;That is the true cost of retrieval, and it&#x27;s why the interesting question isn&#x27;t &quot;how do I make
RAG better,&quot; it&#x27;s &quot;should I be running RAG here at all.&quot; Because look at what the whole episode
was &lt;em&gt;for&lt;&#x2F;em&gt;: those 40 blog posts are about 60,000 tokens. They fit in a context window with room
to spare. The correct thing to do with a corpus that small is not to retrieve over it — it&#x27;s to
paste every post into the prompt, let &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;docs.anthropic.com&#x2F;en&#x2F;docs&#x2F;build-with-claude&#x2F;prompt-caching&quot;&gt;prompt
caching&lt;&#x2F;a&gt; make the repeat
cost trivial, and let the model read all of it. Do that and this bug &lt;em&gt;cannot happen to you&lt;&#x2F;em&gt;,
because there&#x27;s no ranking step to get wrong. There&#x27;s no #1 to incorrectly promote, because there
is no list. I introduced an entire class of silent failure to solve a problem — &quot;which twelve
chunks?&quot; — that, at this scale, I did not have.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Cache-and-dump is the monolith of context: unglamorous, and correct right up until it
physically won&#x27;t fit in the window.&lt;&#x2F;strong&gt; Retrieval is the service you extract when the monolith
overflows — and, exactly like extracting a microservice, the moment you do it you sign up for an
operational surface the monolith never had. Query construction. FTS semantics. Score
normalization. A one-word bug that halves your quality without telling you. You don&#x27;t take that
on because it&#x27;s elegant. You take it on because you were forced to. And the reasons that force
you are specific — not &quot;it&#x27;s cheaper&quot; (with caching, dumping is cheap; &lt;a href=&quot;https:&#x2F;&#x2F;parkerjones.dev&#x2F;posts&#x2F;building-the-harness-theater&#x2F;&quot;&gt;the invoice going up
isn&#x27;t the work getting better&lt;&#x2F;a&gt;), but capability:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Scale past the window&lt;&#x2F;strong&gt; — a corpus you genuinely can&#x27;t fit, like those four repos.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Relevance you can&#x27;t predict&lt;&#x2F;strong&gt; — the one document out of hundreds you didn&#x27;t know you&#x27;d need
until the question existed.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;A corpus that changes&lt;&#x2F;strong&gt; — so you don&#x27;t re-shovel and re-cache everything on every edit.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Attention rot&lt;&#x2F;strong&gt; — even when it all fits, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.anthropic.com&#x2F;engineering&#x2F;effective-context-engineering-for-ai-agents&quot;&gt;more context is not free
accuracy&lt;&#x2F;a&gt;;
a model handed 700K tokens attends worse than one handed the right 5K.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;That cross-repo runbook the fixed engine surfaced — a doc from one repository answering a
question I&#x27;d only documented in another, neither aware the other exists — is retrieval earning
its keep, and it earned it because reason #1 was real: 700K tokens is not going in a window. On
the blog, none of the four apply, so retrieval had no job, and inventing one for it is how I
ended up ranking a smart-pointer cheatsheet above the post I asked for.&lt;&#x2F;p&gt;
&lt;p&gt;This is the same lesson as &lt;a href=&quot;https:&#x2F;&#x2F;parkerjones.dev&#x2F;posts&#x2F;building-the-harness-theater&#x2F;&quot;&gt;last post&lt;&#x2F;a&gt;, one layer down.
There I&#x27;d chopped a solo project into 78 documents and six agents because decomposition &lt;em&gt;felt&lt;&#x2F;em&gt;
like rigor, and called it microservices-on-myself. Retrieval on a corpus that fits in the window
is the identical mistake: paying the coordination cost of a distributed system to solve a problem
a monolith already had handled. Extract the service when you&#x27;re forced. Not before.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;distrust-the-retriever-enough-to-instrument-it&quot;&gt;Distrust the retriever — enough to instrument it&lt;&#x2F;h2&gt;
&lt;p&gt;There&#x27;s a discipline I lean on hard at work; I&#x27;ll keep it generic, but it travels: treat the
model as an &lt;em&gt;advisor to be verified, not an authority to be trusted&lt;&#x2F;em&gt;. You don&#x27;t take its
confident output at face value — you build the system so the output gets checked. Almost nobody
extends that same suspicion to the &lt;em&gt;retriever&lt;&#x2F;em&gt;. Retrieved context gets a pass. It feels like
ground truth: it came from your own documents, it has a similarity score, it&#x27;s the thing you
point to when someone asks whether the model made it up. But &lt;code&gt;rust-memory-cheatsheet&lt;&#x2F;code&gt; came back
at &lt;code&gt;0.60&lt;&#x2F;code&gt; for a question about token savings, and that number was never a measure of whether it
was &lt;em&gt;right&lt;&#x2F;em&gt; — it was a measure of embedding proximity with one of two ropes cut, which is a very
different thing wearing the same costume. The model downstream can&#x27;t tell the difference. It
receives the top chunk as grounding and does what it does with grounding: builds a confident,
well-cited answer on top of it. On a bad ranking, retrieval doesn&#x27;t remove the confabulation
risk — it &lt;em&gt;supplies&lt;&#x2F;em&gt; the confabulation with a footnote.&lt;&#x2F;p&gt;
&lt;p&gt;So distrust it. But the useful form of distrust isn&#x27;t a shrug about how retrieval is
treacherous; it&#x27;s &lt;code&gt;--explain&lt;&#x2F;code&gt;. Instrument the thing so that &quot;this feels wrong&quot; becomes &quot;the
keyword arm returned zero candidates,&quot; which is a bug with a location and a fix. Suspicion that
ends in humility is a pose. Suspicion that ends in a diff is engineering.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;where-this-goes&quot;&gt;Where this goes&lt;&#x2F;h2&gt;
&lt;p&gt;Context Harness got one bug better this afternoon, in public, and I&#x27;ll take it — but the honest
scope of what I proved is: on three queries that used to fail, the right answer is now first. The
reranker I keep &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;parallax-labs&#x2F;context-harness&#x2F;blob&#x2F;main&#x2F;docs&#x2F;prd&#x2F;0009-retrieval-quality-and-dogfooding.md&quot;&gt;deferring&lt;&#x2F;a&gt;
would catch a different, harder class of miss; OR-joining is not the last word on query
construction; and I still can&#x27;t tell you, with a number, whether the engine is &lt;em&gt;good&lt;&#x2F;em&gt; — only that
it&#x27;s better on the cases I looked at, which is exactly how you fool yourself.&lt;&#x2F;p&gt;
&lt;p&gt;Which is the next problem, and a harder one than a one-word bug: I&#x27;ve spent this whole post saying
&quot;first&quot; and &quot;better&quot; and &quot;wrong&quot; as if I can measure any of it. I mostly can&#x27;t — not the way a
benchmark would — and the gap between what I can measure and what actually matters is where the
next post lives.&lt;&#x2F;p&gt;
&lt;p&gt;— Parker Jones&lt;&#x2F;p&gt;
</content>
    </entry>
</feed>
