Skip to content

feat(recall): per-strategy retrieval boost via env config - #1974

Merged
nicoloboschi merged 1 commit into
mainfrom
feat/recall-strategy-boost
Jun 4, 2026
Merged

feat(recall): per-strategy retrieval boost via env config#1974
nicoloboschi merged 1 commit into
mainfrom
feat/recall-strategy-boost

Conversation

@nicoloboschi

Copy link
Copy Markdown
Collaborator

What

Adds HINDSIGHT_API_RECALL_STRATEGY_BOOSTS — a single env knob to prioritise one retrieval arm (semantic / bm25 / graph / temporal) over the others on recall. Requested by a user who wants to favour graph over semantic.

HINDSIGHT_API_RECALL_STRATEGY_BOOSTS="graph:0.1"            # favour graph
HINDSIGHT_API_RECALL_STRATEGY_BOOSTS="graph:0.1,semantic:-0.05"   # favour graph, deprioritise semantic

Empty/unset → complete no-op (current behaviour). Valid strategies: semantic, bm25, graph, temporal. Negative weights deprioritise. Malformed/unknown entries warn-and-skip rather than break recall.

How — two insertion points

The boost is applied in the two places it needs to land:

  1. Before the reranker cap (memory_engine.py, the 300-candidate pre-filter) — used as a weighted-RRF sort key so boosted-arm candidates survive the global candidate budget instead of being trimmed out by raw RRF score. Rank-aware: a candidate ranked Fix dependencies and docker #1 in the boosted arm gets more protection than ci: add upgrade tests #200. Only the sort key changes; rrf_score is left untouched for trace fidelity.

  2. After the reranker (right after apply_combined_scoring) — a flat additive bump to the final ranking weight, then the existing sort reorders. combined_score is left untouched; only weight (the ranking key) changes.

Design notes

  • Global, read via get_config() — mirrors the existing recall_max_candidates_per_source / reranker_max_candidates retrieval knobs. Not per-bank/hierarchical (keeps scope minimal; easy to promote later).
  • Boost math lives in a new pure module engine/search/recall_boost.py (unit-testable, out of the giant memory_engine). Parsing lives in config.py to avoid early-importing the heavy engine.search package.
  • Candidate→strategy attribution reuses the existing MergedCandidate.source_ranks ({"graph_rank": 3, ...}) produced by RRF — no new plumbing.

Tests

  • tests/test_recall_boost.py — 16 deterministic unit tests: env parsing (empty/valid/case/zero-drop/unknown/malformed/negative) and both boost functions (no-op, rank-awareness, flat additive, summing, non-matching arm, negative weights).
  • Related suites green: test_fusion_cap, test_config_validation, test_recall_config, test_reranker_score_normalization (75 passed). No LLM-behaviour change → no judge test needed.

Docs

  • configuration.md (+ skills mirror) documents the new env var.

@nicoloboschi
nicoloboschi force-pushed the feat/recall-strategy-boost branch 2 times, most recently from cbfa7b0 to 06fbd9c Compare June 4, 2026 09:44
Add HINDSIGHT_API_RECALL_STRATEGY_BOOSTS, a single env knob that lets a
deployment prioritise one or more retrieval arms (semantic/bm25/graph/temporal)
over the others using a human priority level — e.g. "graph:high" to strongly
favour graph hits, or "graph:high,semantic:low". Valid levels: low | medium |
high. A strategy listed without a level ("graph") defaults to medium; arms you
don't list keep their normal weight; empty disables the feature.

A named level (not a raw number) is the knob because the boost is applied in
two structurally different places on different score scales:
1. Before the reranker cap, as a weighted-RRF sort key, so boosted-arm
   candidates survive the global candidate budget instead of being trimmed by
   raw RRF score (rank-aware).
2. After the reranker, as a flat additive bump to the final ranking weight.

Level -> per-stage magnitudes (in engine/search/recall_boost.py) are tuned
against real recall traces (LoCoMo bank, 336 merged candidates -> 300-cap,
local ms-marco cross-encoder): the observed cap boundary RRF was ~0.0055, so
the stage-1 multipliers 1/3/6 map to rescue/promote/dominate; the cross-encoder
weight scale is [0,1] and bimodal, so the stage-2 additives 0.05/0.2/0.5 map to
nudge/compete/win-over-most-matches. A guard test keeps the level names in sync
with config. Global, read via get_config(), mirroring
recall_max_candidates_per_source.
@nicoloboschi
nicoloboschi force-pushed the feat/recall-strategy-boost branch from 06fbd9c to 03d5df7 Compare June 4, 2026 11:38
@nicoloboschi
nicoloboschi merged commit 0113404 into main Jun 4, 2026
79 checks passed
nicoloboschi pushed a commit that referenced this pull request Jun 5, 2026
…STS (#1974) (#1991)

#1974 added HINDSIGHT_API_RECALL_STRATEGY_BOOSTS (named low/medium/high
per-source boosts), making retrieval.md's absolute claim 'There are no
per-strategy weight multipliers' factually wrong. Scope the equal-weight
statement to RRF fusion itself, point readers to the boost knob, and note
at the pre-filter cap stage that boosted sources are more likely to survive.
Regenerated the skills mirror.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant