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 regular memory table and the FTS5 index
  • search — executes MATCH queries returning BM25-ranked results
  • delete — 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:

  1. Calls createEmbeddingResponse to generate a dense vector from content
  2. 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/search requests with optional filtering by apiKeyId or sessionId
  • 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:

  1. Execute semantic top-K search in Qdrant for conceptual relevance
  2. Apply keyword refinement using FTS5 filter
  3. 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; ensureCollection lazily 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:

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →