# How OmniRoute Uses FTS5 + Qdrant for Conversational Context Memory

> Discover how OmniRoute's memory system uses FTS5 and Qdrant to provide fast keyword search and semantic recall for conversational context. Improve your chatbots.

- Repository: [Diego Rodrigues de Sa e Souza/OmniRoute](https://github.com/diegosouzapw/OmniRoute)
- Tags: deep-dive
- Published: 2026-08-20

---

**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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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

```typescript
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`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/memory/qdrant.ts) module constructs connection parameters from the Settings table or environment variables (`QDRANT_HOST`, `QDRANT_PORT`, `QDRANT_API_KEY`):

```typescript
// 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`

```typescript
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

```typescript
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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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

```typescript
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

```bash
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`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/handlers/chatCore/memoryExtraction.ts) — identifies salient content to store
- [`open-sse/handlers/chatCore/memorySkillsInjection.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/handlers/chatCore/memorySkillsInjection.ts) — retrieves relevant context for the current prompt

## Summary

- **SQLite FTS5** ([`src/lib/memory/sqliteBackend.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/memory/qdrant.ts)) enables **scalable semantic similarity search** via dense embeddings with optional quantization
- **Memory manager** ([`src/lib/memory/manager.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/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.