Understanding the FTS5 + Entity-Match + Link-Neighbour RRF Retrieval Pipeline in ai-memory
The ai-memory retrieval pipeline combines FTS5 full-text search, entity-based lexical matching, and link-neighbour graph traversal using Reciprocal Rank Fusion (RRF) to produce a single deterministic ranking of candidate pages.
The akitaonrails/ai-memory repository implements a sophisticated hybrid search system that fuses multiple independent retrieval streams into one coherent result set. This FTS5 + entity-match + link-neighbour RRF retrieval pipeline processes queries through distinct lexical and structural channels before applying a mathematically rigorous fusion algorithm to determine final relevance.
The Four-Stream Retrieval Architecture
The pipeline operates on three always-active streams plus one conditional stream when vector embeddings are configured. Each stream generates an independent 1-based rank for matching documents.
FTS5 Full-Text Search
The FTS5 stream leverages SQLite's native FTS5 extension to perform BM25-styled ranking over page content. In crates/ai-memory-store/src/reader.rs, this stream executes full-text queries against the built-in FTS5 index and returns raw BM25 scores that are subsequently transformed into rank positions for fusion.
Entity-Match Lexical Indexing
The entity-match stream queries a specialized lexical index built from the entities front-matter field of each markdown page. This index stores normalized noun tokens extracted during ingestion. When a query matches entity tokens, the stream produces a distinct rank based on inverse-frequency scoring, separate from the BM25 scores generated by FTS5.
Link-Neighbour Graph Traversal
The link-neighbour stream operates on the wiki-link graph structure implicit in markdown connections. A page becomes a candidate if it neighbours (links to or is linked by) a page that directly matches the query. The graph distance determines the neighbour's rank contribution, capturing structural relationships that purely lexical searches miss.
Optional Vector Similarity (Fourth Stream)
When AI_MEMORY_EMBEDDING_PROVIDER is configured, a fourth stream activates to compute cosine similarity between query embeddings and pre-computed page vectors. According to the architecture documentation, this cosine similarity rank becomes an additional RRF contribution alongside the three core streams.
Reciprocal Rank Fusion Implementation
The core fusion logic resides in crates/ai-memory-store/src/reader.rs at lines 3649-3663. For each document d, the system calculates:
score(d) = Σ 1 / (k + rank_i(d))
Where k = 60 (the RRF constant) and the summation runs across all active streams. A lower rank (e.g., 1st place) yields a higher reciprocal contribution. Documents appearing in multiple streams accumulate higher fused scores, surfacing pages that demonstrate relevance across lexical, entity, and structural dimensions.
Authority Multiplier and Post-Processing
After RRF fusion, the pipeline applies a bounded authority multiplier defined in crates/ai-memory-store/src/reader.rs lines 3753-3839. This multiplier adjusts scores based on:
- Page kind (explanatory vs. reference vs. journal)
- Tier (hierarchical importance)
- Pinned status (manually prioritized content)
- Front-matter tags (categorical metadata)
The MCP server endpoint in crates/ai-memory-mcp/src/server.rs (lines 1810-1815) exposes this pipeline via the memory_query HTTP endpoint. Additionally, the system supports optional LLM-based reranking through crates/ai-memory-llm/src/reranker.rs, which can reorder results after the initial RRF computation when the reranker option is enabled.
Practical Usage Examples
Basic Query Using Default Streams
Query the default three-stream pipeline (FTS5 + entity + graph) without vector search:
use ai_memory_mcp::client::MemoryClient;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let client = MemoryClient::new("http://127.0.0.1:49374")?;
let resp = client
.memory_query("how does the recall-eval test work?")
.await?;
println!("{:#?}", resp.hits);
Ok(())
}
Enabling Vector-Based RRF
Activate the fourth stream by configuring an embedding provider before server startup:
// Set environment variables before initializing the server
std::env::set_var("AI_MEMORY_EMBEDDING_PROVIDER", "openai");
std::env::set_var("OPENAI_API_KEY", "sk-…");
// Queries now automatically include cosine similarity in the RRF calculation
Requesting Per-Hit Explanations
Inspect individual stream contributions and graph provenance using the explain flag:
let resp = client
.memory_query("explain: what is the RRF pipeline")
.explain(true)
.await?;
println!("{:#?}", resp.explain);
The explanation includes per-stream ranks, matched entities, raw BM25 scores, RRF contributions, and link-neighbour graph traversal paths as documented in ARCHITECTURE.md.
Summary
- FTS5 + entity-match + link-neighbour RRF retrieval pipeline combines three orthogonal retrieval signals into a unified ranking using Reciprocal Rank Fusion with k=60.
- Each stream (FTS5 BM25, entity lexical matching, graph neighbours, optional vector cosine) generates independent 1-based ranks that are fused via the formula Σ 1/(60 + rank).
- Post-fusion authority weighting in
reader.rsadjusts results based on page metadata, tier, and pinned status. - The architecture is fully deterministic and explainable, with optional LLM reranking available for final refinement.
- Client implementations interact through the MCP server endpoint defined in
server.rs, with configuration managed via environment variables for embedding providers.
Frequently Asked Questions
How does the RRF formula handle documents that appear in multiple streams?
Documents appearing in multiple streams receive cumulative contributions from each stream's reciprocal rank. A document ranked 1st in FTS5 and 3rd in entity-match contributes 1/61 + 1/63 to its final score, while a document ranked 1st in only one stream contributes only 1/61. This multiplicative effect naturally boosts pages that demonstrate relevance across lexical, entity, and structural dimensions simultaneously.
What is the significance of the k=60 constant in the RRF implementation?
The constant k = 60 serves as a damping factor that prevents top-ranked documents from dominating the fused score entirely. By adding 60 to each rank in the denominator, the algorithm ensures that lower-ranked documents (e.g., rank 50 vs. rank 1) still contribute meaningful scores to the final ranking. This value is hardcoded in reader.rs lines 3649-3663 and follows established information retrieval best practices for RRF stability.
Can I disable specific streams like link-neighbour graph search?
The three core streams (FTS5, entity-match, link-neighbour) are always active in the current implementation as defined in the architecture. However, the optional vector stream only activates when AI_MEMORY_EMBEDDING_PROVIDER is explicitly configured. To effectively disable graph influence, you would need to filter results post-query, though the pipeline itself does not expose per-stream toggles in the public API exposed by server.rs.
Where is the LLM reranking logic implemented relative to the RRF fusion?
The optional LLM reranker operates as a post-processing step after RRF fusion and authority weighting complete. Located in crates/ai-memory-llm/src/reranker.rs, this component receives the already-fused and authority-adjusted result list, then uses language model calls to potentially reorder the top-k candidates. It does not replace the RRF calculation but rather refines its output when explicitly requested via the reranker option in the query configuration.
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 →