Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ADR-044: Composite parallel search + deterministic relevance calibration

Date: 2026-07-06 (accepted 2026-07-06 after the persona-bench k-sweep) Status: Accepted (SYN-306 shipped) Author: Rômulo Febasi

Scopes SYN-306 (v1.2 retrieval quality). Extends ADR-042 (chunking + reranker) and the SYN-86 RRF hybrid. Motivated by an owner request (“run more than one kind of search at once, async not chained, return a precision/compatibility signal, weighted variations are fine”) and a 2026 IR research pass.


Outcome (SYN-306 shipped)

Shipped, unconditionally (no ranking change):

  1. Parallel legs. The two independent legs: FTS (lexical) and vector (semantic): run concurrently: the vector leg on its own thread + own SQLite connection, the FTS leg on the caller’s connection meanwhile; the graph leg follows (it seeds from both). Fusion is a pure function of the per-leg orders, so the fused result is identical and deterministic regardless of thread timing: proven by a repeat-run test and by three byte-identical real-model search runs. The win is architectural clarity + overlap of the two SQLite legs; at single-user scale the embedding forward pass dominates latency, as predicted.
  2. Compatibility score. The SYN-304 band is refined into a bounded [0,1] score, 0.6·agreement + 0.4·position (agreement = fraction of the three legs that surfaced the hit; position = mean 1/(1+rank) over those legs). Surfaced per hit over both doors. Deterministic; explicitly not a calibrated probability.

Measured, then held at the default (evidence-gated):

  1. RRF-k sweep. persona-bench --features real (real BGE-M3, the 6-persona haystack growing 77 → 463 entities, 20 fixed needles) swept k ∈ {20,40,60}:

    krecall@1 (final tier)recall@10 (final tier)recall@1 across tiers
    600.851.000.75/0.90/0.85/0.85/0.85/0.85
    400.851.00identical to k=60
    200.851.00identical to k=60

    recall@1 is identical for every k at every tier; recall@10 differs only at the smallest, noisiest tier (k=20: 1.00 vs 0.95, a single needle out of 20) and never on the headline recall@1. Decision: keep the canonical k=60. The sweep is not a systematic win; RRF’s well-known k-robustness holds at this scale. SYNAPSE_RRF_K remains a bench-only knob.

  2. Weighted RRF. No per-leg bias surfaced in the sweep (both legs already drive recall@1 = 0.85 / recall@10 = 1.00), so weights stay equal: the honest default. The knob is not shipped; a future bench that shows a systematic per-leg bias would revisit it.


Context

Search today (src/core/search/hybrid.rs) already runs three retrieval legs (FTS5/BM25, dense vector (BGE-M3 chunks), and graph one-hop) but it runs them sequentially in one function and fuses them with unweighted RRF (k=60), returning a rank-only list. SYN-304 added a per-hit relevance signal (matched_by + a strong/good/partial band from leg agreement), which is a first step, but two owner asks remain:

  1. Parallelism. The independent legs (FTS, vector) should run concurrently, not chained: at single-user scale search is already fast, but the composite design should be genuinely parallel where the legs are independent.
  2. A relevance/compatibility score, so the agent can judge whether results are on-topic, from multiple search variations (weighted variations are acceptable) rather than one path.

A hard prior from the field research (see SYN-305 findings): BM25 k1/b are not tunable in FTS5 (compiled constants), calibrated-score fusion (RSF/DBSF) is a poor fit because the graph leg has no calibratable score and there is no labelled eval set to choose a distribution, and global title>body field weighting is wrong (the owner noted a query is often about the body). So the design space is: parallel execution, weighted RRF variations, a deterministic compatibility signal, and an RRF-k sweep, not learned fusion.

Decision (proposed: evidence-gated)

Evolve the hybrid search into a composite, parallel retriever with a deterministic relevance calibration, keeping RRF’s rank-only robustness.

  1. Parallel legs. Run the independent legs (FTS, vector) concurrently, each on its own SQLite connection (rusqlite Connection is not Sync), then the graph leg (which seeds from FTS+vector) after. Deterministic: the fused order is a pure function of the per-leg orders, independent of thread timing.
  2. Deterministic compatibility score, not calibrated fusion. Extend the SYN-304 signal into a bounded, explainable score derived from **leg agreement
    • rank position within each leg** (e.g. how many legs ranked it and how high), surfaced per hit. Reject RSF/DBSF and any trained ranker, the graph leg has no score to normalise and there is no eval set (keeps ADR-029’s no-opaque-ML line).
  3. Weighted RRF as an optional, bench-gated knob. Allow per-leg weights in the fusion (wᵢ · 1/(k+rank)) (deterministic, no artifact) but ship weights ≠ 1 only if persona-bench shows a systematic per-leg bias. Default stays equal weights (the honest prior). No global title>body field weighting.
  4. RRF-k sweep. Treat k (currently 60) as a bench-tuned global constant; sweep {20,40,60} on persona-bench and pick the precision-best. One constant, never per-query.

Every change is benchmark-gated (ship only on a measured recall@1/precision win with no recall@10 regression, ADR-042 discipline) and disposable-index only.

Consequences

  • Improves: the agent gets a real “how compatible is this result” signal from multiple deterministic search variations; independent legs run concurrently; fusion can be tuned within RRF without a trained artifact. All on-brand (deterministic, local, explainable).
  • Worsens / limits: parallel legs add per-thread connections + coordination (modest at single-user scale: the win is architectural clarity, not latency); a compatibility score risks over-trust if presented as a probability: it must stay a banded, explainable signal, never calibrated.
  • Open: exact score formula (leg agreement × normalised rank); whether parallelism is worth the complexity at this scale (measure first); per-leg weight values (only if the bench demands).

Alternatives considered

  • Relative-score / distribution fusion (RSF / DBSF). Rejected: the graph leg emits a link-count with no calibratable scale, and there is no labelled eval set to choose a distribution: RRF’s rank-only design is why the three heterogeneous legs fuse soundly today.
  • Global title>body field weighting (BM25F-lite). Rejected per owner: a query is often about the body; a global title bias hurts those. Field weighting, if ever, belongs inside a weighted variation, not the default.
  • Tune FTS5 k1/b. Not possible (compiled constants); a C extension is a native dep not worth ≤15% for.
  • A trained learning-to-rank / calibrated confidence model. Rejected: opaque, off-ADR-029, and single-user data is too sparse (see the SYN-305 LTR note).