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):
- 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.
- 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 = mean1/(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):
-
RRF-k sweep.
persona-bench --features real(real BGE-M3, the 6-persona haystack growing 77 → 463 entities, 20 fixed needles) sweptk ∈ {20,40,60}:k recall@1 (final tier) recall@10 (final tier) recall@1 across tiers 60 0.85 1.00 0.75/0.90/0.85/0.85/0.85/0.85 40 0.85 1.00 identical to k=60 20 0.85 1.00 identical to k=60 recall@1is identical for every k at every tier;recall@10differs only at the smallest, noisiest tier (k=20: 1.00 vs 0.95, a single needle out of 20) and never on the headlinerecall@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_Kremains a bench-only knob. -
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:
- 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.
- 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.
- Parallel legs. Run the independent legs (FTS, vector) concurrently, each
on its own SQLite connection (rusqlite
Connectionis notSync), 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. - 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).
- 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 ifpersona-benchshows a systematic per-leg bias. Default stays equal weights (the honest prior). No global title>body field weighting. - RRF-k sweep. Treat
k(currently 60) as a bench-tuned global constant; sweep {20,40,60} onpersona-benchand 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).