How OmniRoute Uses FTS5 + Qdrant for Conversational Context Memory
OmniRoute implements a dual-backend memory system that combines SQLite FTS5 for fast keyword search and Qdrant for semantic vector retrieval, enabling both exact-match browsing and similarity-based recall across conversation histories.
The OmniRoute routing engine for LLM applications requires persistent, queryable memory to maintain conversational context across sessions. According to the OmniRoute source code, this is achieved through a hybrid architecture where two specialized storage layers handle different retrieval strategies—full-text search via SQLite FTS5 and semantic search via Qdrant—unified under a single memory manager API.
SQLite FTS5: Fast Keyword-Based Memory Retrieval
OmniRoute's full-text search backend leverages SQLite's built-in FTS5 virtual table module for rapid, relevance-ranked keyword lookups against recent chats and user-provided notes.
FTS5 Schema and Migration
The FTS5 infrastructure is established through migration src/lib/db/migrations/022_add_memory_fts5.sql, which creates a virtual table indexing the content column of memory entries. This allows SQLite's BM25 ranking algorithm to score matches naturally.
CRUD Operations in sqliteBackend.ts
The src/lib/memory/sqliteBackend.ts module implements the complete lifecycle:
upsert— writes to both the regularmemorytable and the FTS5 indexsearch— executesMATCHqueries returning BM25-ranked resultsdelete— removes entries from both tables to maintain index consistency
import { searchFullTextMemory } from "@/lib/memory/sqliteBackend";
const results = await searchFullTextMemory("roadmap Q4");
FTS5 serves use cases requiring exact phrase matching, prefix queries, or recent conversation browsing where users remember specific keywords.
Qdrant: Semantic Vector Search for Similarity Recall
For conceptual similarity search across the full conversation history, OmniRoute integrates Qdrant as a remote vector database, storing dense embeddings that enable nearest-neighbor retrieval.
Configuration and Connection
The src/lib/memory/qdrant.ts module constructs connection parameters from the Settings table or environment variables (QDRANT_HOST, QDRANT_PORT, QDRANT_API_KEY):
// Qdrant configuration loaded from environment or database settings
const config = buildQdrantConfig(); // implemented in qdrant.ts
Vector Upsert Flow
The upsertSemanticMemoryPoint function orchestrates embedding generation and storage:
- Calls
createEmbeddingResponseto generate a dense vector from content - Stores the vector with payload metadata via
/collections/<collection>/points
import { upsertSemanticMemoryPoint } from "@/lib/memory/qdrant";
await upsertSemanticMemoryPoint({
id: "mem-123",
apiKeyId: "user-abc",
sessionId: "sess-456",
key: "project-xyz-notes",
content: "Discussed roadmap for Q4, decided on feature-flags.",
metadata: { tags: ["roadmap", "q4"] },
createdAt: new Date().toISOString(),
expiresAt: null,
});
Semantic Search Implementation
The searchSemanticMemory function:
- Embeds the query text using the same embedding service
- Issues
/points/searchrequests with optional filtering byapiKeyIdorsessionId - Returns ranked nearest neighbors with payload metadata
import { searchSemanticMemory } from "@/lib/memory/qdrant";
const { ok, results } = await searchSemanticMemory("feature-flags plan", 5);
if (ok) console.log(results);
Quantization for Performance
Qdrant collections support optional quantization via buildQuantizationConfig and searchQuantizationParams:
| Quantization Type | Use Case |
|---|---|
none |
Maximum accuracy, higher RAM |
int8 |
Balanced compression with rescoring |
binary |
Minimal footprint, fastest search with rescoring |
Hybrid Retrieval via the Memory Manager
The src/lib/memory/manager.ts module exposes a unified API that orchestrates both backends based on request context:
| Method | Backend Selection |
|---|---|
retrieveMemory |
Chooses FTS5 or Qdrant via type flag; supports hybrid pipelines |
upsertMemory |
Routes to appropriate backend based on memory kind |
cleanupMemory |
Applies retention policies across both stores |
Hybrid Search Pattern
UI components can execute combined retrieval:
- Execute semantic top-K search in Qdrant for conceptual relevance
- Apply keyword refinement using FTS5 filter
- Merge and rank for delivery
import { retrieveMemory } from "@/lib/memory/manager";
const { ok, results } = await retrieveMemory({
query: "roadmap",
topK: 10,
scope: { apiKeyId: "user-abc" },
});
Persistence and Maintenance
Both backends guarantee durability across restarts:
- SQLite: Database files reside in
~/.omniroute/with WAL mode for reliability - Qdrant: Runs as containerized service with disk-backed collections;
ensureCollectionlazily creates collections on first use
The cleanupSemanticMemoryPoints routine enforces configurable retention policies, automatically purging expired entries to bound storage growth.
CLI and Programmatic Access
Command-Line Interface
omniroute memory add \
--key "project-xyz-notes" \
--content "Discussed roadmap for Q4, decided on feature-flags." \
--metadata '{"tags":["roadmap","q4"]}'
Integration with Chat Handlers
Memory extraction and injection into active conversations occurs in:
open-sse/handlers/chatCore/memoryExtraction.ts— identifies salient content to storeopen-sse/handlers/chatCore/memorySkillsInjection.ts— retrieves relevant context for the current prompt
Summary
- SQLite FTS5 (
src/lib/memory/sqliteBackend.ts) provides fast, BM25-ranked keyword search ideal for exact matches and recent conversation browsing - Qdrant (
src/lib/memory/qdrant.ts) enables scalable semantic similarity search via dense embeddings with optional quantization - Memory manager (
src/lib/memory/manager.ts) unifies both backends with automatic backend selection and hybrid retrieval pipelines - Full persistence: SQLite files survive process restarts; Qdrant persists vectors to disk with lazy collection initialization
- Automatic cleanup maintains bounded storage via configurable expiration policies
Frequently Asked Questions
How does OmniRoute decide between FTS5 and Qdrant for a memory query?
The memory manager's retrieveMemory method accepts a type parameter that explicitly selects the backend, or defaults to a hybrid strategy. When hybrid mode is active, the system first retrieves semantically similar candidates from Qdrant, then refines with FTS5 keyword filtering—delivering both conceptual relevance and keyword precision.
What embedding model does OmniRoute use for Qdrant vectors?
The source code references createEmbeddingResponse as the embedding service interface, but the specific model configuration is determined by the Settings table or environment configuration. The embedding dimension must match the Qdrant collection's vector size defined at creation time.
Can OmniRoute's memory system work without Qdrant?
Yes. The SQLite FTS5 backend operates independently and provides full CRUD functionality for keyword-based memory retrieval. Qdrant is only required for semantic similarity features; deployments can function with FTS5 alone if vector search is not needed.
How is conversational memory scoped to prevent cross-user leakage?
Both backends support multi-tenant filtering. The SQLite backend queries include apiKeyId and sessionId columns in WHERE clauses. Qdrant searches apply payload filters on the same fields, ensuring users only retrieve their own memory entries regardless of which retrieval method is used.
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 →