Search over a personal knowledge base fails in three different ways. An exact search misses a concept the author named differently. Full-text search ranks keyword density above the note that actually answers the question. Vector search finds things that feel adjacent and occasionally surfaces the thing that is adjacent. Hybrid retrieval exists because each lane covers the others’ blind spots, but it only stays trustworthy if you can still see which lane produced which result.
The question this article answers: how do you combine exact, text, and semantic search without losing the ability to explain a ranking? Wordcell’s wordcell search is the working implementation: a default hybrid mode that runs a live exact scan alongside a pinned local QMD index, fuses the ranked lists deterministically, and reports every lane’s contribution as inspectable evidence rather than a collapsed score.
three questions, three lanes
wordcell search exposes the lanes directly as modes rather than hiding them behind one opaque pipeline:
--mode exactscans the current Markdown for identity, phrase, and term matches: note IDs, titles, aliases, paths, tags, typed metadata, and prose. It is model-free: no index, no embedding, no service.--mode keyworduses QMD’s local full-text index: conventional FTS over the vault’s Markdown projection.--mode semanticselects QMD’s vector lane: embedding similarity over the same local projection.--mode hybrid, the default, runs the exact lane beside QMD’s combined retrieval and fuses the result lists.
The modes matter because the lanes are answering different questions. Exact search answers “where is this named thing”; keyword search answers “which notes talk in these terms”; semantic search answers “what is adjacent in meaning.” A retrieval system that cannot say which question it answered cannot be evaluated and cannot be trusted when it is wrong.
fusion without a shared score
The fusion problem is that the lanes’ scores are incomparable: an exact-match counter, an FTS rank, and a cosine similarity do not share a scale, and any weighted sum of raw scores smuggles in a calibration nobody validated. Wordcell’s answer is reciprocal-rank fusion (score each candidate by its position in each lane rather than its points), implemented as fuseRankedCandidates in src/search.ts. Each lane contributes weight / (k + rank) for every candidate it returned, the contributions sum per candidate, and the total is normalized by the maximum a single lane could produce. The fused score is a normalized rank statistic; the type comment states it outright: it is not a probability.
Two design choices keep the ordering honest. Exact title, alias, and path identities are pinned separately (a note whose identity matches the query is not allowed to be outranked by fuzzy agreement), and agreement between lanes outranks single-lane evidence, so a note both retrievers found beats a one-lane flash. Every returned hit carries an evidence array naming the lanes that produced it with their ranks, plus the full contributions breakdown that produced its final position. The ranking is a report, not a verdict.
lanes fail independently
The inspectability extends to failure. Each lane reports a diagnostic (ready, degraded, or unavailable, with a message), and any non-ready lane marks the whole result partial. A QMD failure does not erase exact results; a degraded embedding pass does not silently lower coverage. An underfilled filtered search reports itself degraded rather than padding the result with unrelated notes: QMD cannot rank against a path allowlist, so a filtered search uses a bounded global candidate window, and rows discarded by live reconciliation or metadata filters leave the request explicitly degraded.