Context-Injection Ranking — what actually helps (2025-2026)¶
Date: 2026-06-24
Trigger: ARGUS-76 cross-encoder re-rank shipped behind a flag, then the on/off
eval (ARGUS-87) measured a wash: ~+6% precision@12 but ~-10% recall@12 with
bge-reranker-base. This doc is the research into why, and what to do instead.
TL;DR — the reframe¶
The problem is set selection, not ordering. Modern LLMs (Claude included) are increasingly order-insensitive — they reason over multi-chunk context robustly regardless of rank order. A cross-encoder optimizes order / precision@K, which is the thing the consuming model cares least about. So reranking is the wrong lever for injection-into-an-agent. The field has moved to adaptive selection of a small, high-precision set + lost-in-the-middle mitigation.
(a) Why our reranker washed — expected, not a config bug¶
- Domain mismatch + model size. Off-the-shelf cross-encoders degrade
out-of-distribution; OOD generalization needs larger models.
bge-reranker-baseis the small end, and our corpus (CC sessions, dev decisions, syntheses) is OOD vs its web/QA training. Reranking a noisy candidate pool produces exactly our symptom — recall decline + high-scoring false positives. (Déjean et al. 2024, arXiv:2403.10407.) - Rerankers optimize the wrong objective. They optimize ranking metrics (NDCG/MRR), not downstream decision correctness (CalibRAG). A listwise LLM reranker matched but did not beat a simpler approach on a real downstream metric, at +40% latency / +15% cost (Fin.ai A/B report).
- Order-insensitivity. GPT-4o / Claude 3 / Gemini 1.5 handle multi-chunk reasoning regardless of order, undercutting reranking's main benefit.
(b) Lost-in-the-middle — this challenges maxNotes=12¶
Injecting more notes (high recall) actively hurts generation: relevant info in the middle of a long context is under-attended — a U-shaped accuracy curve (Liu et al. 2023). A benchmark showed RAG accuracy drop 33% when the relevant chunk sat in the middle of 10. Current guidance: fewer, higher-precision chunks (3-5), don't dump 12-20.
This is in direct tension with our maxNotes 8->12 bump, which optimized retrieval recall. 12 notes may be past the point where the consuming model loses the middle ones. Retrieval wants recall; generation wants few-and-precise. We optimized only the retrieval side. Re-examine maxNotes; let adaptive-K set it per query.
What actually solves "precision without losing recall" (priority order)¶
- Adaptive-K (top recommendation). Replace the fixed cap with a dynamic cutoff from the score distribution / a confidence gap — 3 notes on a focused query, more on a broad one. Solves the exact problem: no force-drop of relevant (it's adaptive), no junk dump (it's thresholded). Plug-and-play, single-pass, no training, runs on the pgvector scores we already compute. (Adaptive-k, Taguchi et al. 2025, arXiv:2506.08479; Cluster-based Adaptive Retrieval, arXiv:2511.14769; CRAG confidence evaluator.)
- Lost-in-the-middle reordering (cheap). Place the top notes at the start and end of the injected block, not the middle. Trivial; our injection is already an ordered block.
- Structural signals. Shared-entity / same-project / same-file co-retrieval (Mem0's real lever). We have project-boost; file-path + entity matching are reliable dev-memory cues that cosine misses.
- Temporal validity, not recency decay. Suppress superseded facts (a reversed decision, a changed preference) — Zep's win. Distinct from recency decay, which we tested and which hurt (dev knowledge is durable). About correctness, not freshness.
- HyDE as a recall fallback for short/vague prompts, gated on low first-stage confidence only (it adds +25-60% latency + hallucination risk; not always-on). (Gao et al. 2022; medical fine-tune +4.9% NDCG@10, Li et al. 2024.)
- Cross-encoder: keep OFF. Correct from the eval, now backed by the literature. If ever revisited, fine-tune on our own grade labels rather than a bigger off-the-shelf model.
(c) What production agent-memory systems actually do (2025-2026)¶
None rank by cross-encoder.
- Mem0: semantic + BM25 fusion (≈ exactly our RRF) + shared-entity co-retrieval.
- Zep/Graphiti: + temporal validity windows + graph traversal + community summaries (beats Mem0 on temporal benchmarks).
- Letta/MemGPT: agent-driven search (the agent decides what to page in), tiered store, no fixed ranker.
The field ranks by structural + temporal signals + LLM/agent selection, not rerankers. External confirmation our energy belongs in (1)-(4).
(d) Evidence caveats (honesty)¶
Much of the downstream-outcome evidence is industry blogs, not peer-reviewed — treat percentages loosely. Reranking does help some QA tasks; task type is the decisive variable. Lost-in-the-middle is being mitigated by newer order-robust models, shrinking its severity. But the direction — adaptive selection over reranking — is consistent across academic and practitioner sources.
Decision for Argus¶
Pivot from "make the reranker work" to set selection: ship adaptive-K (biggest, best-evidenced win) + start/end reordering, then structural signals; re-examine maxNotes=12. Leave the reranker built-but-off (ARGUS-85/86 are merged and flag-gated, so they cost nothing idle). Higher-leverage and pgvector-native vs chasing a bigger cross-encoder.
M1 card update (ARGUS-1900/ARGUS-1962/ARGUS-1966, 2026-09-11)¶
The ARGUS-87 wash above was re-tested at a larger corpus size against a
labeled-verdict probe-eval-jobs replay (bge-reranker-base, n=800, ks
5/8/10/12/15/20, checkpoint/resume-backed job 4, clean arm confirmed via
rerank_failures: 0, wall-clock 31.64s, status done, no restart during
execution — the run completed straight through and never exercised the
ARGUS-1941 checkpoint/resume mechanism). Precision@8 off 0.6304 -> on 0.6346
(+0.0042 lift), against the +0.05 gate; recall@20 off 0.98625 -> on 0.98625
(no regression, identical to four decimal places).
This confirms ARGUS-1900's original M1 verdict (precision@8 off 0.6349 -> on
0.6326, -0.0023) even after that number was re-measured through a clean
(non-degraded) reranker arm rather than the silent-RRF-fallback path
ARGUS-1961 later found and fixed the reporting for. Both the original and
the re-run lift sit within noise of zero and nowhere near the +0.05 gate
(sampling noise on precision@8 at n=800 is ~0.017, so +0.0042 is a quarter
of one standard error) — the sign flipped between runs but the magnitude
did not, which is exactly what "no real effect" looks like at this sample
size. Decision: recall_rerank_enabled stays OFF in live recall. No
config flip made; the gate was not met.
M4 card update (ARGUS-1901, 2026-09-11)¶
ARGUS-1901 depended on ARGUS-1900's outcome either way (flip the ordering, or
document no-lift). The M1 card above already settled that: the reranker gate
was not met and recall_rerank_enabled stayed OFF, so the "post-1900
ordering" IS the same RRF-fused order the injection cap was already tuned
against in ARGUS-75 — no new ordering to wait on.
GET /recall/probe-eval?ks=5,8,10,12,15,20 (pure SQL over the accumulated
recall_probe_verdicts window, no LLM call) against the live corpus:
| K | avg precision | avg recall |
|---|---|---|
| 5 | 0.7390 | 0.3357 |
| 8 | 0.7006 | 0.4934 |
| 10 | 0.6775 | 0.5868 |
| 12 | 0.6568 | 0.6755 |
| 15 | 0.6284 | 0.7987 |
| 20 | 0.5941 | 0.9931 |
(5,499 graded prompt-mode injections — every point shares the same denominator, so this is one controlled sweep, not independently-sampled numbers.) These numbers are well above the epic's original framing (precision ~0.40, ~5 relevant notes dropped/query) — that framing predates several since-landed precision wins on this same path (ImportanceWeight ARGUS-89, RecencyHalfLife ARGUS-1902) and is superseded by this measurement, not reconciled with it.
K=20 is the ceiling of what /recall/probe-eval can currently measure: the
grade cron's miss-probe re-runs recall at missProbeLimit = 20 notes wide
(recall_grade.go), so no judged data exists past rank 20 to sweep further.
Recall@20 (0.9931) is effectively complete within that window — going from
K=12 to K=20 recovers nearly all of the remaining judged-relevant notes
(recall 0.676 -> 0.993) for a graceful, not cliff-edge, precision cost (0.657
-> 0.594), the same shape of trade ARGUS-75's 8->12 bump made on this same
lever. This matches the epic's own mandate ("attack capacity (inject
more)... with cheap, no-hot-path-cost levers") more directly than
AdaptiveKAlpha does here: AdaptiveKAlpha needs a per-note score distribution
to cut on, and recall_probe_verdicts stores judged relevance by RANK only
(no score column) — there is no accumulated data to calibrate an alpha
against, only a flat K is measurable with the tooling that exists today.
Decision: raise the injection cap (promptHookMaxNotes, prompt_hook.go)
12 -> 20. recall_floor (PromptRelevanceFloor, tuning.go) stays at its
default 0.015 — probe-eval has no per-note Score column to evaluate a floor
change against (see the ARGUS-1901 correction added to that constant's own
doc comment: the floor's original RRF-scale calibration basis is stale
relative to what HybridRecall's fused Score actually contains today, but
fixing that gap needs recall_probe_verdicts to persist Score alongside
rank — left as a named follow-up, not built into this change).
Known follow-on caveat, not fixed by this task: missProbeLimit (=20)
now equals the new injection cap (=20), so the grade cron's miss-probe window
no longer has headroom beyond what is already injected — future probe-eval
runs will see probed_count/miss_count trend toward zero going forward
even though those columns' PAST accumulated rows (measured against the old
12-cap) remain valid and are exactly what the sweep above used. Re-widening
missProbeLimit past the new cap (e.g. to 30) to restore that telemetry is
real follow-up work for a future M5, not done here (this task's acceptance is
the cap/floor operating point, not the grading cron's own instrumentation).
recall_grade_history accumulates automatically off the grading cron
(GradeRecall, internal/memory/internal/store/recall_grade.go) — this PR's
own merge/deploy does not itself write a row. The visible discontinuity this
change causes (from more, lower-ranked notes now getting graded from rank
13-20 that were dropped by the cap before) will show up as new
recall_grade_history rows dated after this change deploys; see the PR's
ACCEPTANCE section for how that clause is tracked.
Sources¶
- Déjean et al. 2024, cross-encoder/LLM/SPLADE comparison — arXiv:2403.10407
- Lost in the middle (Liu et al. 2023) — via arXiv:2512.14313 (Dynamic Context Selection)
- Adaptive-k (Taguchi et al. 2025) — arXiv:2506.08479
- Cluster-based Adaptive Retrieval (CAR) — arXiv:2511.14769
- HyDE (Gao et al. 2022); medical iterative fine-tune (Li et al. 2024)
- Production framework comparisons (Mem0/Zep/Letta) — particula.tech, agentmarketcap.ai, 2026
- CalibRAG; Fin.ai listwise-rerank A/B; CRAG; Self-RAG (practitioner + arXiv)