<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Bug Report on Nikhil Adhikari</title><link>https://kai2055.github.io/tags/bug-report/</link><description>Recent content in Bug Report on Nikhil Adhikari</description><generator>Hugo -- gohugo.io</generator><language>en-US</language><lastBuildDate>Thu, 23 Jul 2026 00:00:00 +0200</lastBuildDate><atom:link href="https://kai2055.github.io/tags/bug-report/index.xml" rel="self" type="application/rss+xml"/><item><title>Chasing a Bug That Didn't Exist</title><link>https://kai2055.github.io/p/chasing-a-bug-that-didnt-exist/</link><pubDate>Thu, 23 Jul 2026 00:00:00 +0200</pubDate><guid>https://kai2055.github.io/p/chasing-a-bug-that-didnt-exist/</guid><description>
 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;The short version, for anyone:&lt;/strong&gt; A search system was returning &amp;ldquo;nothing
found&amp;rdquo; — while the correct answer sat in its database, ranked first. Nothing had
crashed; every function did exactly what it was written to do. This is the story
of discovering that &lt;em&gt;the code was fine and the system was still broken&lt;/em&gt; — because
the test suite had been unknowingly grading the system on easy questions, and a
hidden limit was throwing away correct answers before anyone looked at them. It&amp;rsquo;s
a good example of the kind of failure that doesn&amp;rsquo;t show up as an error message —
the most dangerous kind.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Below is the full technical investigation. Skip it freely — the summary above is
the point.&lt;/em&gt;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Layer:&lt;/strong&gt; 1 (retrieval) · &lt;strong&gt;Outcome:&lt;/strong&gt; No defect found in application code. Two
real problems found anyway.&lt;/p&gt;
&lt;h2 id="where-it-started"&gt;Where it started
&lt;/h2&gt;&lt;p&gt;Layer 2 was blocked. A retrieval probe returned zero results against a store
holding 83 chunks. No error, no exception — just an empty list.&lt;/p&gt;
&lt;p&gt;The working theory was environmental: the embedding model wasn&amp;rsquo;t loaded in Ollama.
That theory mattered, because Layer 2&amp;rsquo;s &amp;ldquo;the agent declines honestly&amp;rdquo; framing
depended on knowing whether the empty result was a genuine no-match or a broken
pipe.&lt;/p&gt;
&lt;p&gt;First command of the day killed it. &lt;code&gt;ollama list&lt;/code&gt; showed &lt;code&gt;nomic-embed-text&lt;/code&gt;,
274 MB, installed four weeks earlier. The model was fine. So the bug was real, and
somewhere in code I&amp;rsquo;d written.&lt;/p&gt;
&lt;h2 id="the-elimination"&gt;The elimination
&lt;/h2&gt;&lt;p&gt;Rather than guess, I listed every link in the chain and tested each in order. Six
suspects.&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Suspect&lt;/th&gt;
 &lt;th&gt;Result&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Model not installed&lt;/td&gt;
 &lt;td&gt;Present&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Wrong database path&lt;/td&gt;
 &lt;td&gt;App points at &lt;code&gt;data/chromadb&lt;/code&gt; — correct&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Store empty&lt;/td&gt;
 &lt;td&gt;83 chunks&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Wrong distance measure&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;hnsw:space: cosine&lt;/code&gt;, set explicitly&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Embedding prefix mismatch&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;search_document:&lt;/code&gt; / &lt;code&gt;search_query:&lt;/code&gt; correctly paired&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Threshold comparison inverted&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;distance &amp;lt;= threshold&lt;/code&gt; — right direction&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Every single one came back clean. Every function I checked did exactly what it was
written to do. That was the first real finding: &lt;strong&gt;the system was correct and still
useless.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="three-false-leads-all-self-inflicted"&gt;Three false leads, all self-inflicted
&lt;/h2&gt;&lt;p&gt;The diagnostic script lied to me three times, and each time for the same reason.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It hardcoded the database path.&lt;/strong&gt; Pointed at &lt;code&gt;data/chroma&lt;/code&gt; instead of
&lt;code&gt;data/chromadb&lt;/code&gt;. ChromaDB doesn&amp;rsquo;t error on an empty folder — it silently creates a
blank database. So the script made an empty store and correctly reported it was
empty. Cost: half an hour convinced the corpus had vanished.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It reimplemented the embedding call.&lt;/strong&gt; Called Ollama directly with plain text,
no prefix. The corpus was embedded with &lt;code&gt;search_document:&lt;/code&gt;. So it was comparing
unlabelled queries against labelled documents — a mismatched measurement that
produced a real-looking number I then reasoned from.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It used a candidate depth the system doesn&amp;rsquo;t use.&lt;/strong&gt; I set 15 to see where
documents ranked. The system uses 5. That made the diagnostic disagree with the
sweep, and reconciling the two is what exposed the actual bug.&lt;/p&gt;
&lt;p&gt;The lesson generalises: &lt;strong&gt;a diagnostic that restates what the system defines will
eventually disagree with it, and it will disagree quietly.&lt;/strong&gt; Every fix was the same
— import the real code instead of copying it.&lt;/p&gt;
&lt;h2 id="what-was-actually-wrong"&gt;What was actually wrong
&lt;/h2&gt;&lt;p&gt;Two things, neither a broken function.&lt;/p&gt;
&lt;h3 id="the-evaluation-suite-was-easier-than-reality"&gt;The evaluation suite was easier than reality
&lt;/h3&gt;&lt;p&gt;The 31-query suite was written after reading and normalising all 15 post-mortems.
So it reuses the documents&amp;rsquo; own vocabulary without meaning to. It scored the system
1.000 while plain-English questions were failing.&lt;/p&gt;
&lt;p&gt;To measure this instead of asserting it, I built a paired suite: same entries, same
&lt;code&gt;expected_doc_id&lt;/code&gt;, same difficulty, only the query text rewritten in plain English.
No-match probes and filter queries held constant as a control.&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Suite&lt;/th&gt;
 &lt;th&gt;Hit rate @ 0.30&lt;/th&gt;
 &lt;th&gt;MRR&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Original wording&lt;/td&gt;
 &lt;td&gt;1.000&lt;/td&gt;
 &lt;td&gt;0.949&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Plain English&lt;/td&gt;
 &lt;td&gt;0.826&lt;/td&gt;
 &lt;td&gt;0.783&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The control held — decline rate identical across both suites at every threshold. So
the gap came from wording and nothing else.&lt;/p&gt;
&lt;p&gt;The clearest single illustration:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Query&lt;/th&gt;
 &lt;th&gt;Distance&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&amp;ldquo;BGP route leak&amp;rdquo; — the document&amp;rsquo;s own words&lt;/td&gt;
 &lt;td&gt;0.1996&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&amp;ldquo;database ran out of connections&amp;rdquo; — same kind of event, plain words&lt;/td&gt;
 &lt;td&gt;0.3035&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The distance was largely measuring word overlap. The threshold was acting as a
jargon filter.&lt;/p&gt;
&lt;h3 id="top_k-was-discarding-correct-answers-before-checking-them"&gt;&lt;code&gt;top_k&lt;/code&gt; was discarding correct answers before checking them
&lt;/h3&gt;&lt;p&gt;The plain-English curve flattened at 0.913 and never moved, even at threshold 0.50
where the filter does nothing. That meant some failures weren&amp;rsquo;t threshold failures
at all.&lt;/p&gt;
&lt;p&gt;A per-query diagnostic found the reason: one query&amp;rsquo;s correct document sat at &lt;strong&gt;rank
6, distance 0.2888&lt;/strong&gt; — comfortably inside the 0.30 threshold, never looked at,
because &lt;code&gt;top_k=5&lt;/code&gt; truncated the list first.&lt;/p&gt;
&lt;p&gt;The candidate count was overriding the relevance rule. Raising it to 10:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;&lt;/th&gt;
 &lt;th&gt;Before&lt;/th&gt;
 &lt;th&gt;After&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Hit rate @ 0.30&lt;/td&gt;
 &lt;td&gt;0.826&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;0.870&lt;/strong&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Ceiling&lt;/td&gt;
 &lt;td&gt;0.913&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;0.957&lt;/strong&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Decline rate&lt;/td&gt;
 &lt;td&gt;0.600&lt;/td&gt;
 &lt;td&gt;0.600&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Real answers gained, no noise admitted. The same number turned out to be hardcoded
in four places — &lt;code&gt;retrieve()&lt;/code&gt;, &lt;code&gt;run_sweep&lt;/code&gt;, &lt;code&gt;score_filter_query&lt;/code&gt;, and the agent&amp;rsquo;s
retrieve node at 3. It&amp;rsquo;s now one constant, &lt;code&gt;DEFAULT_TOP_K&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Worth noting the agent was retrieving &lt;strong&gt;three&lt;/strong&gt; candidates per symptom while Layer 1
used five. The component doing the harder work had the least evidence.&lt;/p&gt;
&lt;h2 id="the-decision-i-didnt-make"&gt;The decision I didn&amp;rsquo;t make
&lt;/h2&gt;&lt;p&gt;Two queries still fail at 0.30, missing by 0.0055 and 0.0092. Moving the threshold
to 0.35 would recover both. I left it alone.&lt;/p&gt;
&lt;p&gt;The distance bands overlap — a junk probe scored 0.215, closer than four of five
real queries. There is no value that separates good from bad, so any number is a
tradeoff, and one tuned to clear 23 specific queries is fitting the sample rather
than the problem. 0.35 would also drop decline rate from 0.600 to 0.200 — triple the
noise to recover two borderline cases.&lt;/p&gt;
&lt;p&gt;And the failure modes aren&amp;rsquo;t equal. Returning nothing costs an engineer time.
Returning the wrong past incident during a live outage sends them after the wrong
root cause. Strict is the right side to fail on.&lt;/p&gt;
&lt;h2 id="what-this-is-actually-a-story-about"&gt;What this is actually a story about
&lt;/h2&gt;&lt;p&gt;Not a bug hunt. Every function was correct. It&amp;rsquo;s about a system that stays up,
throws no errors, and quietly returns nothing while holding the answer. During a
live outage it would tell an engineer that nothing similar has ever happened — with
the matching post-mortem in hand, ranked first.&lt;/p&gt;
&lt;p&gt;And it&amp;rsquo;s about the evaluation framework marking its own homework. The suite said
1.000. The system was at 0.826 for anyone who hadn&amp;rsquo;t read the corpus. The
measurement was wrong in the flattering direction, which is the direction you don&amp;rsquo;t
check.&lt;/p&gt;
&lt;p&gt;The fix for that isn&amp;rsquo;t a better threshold. It&amp;rsquo;s a regression gate — re-running these
measurements on every corpus change and reporting when the numbers move. You don&amp;rsquo;t
calibrate once; you build the thing that notices when calibration has gone stale.&lt;/p&gt;</description></item><item><title>BUG-001 — A Threshold Applied Where It Didn't Belong</title><link>https://kai2055.github.io/p/bug-001-distance-threshold/</link><pubDate>Mon, 29 Jun 2026 00:00:00 +0200</pubDate><guid>https://kai2055.github.io/p/bug-001-distance-threshold/</guid><description>
 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;The short version, for anyone:&lt;/strong&gt; The system had two different jobs that
happened to share one piece of code: &lt;em&gt;ranking&lt;/em&gt; results by closeness, and
&lt;em&gt;fetching an exact set&lt;/em&gt; of records that match a filter. A cutoff meant only for
the first job was silently being applied to the second — but it stayed harmless
as long as a temporary setting was loose enough to never cut anything. The moment
that setting was tightened to a realistic value, filter accuracy collapsed from
100% to 33%. The bug had been there since the first line of code; changing one
config value exposed it. The lesson: &lt;em&gt;tests passing at a permissive setting are
not proof of correctness.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Full bug report below.&lt;/em&gt;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;&lt;/th&gt;
 &lt;th&gt;&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;ID&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;BUG-001&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Component&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;retrieve()&lt;/code&gt; — &lt;code&gt;src/embedding.py&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Severity&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Major · &lt;strong&gt;Priority&lt;/strong&gt; High&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Status&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Closed — fixed and verified&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Related&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;TP-001, ADR-013&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="what-happened"&gt;What happened
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;RELEVANCE_THRESHOLD&lt;/code&gt; was lowered from 1.0 to 0.30. Semantic queries improved.
Filter queries collapsed. Filter precision, recall, and exact-match all dropped from
1.000 to 0.333 — one of three queries passing. The same value across three runs. Not
noise. A logic bug.&lt;/p&gt;
&lt;p&gt;The defect was already there. At 1.0, nothing ever got cut, so the bad path never
ran. A config value changed and the latent bug surfaced.&lt;/p&gt;
&lt;h2 id="evidence"&gt;Evidence
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Metric&lt;/th&gt;
 &lt;th&gt;t = 1.0&lt;/th&gt;
 &lt;th&gt;t = 0.30&lt;/th&gt;
 &lt;th&gt;Delta&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Hit rate@5&lt;/td&gt;
 &lt;td&gt;1.000&lt;/td&gt;
 &lt;td&gt;1.000&lt;/td&gt;
 &lt;td&gt;—&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;MRR&lt;/td&gt;
 &lt;td&gt;0.949&lt;/td&gt;
 &lt;td&gt;0.949&lt;/td&gt;
 &lt;td&gt;—&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Section accuracy&lt;/td&gt;
 &lt;td&gt;0.435&lt;/td&gt;
 &lt;td&gt;0.435&lt;/td&gt;
 &lt;td&gt;—&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Decline rate&lt;/td&gt;
 &lt;td&gt;0.000&lt;/td&gt;
 &lt;td&gt;0.600&lt;/td&gt;
 &lt;td&gt;+0.600&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Filter precision / recall / exact&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;1.000&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;0.333&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;−0.667&lt;/strong&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Regression is isolated. Everything else held.&lt;/p&gt;
&lt;h2 id="root-cause"&gt;Root cause
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;retrieve()&lt;/code&gt; applied the cosine-distance threshold to &lt;em&gt;every&lt;/em&gt; query — including
metadata-filtered ones:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Keep only results close enough to be relevant&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;relevant &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [r &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; r &lt;span style="color:#f92672"&gt;in&lt;/span&gt; results &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; r[&lt;span style="color:#e6db74"&gt;&amp;#34;distance&amp;#34;&lt;/span&gt;] &lt;span style="color:#f92672"&gt;&amp;lt;=&lt;/span&gt; threshold] &lt;span style="color:#75715e"&gt;# applied unconditionally&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Two things are wrong. The list comprehension has no condition — every caller gets
the distance cutoff, so semantic ranking and metadata set-retrieval share one path.
And the docstring states it as a flat rule, with no sign that a metadata query might
need different treatment. The defect is in the design, not a slip in the code.&lt;/p&gt;
&lt;p&gt;A filter query like &amp;ldquo;minor-severity incidents&amp;rdquo; is a &lt;strong&gt;set question&lt;/strong&gt;. The right
answer is &lt;em&gt;every&lt;/em&gt; document matching the filter — it doesn&amp;rsquo;t matter how semantically
close the query phrase sits to the chunk text; the metadata already decided. At 1.0,
nothing was ever discarded, so the wrong logic produced correct output by accident.
At 0.30, documents that matched the filter correctly but sat far in cosine distance
got silently dropped. Filter scoring is exact set-match — lose one document, lose
the query.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The real defect:&lt;/strong&gt; two retrieval modes — semantic ranking and metadata
set-retrieval — forced through one code path, with a parameter meant for one mode
leaking into the other.&lt;/p&gt;
&lt;h2 id="fix"&gt;Fix
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;retrieve()&lt;/code&gt; now accepts &lt;code&gt;threshold=None&lt;/code&gt; — no distance cutoff, return all
metadata-matched results, ranked. The call site for filter scoring passes
&lt;code&gt;threshold=None&lt;/code&gt;; semantic queries keep the real threshold. The two modes are now
distinguished at the call site.&lt;/p&gt;
&lt;p&gt;Fixed at the source — &lt;code&gt;retrieve()&lt;/code&gt;, not the eval harness — so every downstream
caller inherits it, including the Layer 2 diagnostic agent.&lt;/p&gt;
&lt;h2 id="what-was-missed"&gt;What was missed
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;score_filter_query&lt;/code&gt; passes &lt;code&gt;top_k=5&lt;/code&gt;. If a metadata filter matches more than 5
documents, the vector store returns only the top 5 by distance, and set-match scoring
counts the rest as missing. Harmless now — no filter in the 15-document corpus exceeds
5 — but the same defect class: a semantic parameter constraining a metadata query.
Tracked separately, to fix before the corpus grows.&lt;/p&gt;
&lt;h2 id="lessons-learned"&gt;Lessons learned
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;1. Passing tests at a permissive setting are not evidence of correctness.&lt;/strong&gt; The bug
existed from the first line of code. &lt;code&gt;RELEVANCE_THRESHOLD = 1.0&lt;/code&gt; meant the faulty
branch never discarded anything. The placeholder was even flagged in the source
(&lt;code&gt;# loose placeholder&lt;/code&gt;). Knowing a setting is temporary is not the same as testing
what happens when it changes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Partial metric reporting hides regressions.&lt;/strong&gt; The threshold sweep tracked 3 of 5
metrics — hit rate, MRR, decline rate. It &amp;ldquo;confirmed&amp;rdquo; 0.30 as optimal while silently
breaking a metric it did not watch. Caught only on the confirming run that reported
the full set. Fix: full metrics on every run, enforced by exit criteria.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Determinism separates bugs from noise.&lt;/strong&gt; The first thought was jitter. Three
identical runs at 0.333 reclassified it as logic, worth investigating.&lt;/p&gt;</description></item></channel></rss>