PR #534 agent changes, tested one at a time

Qaf chat retrieval ablation · 26 Sep 2026 · 128 real production conversations from Langfuse (Arabic and Latin-script) · 2,610 blind pairwise verdicts

As written, PR #534 makes answers worse. Against production it wins only 36% of blind head-to-heads (38 wins, 69 losses; p = 0.004), mainly because answers become less complete. It is 16% cheaper per turn, but the first text arrives about 5 s later.

No single change causes this. Tested alone, every change except the evidence byte caps is quality-neutral. The regression comes from their combined effect on how much evidence reaches the model: 8 passages per search instead of 20, plus the 24/48 KiB caps, cuts delivered evidence from about 165 KiB to about 60 KiB per turn.

The fix is small and measured. Keep every PR change except two: deliver 20 passages instead of 8, and remove the byte caps. That version is at parity with production (54%, 51–44), costs 6% less, adds only 1.4 s to first text, and keeps the PR's real wins: working expansion, catalog-filtered search, dedup and the budget.

36%PR as written: blind win rate vs production (p = 0.004)
54%PR with 20 passages and no byte caps: parity (CI 44–63%)
−16% / −6%cost per turn: PR as written / recommended version
+5.0 s / +1.4 smedian time to first text: PR as written / recommended version
29%byte caps alone: the only atomic change that clearly hurts
0% → 79%expand success, production vs PR (production expand is broken today)

Recommendations

Each atomic change, measured alone

Each row turns on one PR change on top of production (main code + prompt v24) and replays the same real questions. Costs and times are on the 80 first-turn questions unless noted. Quality is the order-collapsed blind win rate against production over decided pairs (95% CI); the production-vs-production null control landed at 48%. Green or red means the CI excludes 50%; grey means no measurable difference. Rows at the bottom are combinations.

ChangeCost / turn vs prodFirst text (p50) vs prodTotal time (p50) vs prodAnswer quality vs prod (blind win rate, decided pairs, all judged slices)Other effectsRecommendation
Always hybrid search
Vector + BM25 (25 each) fused with RRF, then rerank → 20; the model no longer picks semantic/keyword.
+10%
$0.0585
+0.7 s
14.2 s
+0.2 s 45% 15W / 18L / 7T · CI 30%–62% · no clear difference Search latency +265 ms (1.68 s vs 1.41 s p50). Evidence +16% because BM25 favours long passages. Quality 45%, n.s. (Arabic 38%, English 53%). Take
Quality-neutral; needed to remove the unreliable mode choice. Its cost is absorbed in the recommended version.
8 passages per search (was 20)
Reranker returns 8 instead of 20.
-24%
$0.0404
+0.8 s
14.4 s
+0.6 s 53% 17W / 15L / 8T · CI 36%–69% · no clear difference Largest cost cut alone (−24%). Completeness already trends worse alone (9–16). With the other changes it drives the regression: the PR at 8 passages scores 36–38%, and at 20 it scores 54%. Drop
Keep 20. Evidence volume is what moves answer quality (same finding as the Aug rerank study).
lookup_catalog + source filters
Resolve author, book or genre names to IDs; search accepts authorIds, bookIds and categoryIds.
+11%
$0.0472
+3.0 s
15.6 s
-1.0 s 64% 9W / 5L / 6T · CI 39%–84% · no clear difference On random questions: +2% cost, 49% quality. Filters are applied on only 2 of 20 named questions without prompt v28, and 9 of 20 with it. The alias “ابن قيم الجوزية” missed; the model recovered. Take
Wins 64% on named-author and named-book questions; ship together with prompt v28.
Grounded expansion
The model sees version_id and sequence_number; expand is validated against retrieved passages; ±2 neighbours (was ±5).
+9%
$0.0579
+1.2 s
14.8 s
+1.2 s 57% 38W / 29L / 29T · CI 45%–68% · no clear difference Production expand is broken: the model never sees coordinates, guesses them and gets 0 rows (0 of 12 calls across production-code arms succeeded). With the PR, 11/14 succeed and 3 guessed coordinates are rejected. +9% cost. Take
Fixes a dead tool; quality 57% (38–29), the best single change.
Dedup + memoization
Identical calls are cached; already-delivered passages are dropped and excluded (NotIn) from later searches.
-6%
$0.0500
+1.3 s
14.9 s
+2.6 s 51% 25W / 24L / 7T · CI 37%–64% · no clear difference −6% cost. The 3.4% unretrieved citations here were malformed IDs (truncated UUIDs, :page suffixes), concentrated in one run, not a dedup effect. Take
Quality-neutral (51%), small saving, prevents repeated evidence.
Retrieval call budget
At most 8 retrieval calls; stop after 2 empty calls; tools off at step 18.
+5%
$0.0561
+1.1 s
14.6 s
-0.1 s 53% 18W / 16L / 6T · CI 37%–69% · no clear difference Rarely binds on production code (4/80 turns). Inside the PR it binds on 7/80 (13/80 with byte caps), and then Gemini still calls search → AI_NoSuchToolError. Fix, then take
Quality-neutral (53%). Fix the tool-removal behaviour first (see findings).
Evidence byte caps
24 KiB per tool result, 48 KiB per model step; the newest evidence is kept and older evidence is dropped from context.
-8%
$0.0491
+2.8 s
16.3 s
+0.1 s 29% 13W / 32L / 11T · CI 18%–43% · worse The model re-searches for evidence that was dropped: searches 5.3 vs 3.4, budget hits 13/80, first text +2.7 s. Judges cite thinner, mis-cited and invented quotations. On Arabic it is even more expensive (+7%). Drop
The only atomic change that is clearly worse: 29% (13–32), p = 0.007.
Keep tool history on follow-ups
pruneMessages toolCalls: "none"; earlier turns' evidence stays in context (needed for follow-up expansion).
+12%
$0.0364
-3.9 s
5.6 s
-0.5 s 64% 7W / 4L / 5T · CI 35%–85% · no clear difference Follow-up first text is 3.8 s faster (the model reuses evidence instead of searching). +12% cost from larger inputs. Only 16 conversations. Take
64% on follow-ups (7–4, small n); faster; enables grounded expansion.
Prompt: no minimum number of searches
Removes “minimum of two initial searches for simple questions, four for complex” from v24.
-16%
$0.0449
+0.2 s
13.8 s
-0.3 s 50% 13W / 13L / 14T · CI 32%–68% · no clear difference Searches 2.5 vs 3.4; −16% cost with no change in first-text time. Take
Free saving: quality 50% (13–13–14).
Combined: PR as written
Every change above, 8 passages, byte caps, prompt v28.
-16%
$0.0448
+5.0 s
18.6 s
+1.8 s 36% 38W / 69L / 21T · CI 27%–45% · worse Tool rounds before the answer 2.4 vs 1.2. Cited IDs 8.9 vs 16.7. Completeness 27–77. 9 turns with tool errors. Don’t merge as-is
36% (38–69), p = 0.004; Arabic 31%.
Combined: PR code, old prompt v24
Isolates the prompt: the same code with production prompt v24.
-16%
$0.0447
+4.5 s
18.0 s
+1.7 s 34% 32W / 63L / 33T · CI 25%–44% · worse v28 is better than v24 on the same code: 61% (63–40), p = 0.03. The prompt is not the problem. —
34% (32–63): the regression lives in the code changes.
Combined: PR without byte caps
Leave-one-out: everything except the byte caps.
-28%
$0.0382
+2.9 s
16.5 s
+2.5 s 38% 43W / 70L / 15T · CI 30%–47% · worse Cheapest arm (−28%). 0 tool errors. Still thin evidence at 8 passages. Not enough
38% (43–70), p = 0.014. Removing the caps alone does not fix quality.
Combined: PR, no byte caps, 20 passages
Recommended: every PR change + v28, without byte caps, delivering 20 passages (hybrid 25/branch).
-6%
$0.0501
+1.5 s
15.0 s
+1.4 s 54% 51W / 44L / 33T · CI 44%–63% · no clear difference Evidence 128 KiB/turn. Named-source questions 62%, Arabic 59%, chronology 40% (n = 12). Completeness tied 48–54. Ship this
Parity at 54% (51–44, CI 44–63), −6% cost, +1.4 s to first text.

Bugs and risks found while testing

Date filter fails on the live index today

A direct Turbopuffer query with the PR's author_death_year filter returns HTTP 400: filter error in key author_death_year: attribute not found. Until the manual backfill runs and is verified, every date-filtered search call is a tool error. The PR body says release order is backfill first; this confirms it is a hard gate, not a nicety. The model also rarely uses the filter: once in 92 real questions, and never on 12 real “salaf / early scholars / المتقدمين” questions.

Budget cut-off produces tool-call errors

When the retrieval budget is exhausted, prepareRetrievalStep returns activeTools: [] with toolChoice: "none", but Gemini still emits search calls, which surface as AI_NoSuchToolError (2–3 per affected turn in replays). The budget is also checked per call, not per step: a step with 10 parallel searches runs 8 and silently returns [] for the rest. Suggest keeping the tools active and returning an explicit “retrieval budget exhausted — answer from the evidence above” result.

Byte caps trigger re-search loops

With older evidence dropped from context, the model searches again for what it lost: 5.3 vs 3.4 searches per turn, and the budget is hit in 13 of 80 turns vs 1 of 80. The saving from shorter inputs is eaten by the extra rounds (on Arabic questions cost goes *up* 7%), and first text is 2.7 s slower.

Evidence projection silently drops results that fail its schema

readEvidence in retrieval-evidence.ts parses tool outputs with the full chunk schema and returns [] on any mismatch. For example, results without version_id are erased from the model's context with no log. The PR's own outputs pass, but any future format change or odd historical message would vanish silently. The harness hit this directly. Suggest logging and passing through unparseable results instead of dropping them.

Production `expand` is broken today

On main, formatChunkForModel hides version_id and there is no sequence_number, so the model guesses coordinates and gets 0 rows: none of the 12 expand calls made by production-code arms returned anything. The PR's grounded expansion fixes this (11 of 14 succeed; 3 invented coordinates were correctly rejected).

Follow-ups now keep tool history

handlers/chat/index.ts changes pruneMessages({ toolCalls }) from before-last-2-messages to none. This is required for follow-up expansion. It makes follow-up first text 3.8 s faster but costs 12% more per follow-up. Bigger histories also move the Gemini implicit-cache prefix; check prod cache-hit rates after release.

Langfuse production traces stop on 16 Sep

The newest production chat-message trace is from 2026-09-16 19:39 UTC, and nothing arrived in the 10 days since. This harness sampled 6–16 Sep for that reason. The Langfuse exporter or keys likely broke around a deploy that day.

Full PR vs production

The PR as written loses to production on completeness, not tone or language. Judges repeatedly note fewer salaf sayings, scholars or verses than the question asked for, whole sections without citations, and claims cited to passages that do not contain them. That is what an evidence-starved model does: it sees about 60 KiB of evidence per turn instead of about 165 KiB, and cites 8.9 passages instead of 16.7.

Speed: total turn time is about the same (answer generation dominates), but the model makes 2.4 sequential tool rounds before answering instead of 1.2, so first text moves from 13.6 s to 18.6 s (median). The recommended version makes 1.4 rounds and lands at 15.0 s.

Cost: −16% as written. The recommended version is −6%: most of the saving came from starving the model, which is what cost the quality.

First-turn questions (80: 40 Arabic, 40 Latin-script)

Armn$/turnFirst text s (p50)Total s (p50)StepsSearchesExpandsLookupsInput tokEvidenceWordsCited idsUnretrieved citesTurns w/ tool error
Production (main)80$0.053313.629.72.23.40.030.0048k161 KiB50016.70.9%0
Production replicate80$0.050013.830.62.13.20.010.0044k143 KiB50916.90.5%0
PR #534 as written80$0.044818.631.63.43.60.190.1935k58 KiB4458.92.4%9
PR code, prompt v2480$0.044718.031.53.04.60.060.1430k68 KiB44010.10.4%12
PR without byte caps80$0.038216.532.22.92.90.070.1736k56 KiB44711.50.5%0
PR, no byte caps, 20 passages80$0.050115.031.12.42.50.050.1647k125 KiB49216.30.5%0
Hybrid search only80$0.058514.229.92.13.30.000.0052k188 KiB49817.81.4%0
8 passages only80$0.040414.430.42.53.70.030.0034k74 KiB44412.90.5%0
Catalog lookup + source filters80$0.054414.729.82.33.30.010.2150k164 KiB47616.71.0%0
Grounded expansion80$0.057914.830.92.23.60.030.0055k174 KiB54016.50.8%0
Dedup + memo79$0.050014.932.32.23.40.010.0044k129 KiB51117.33.4%0
Retrieval call budget80$0.056114.629.62.23.50.030.0053k165 KiB51416.70.7%0
Evidence byte caps80$0.049116.329.83.25.30.110.0033k93 KiB44010.32.9%0
No minimum searches (prompt)80$0.044913.829.42.42.50.040.0044k96 KiB49815.60.9%1

Quality by language

Arabic

ContrastWin rate of left arm (decided pairs)
full vs base31% 10W / 22L / 8T · CI 18%–49% · worse
pr_v24 vs base26% 8W / 23L / 9T · CI 14%–43% · worse
full_nobytes vs base31% 11W / 24L / 5T · CI 19%–48% · worse
full_20 vs base59% 16W / 11L / 13T · CI 41%–75% · no clear difference
full vs pr_v2466% 23W / 12L / 5T · CI 49%–79% · no clear difference
size8 vs base44% 8W / 10L / 2T · CI 25%–66% · no clear difference
min_search vs base50% 6W / 6L / 8T · CI 25%–75% · no clear difference
hybrid vs base38% 6W / 10L / 4T · CI 18%–61% · no clear difference
bytes vs base29% 5W / 12L / 3T · CI 13%–53% · no clear difference
base2 vs base47% 14W / 16L / 10T · CI 30%–64% · no clear difference

Latin-script

ContrastWin rate of left arm (decided pairs)
full vs base39% 14W / 22L / 4T · CI 25%–55% · no clear difference
pr_v24 vs base43% 13W / 17L / 10T · CI 27%–61% · no clear difference
full_nobytes vs base36% 12W / 21L / 7T · CI 22%–53% · no clear difference
full_20 vs base52% 16W / 15L / 9T · CI 35%–68% · no clear difference
full vs pr_v2452% 16W / 15L / 9T · CI 35%–68% · no clear difference
size8 vs base64% 9W / 5L / 6T · CI 39%–84% · no clear difference
min_search vs base50% 7W / 7L / 6T · CI 27%–73% · no clear difference
hybrid vs base53% 9W / 8L / 3T · CI 31%–74% · no clear difference
bytes vs base27% 4W / 11L / 5T · CI 11%–52% · no clear difference
base2 vs base50% 16W / 16L / 8T · CI 34%–66% · no clear difference

Targeted slices

Named authors and books: this is the slice the catalog tool is for. With prompt v28 the model resolves the name and filters in 9 of 20 questions, and the catalog change alone wins 64% there. The PR as written still loses (35%) because of thin evidence; the recommended version wins 62% (8–5), directionally better than production.

Chronology: real “salaf / early vs later scholars” questions never triggered the date filter, so they measure evidence volume more than chronology. The PR as written is slowest here (27.7 s to first text). The recommended version is at 40% on only 12 questions (CI 17–69), so no conclusion either way.

Follow-ups: production English follow-ups are rare (1 in about 250 multi-turn traces), so this set of 16 is 8 Arabic and 8 Latin-script, including French and Indonesian. Keeping tool history is the useful change here; everything is within noise at this size.

Named author or book (20)

Armn$/turnFirst text s (p50)Total s (p50)StepsSearchesExpandsLookupsInput tokEvidenceWordsCited idsUnretrieved citesTurns w/ tool error
Production (main)20$0.042312.628.22.12.80.000.0036k110 KiB41611.60.4%0
PR #534 as written20$0.041219.132.73.62.90.000.6034k47 KiB3577.70.7%0
PR code, prompt v2420$0.037815.029.32.83.70.000.3524k60 KiB4058.60.6%0
PR without byte caps20$0.030215.327.52.82.10.000.6024k40 KiB3659.20.0%0
PR, no byte caps, 20 passages20$0.043215.931.52.61.90.000.5041k97 KiB41814.10.4%0
Catalog lookup + source filters20$0.047215.627.32.33.00.000.3042k162 KiB42213.80.4%0
ContrastWin rate of left arm (decided pairs)
full vs base35% 6W / 11L / 3T · CI 17%–59% · no clear difference
full_nobytes vs base47% 8W / 9L / 3T · CI 26%–69% · no clear difference
full_20 vs base62% 8W / 5L / 7T · CI 36%–82% · no clear difference
pr_v24 vs base27% 3W / 8L / 9T · CI 10%–57% · no clear difference
catalog vs base64% 9W / 5L / 6T · CI 39%–84% · no clear difference
full vs pr_v2464% 9W / 5L / 6T · CI 39%–84% · no clear difference

Chronology / “salaf”, “early scholars” (12)

Armn$/turnFirst text s (p50)Total s (p50)StepsSearchesExpandsLookupsInput tokEvidenceWordsCited idsUnretrieved citesTurns w/ tool error
Production (main)12$0.058713.838.62.24.20.000.0053k182 KiB57619.70.4%0
PR #534 as written12$0.054827.743.23.84.70.000.0042k105 KiB46710.20.8%1
PR code, prompt v2412$0.048618.840.53.16.20.000.0032k90 KiB46010.60.8%4
PR without byte caps12$0.047221.743.23.13.80.000.0046k80 KiB55414.70.6%1
PR, no byte caps, 20 passages12$0.050215.630.72.22.70.000.0049k113 KiB55016.30.5%0
Catalog lookup + source filters12$0.057217.034.52.23.40.000.0051k188 KiB57617.60.9%0
ContrastWin rate of left arm (decided pairs)
full vs base27% 3W / 8L / 1T · CI 10%–57% · no clear difference
full_nobytes vs base42% 5W / 7L / 0T · CI 19%–68% · no clear difference
full_20 vs base40% 4W / 6L / 2T · CI 17%–69% · no clear difference
pr_v24 vs base20% 2W / 8L / 2T · CI 6%–51% · no clear difference
catalog vs base50% 4W / 4L / 4T · CI 22%–78% · no clear difference
full vs pr_v2470% 7W / 3L / 2T · CI 40%–89% · no clear difference

Follow-up turns (16 two-turn conversations; second turn measured)

Armn$/turnFirst text s (p50)Total s (p50)StepsSearchesExpandsLookupsInput tokEvidenceWordsCited idsUnretrieved citesTurns w/ tool error
Production (main)16$0.03249.419.62.01.60.000.0032k49 KiB4209.93.8%0
PR #534 as written16$0.033015.726.42.62.40.000.4428k39 KiB3676.70.0%0
PR code, prompt v2416$0.045517.529.63.23.60.000.4437k43 KiB4737.80.8%2
PR without byte caps16$0.035013.826.62.82.40.000.4437k42 KiB3939.50.0%0
PR, no byte caps, 20 passages16$0.039712.423.82.41.80.000.2545k56 KiB36310.10.0%0
Grounded expansion16$0.043312.323.32.12.50.000.0045k77 KiB45610.22.5%0
Keep tool history16$0.03645.619.02.11.90.000.0042k51 KiB3919.24.8%0
Dedup + memo16$0.041514.227.22.02.60.000.0041k90 KiB46012.05.2%0
Evidence byte caps16$0.031710.922.62.42.60.000.0028k47 KiB4145.82.2%0
ContrastWin rate of left arm (decided pairs)
full vs base45% 5W / 6L / 5T · CI 21%–72% · no clear difference
full_nobytes vs base44% 7W / 9L / 0T · CI 23%–67% · no clear difference
full_20 vs base50% 7W / 7L / 2T · CI 27%–73% · no clear difference
pr_v24 vs base46% 6W / 7L / 3T · CI 23%–71% · no clear difference
expand vs base50% 4W / 4L / 8T · CI 22%–78% · no clear difference
history vs base64% 7W / 4L / 5T · CI 35%–85% · no clear difference
session vs base44% 7W / 9L / 0T · CI 23%–67% · no clear difference
bytes vs base31% 4W / 9L / 3T · CI 13%–58% · no clear difference
full vs pr_v2462% 8W / 5L / 3T · CI 36%–82% · no clear difference

Why judges preferred one side

PR as written vs production
Byte caps vs production
Recommended version (20 passages, no caps) vs production

Method and caveats

Prepared for the Qaf team. Internal; not indexed.