How Reciprocal Rank Fusion Works in ai-memory: Multi-Stream Retrieval Explained
Reciprocal Rank Fusion (RRF) in ai-memory combines independent full‑text, entity, graph, and vector retrieval streams into a single relevance ranking using the classic formula Σ 1/(k + rank) with a constant k = 60.
The ai-memory project (akitaonrails/ai-memory) implements a hybrid search architecture that merges multiple retrieval signals without requiring learned weights or complex training pipelines. By applying Reciprocal Rank Fusion, the system balances lexical matching, semantic similarity, and structural graph relationships into one coherent result set. This approach is particularly effective for personal knowledge bases where queries may require exact matches, conceptual similarity, or navigational context.
The RRF Algorithm in ai-memory
The Mathematical Foundation
At the heart of ai-memory’s retrieval system lies the standard RRF scoring formula:
[ \text{score}(d) = \sum_{i=1}^{N} \frac{1}{k + \text{rank}_i(d)} ]
Where:
- k = 60 – a constant smoothing factor (explicitly defined in the source code comments)
- rank_i(d) – the 1‑based position of document d in stream i
- N – the total number of active retrieval streams (between three and four depending on configuration)
If a document does not appear in a particular stream, its contribution is treated as zero and ignored in the summation. The final RRF score is the arithmetic sum of all per‑stream contributions, with higher values indicating consensus across multiple retrieval methods.
The Four Retrieval Streams
ai-memory fuses results from up to four independent streams:
- FTS5 (Full‑Text Search) – BM25‑based ranking over page content and titles
- Lexical Entity Matching – Exact and fuzzy matching against extracted noun phrases and named entities
- Graph‑Neighbour Expansion – Pages reachable via wikilinks, ranked by hop distance from seed nodes
- Vector Cosine Similarity – Optional dense retrieval using pre‑computed embeddings (requires
AI_MEMORY_EMBEDDING_PROVIDERconfiguration)
Each stream returns its own ranked list, which the fusion layer normalizes into RRF contributions without requiring score calibration between heterogeneous metrics.
Implementation Details
Core Fusion Logic in reader.rs
The primary fusion implementation resides in crates/ai-memory-store/src/reader.rs. According to the source code comments, the reader computes "Per‑stream RRF contributions (1/(k+rank), k=60) for one hit" before aggregating them into a final fused score.
Key implementation characteristics from the source:
- The fusion occurs after individual stream execution but before authority weighting
- Total fused score calculation: "Total fused score (sum of the RRF contributions) — higher is better" as noted in the reader implementation
- The RRF fuse is applied as a distinct pipeline stage: "RRF fuse: score(d) = Σ 1/(k + rank_i(d))" followed by authority multipliers
The implementation handles missing streams gracefully—if vector search is disabled, the fusion simply sums contributions from the three available streams without requiring conditional logic changes.
API Integration
The public HTTP API endpoint defined in crates/ai-memory-web/src/routes/api.rs exposes this functionality through the memory_query route. As documented in the source, this endpoint "RRF‑fuses FTS5 + entity matching + cosine over stored embeddings + link‑graph" to return a unified result set.
The architecture documentation in docs/ARCHITECTURE.md confirms this design, stating that the system answers queries "via FTS5 + entity‑match + graph RRF … optional vector RRF", establishing RRF as the canonical method for multi‑signal retrieval in the codebase.
The Retrieval Pipeline Step-by-Step
The ai-memory query engine processes RRF fusion through the following deterministic stages:
-
Execute Streams Independently – Each retrieval method (FTS5, entity index, graph walk, optional vector) runs in parallel against the query, producing separate ranked lists with raw scores.
-
Convert Ranks to RRF Contributions – For every hit in each stream, the system calculates
1 / (60 + rank). First‑place results (rank = 1) contribute approximately 0.0164, while tenth‑place results contribute 0.0143. -
Sum Cross‑Stream Contributions – Documents appearing in multiple streams accumulate higher RRF scores through simple arithmetic summation. A document ranked 5th in two streams (contribution ≈ 0.0154 each) outranks a document ranked 1st in only one stream (contribution ≈ 0.0164).
-
Apply Authority Multiplier – The raw RRF score is multiplied by a bounded authority factor derived from page metadata (kind, tier, pinned status, and tag‑based authority).
-
Optional LLM Reranking – When
AI_MEMORY_RERANKER=llmis configured, the top‑k candidates from the RRF stage are passed to an LLM for final relevance scoring before presentation. -
Return with Explanation – When
explain=true, the response includes per‑stream ranks, raw scores, and individual RRF contributions for transparency.
Why RRF for Memory Retrieval?
ai-memory selects Reciprocal Rank Fusion over learned rankers or weighted linear combinations for three specific reasons grounded in the repository’s design philosophy:
Simplicity – The implementation requires only basic arithmetic operations without neural training or parameter tuning. As evidenced by the minimal code footprint in reader.rs, the fusion logic adds negligible computational overhead.
Robustness – Streams can fail or remain unconfigured (e.g., missing embedding provider) without breaking the fusion. The summation naturally handles absent streams by treating missing documents as zero contributions.
Recall Optimization – Documents that appear moderately early across several streams often outrank those that dominate a single stream. This characteristic improves recall for diverse query formulations—critical for personal knowledge bases where the same concept may appear as structured metadata, linked references, or body text.
Querying with RRF
The following Rust examples demonstrate how to interact with ai-memory’s RRF‑based retrieval:
use ai_memory_mcp::client::MemoryClient;
// Standard query - automatically applies RRF across FTS, entity, and graph streams
let mut client = MemoryClient::new("http://127.0.0.1:49374")?;
let response = client.memory_query("how to reset ai-memory").await?;
println!("Top hit: {}", response.hits[0].page_path);
To inspect the fusion mechanics and per‑stream contributions:
// Enable explanation to see RRF calculation details
let response = client
.memory_query_explain("how to reset ai-memory", true)
.await?;
for hit in response.hits {
println!(
"Page: {}\n RRF score: {:.4}\n Stream details: {:?}",
hit.page_path,
hit.score_details.fused_score,
hit.score_details.per_stream // Contains rank, raw_score, rrf_contribution per stream
);
}
To include vector similarity in the fusion, configure the embedding provider before querying:
std::env::set_var("AI_MEMORY_EMBEDDING_PROVIDER", "openai");
std::env::set_var("OPENAI_API_KEY", "sk-placeholder");
let response = client.memory_query("semantic search example").await?;
println!("Hybrid RRF top hit: {}", response.hits[0].page_path);
The client communicates with the MCP server defined in crates/ai-memory-mcp/src/server.rs, which dispatches to the store’s reader for actual RRF computation as noted in the source comment "Hybrid search: RRF‑fuse FTS5 results with cosine‑similarity".
Summary
- Reciprocal Rank Fusion in ai-memory uses the formula Σ 1/(60 + rank) to merge heterogeneous retrieval signals into a single relevance score.
- The implementation in
crates/ai-memory-store/src/reader.rsfuses up to four streams: FTS5 full‑text search, lexical entity matching, graph‑neighbour expansion, and optional vector cosine similarity. - k = 60 serves as the constant smoothing factor, with per‑stream contributions calculated as reciprocals of the adjusted rank.
- The pipeline applies RRF fusion before authority multipliers (kind/tier/pinned/tag) and optional LLM reranking.
- Documents missing from specific streams are handled gracefully, receiving zero contribution for those streams without breaking the aggregation.
Frequently Asked Questions
What is the constant k in ai-memory's RRF implementation?
The constant k is set to 60, as explicitly documented in the source code comments within crates/ai-memory-store/src/reader.rs. This value follows the classic RRF recommendation, providing sufficient smoothing to prevent top‑ranked documents from completely dominating the fused score while maintaining discrimination between lower ranks.
Which retrieval streams does ai-memory fuse with RRF?
According to the architecture documentation and API source in crates/ai-memory-web/src/routes/api.rs, ai-memory fuses FTS5 (full‑text search), lexical entity matching, graph‑neighbour expansion, and optional vector cosine similarity. The vector stream requires explicit configuration of an embedding provider; when disabled, the system seamlessly fuses the remaining three streams.
How does ai-memory handle documents that appear in only some streams?
Documents receive zero contribution from streams where they do not appear, and the RRF score is computed as the sum of contributions only from streams containing the document. This design ensures that missing streams (such as disabled vector search or empty graph results) do not penalize documents or break the fusion logic.
Does ai-memory support LLM reranking after RRF fusion?
Yes. When the environment variable AI_MEMORY_RERANKER is set to llm, the system passes the RRF‑fused candidate pool to a language model for final relevance scoring. This occurs after the RRF fusion and authority multiplier stages, serving as a refinement layer rather than a replacement for the rank fusion algorithm.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →