# How OmniRoute Uses FTS5 and Qdrant for Persistent Conversational Memory

> Discover how OmniRoute leverages FTS5 and Qdrant for robust, persistent conversational memory. Experience fast keyword search and semantic retrieval for seamless chat experiences.

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

---

**OmniRoute implements a dual-backend memory system combining SQLite FTS5 for fast keyword search and Qdrant for semantic vector retrieval, orchestrated through a unified manager that provides persistent, searchable conversational memory across restarts.**

The **OmniRoute** routing and conversation platform stores conversational snippets in two complementary backends that serve different retrieval strategies. This hybrid architecture enables both precise keyword matching and powerful semantic similarity search, ensuring developers can retrieve relevant context from large conversation histories efficiently. According to the OmniRoute source code, the system persists data to disk on SQLite and Qdrant, making memory durable across server restarts.

## FTS5 Full-Text Search: Fast Keyword-Based Retrieval

OmniRoute uses **SQLite FTS5** as its lightweight, embedded full-text search engine for quick lookup of recent chats and user-provided notes.

### FTS5 Schema and Migration

The FTS5 virtual table is created via migration in [`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). This indexes the `content` column of each memory entry, enabling BM25-ranked relevance scoring out of the box:

```sql
-- Creates virtual FTS5 table linked to memory content
CREATE VIRTUAL TABLE memory_vec USING fts5(
  content,
  content_rowid=rowid
);

```

### SQLite Backend Operations

The [`src/lib/memory/sqliteBackend.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/memory/sqliteBackend.ts) file implements **CRUD operations** that automatically synchronize the regular `memory` table with the FTS5 index:

| Method | Purpose |
|--------|---------|
| `upsert` | Inserts or updates memory entries in both tables |
| `search` | Executes FTS5 `MATCH` queries with BM25 ranking |
| `delete` | Removes entries and maintains index consistency |

Retrieval uses SQLite's built-in `MATCH` operator. The BM25 relevance score ranks results automatically without additional configuration.

```typescript
import { searchFullTextMemory } from "@/lib/memory/sqliteBackend";

const results = await searchFullTextMemory("roadmap Q4");

```

The SQLite database resides under `~/.omniroute/` and survives process restarts, providing **zero-configuration persistence** for deployments without external dependencies.

## Qdrant Vector Search: Semantic Similarity at Scale

For **nearest-neighbor lookup based on embeddings**, OmniRoute integrates with Qdrant, a remote vector database that enables similarity-based recall across entire conversation histories.

### Qdrant Configuration

The [`src/lib/memory/qdrant.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/memory/qdrant.ts) module builds Qdrant configuration from the Settings table or environment variables:

```typescript
// Environment-driven configuration
QDRANT_HOST    // Server hostname
QDRANT_PORT    // HTTP port
QDRANT_API_KEY // Authentication (optional)

```

### Semantic Memory Operations

**Upserting semantic memory** (`upsertSemanticMemoryPoint`) follows a two-step pipeline:

1. Call `createEmbeddingResponse` to generate a dense vector from content
2. Store the vector with payload metadata to `/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** (`searchSemanticMemory`) embeds query text and issues `/points/search` requests:

```typescript
import { searchSemanticMemory } from "@/lib/memory/qdrant";

const { ok, results } = await searchSemanticMemory("feature-flags plan", 5);
if (ok) console.log(results);

```

Results filter by `apiKey` or `session` when scoping is required, ensuring multi-tenant isolation.

### Quantization for Performance

Qdrant collections support optional quantization configured via `buildQuantizationConfig`:

| Mode | RAM Reduction | Use Case |
|------|---------------|----------|
| `none` | None | Maximum accuracy, smaller datasets |
| `int8` | ~4x | Balanced speed/quality for medium scale |
| `binary` | ~32x | Maximum throughput, acceptable precision loss |

The `searchQuantizationParams` helper applies rescore settings when quantized collections need refinement.

Collections are created **lazily** on first use via `ensureCollection`, eliminating deployment-time setup.

## Memory Manager: Unified Hybrid Retrieval

The [`src/lib/memory/manager.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/memory/manager.ts) module exposes a **single API** that orchestrates both backends based on request characteristics:

| Method | Backend Selection Logic |
|--------|------------------------|
| `upsertMemory` | Routes to FTS5, Qdrant, or both based on `type` flag |
| `retrieveMemory` | Chooses semantic, keyword, or hybrid strategy |
| `cleanupMemory` | Applies retention policies to both stores |

### Hybrid Retrieval Pattern

UI components can issue combined requests that merge strengths of both systems:

1. Execute **semantic top-K search** in Qdrant for conceptual relevance
2. **Refine results** with keyword filtering via FTS5 for precision

```typescript
import { retrieveMemory } from "@/lib/memory/manager";

const { ok, results } = await retrieveMemory({
  query: "roadmap",
  topK: 10,
  scope: { apiKeyId: "user-abc" },
});

```

The manager handles backend fallbacks transparently—if Qdrant is unavailable, queries degrade gracefully to FTS5 where possible.

## Automatic Cleanup and Retention

Storage growth is bounded via **`cleanupSemanticMemoryPoints`**, which removes expired or stale entries based on configurable retention policies. This runs against both backends to maintain consistency.

## CLI and Integration Examples

### Command-Line Memory Management

```bash
omniroute memory add \
  --key "project-xyz-notes" \
  --content "Discussed roadmap for Q4, decided on feature-flags." \
  --metadata '{"tags":["roadmap","q4"]}'

```

### Chat-Core Integration

Memory extraction and injection hooks connect the persistence layer to active conversations:

| Component | File |
|-----------|------|
| Extraction from incoming messages | [`open-sse/handlers/chatCore/memoryExtraction.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/handlers/chatCore/memoryExtraction.ts) |
| Injection into context windows | [`open-sse/handlers/chatCore/memorySkillsInjection.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/handlers/chatCore/memorySkillsInjection.ts) |

## Summary

- **SQLite FTS5** provides cheap, exact-match keyword search with BM25 ranking—ideal for recent notes and tagged content
- **Qdrant** delivers scalable semantic similarity via embeddings, with quantization options for performance tuning
- **Memory manager** in [`src/lib/memory/manager.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/memory/manager.ts) unifies both backends behind a single API with hybrid retrieval support
- Both systems persist to disk: SQLite in `~/.omniroute/`, Qdrant as external container with disk-backed collections
- Automatic cleanup prevents unbounded growth across both stores

## Frequently Asked Questions

### What is the difference between FTS5 and Qdrant in OmniRoute's memory system?

**FTS5 handles fast keyword search** with exact and prefix matching, ideal for finding specific terms, tags, or recent conversations. **Qdrant enables semantic search** by comparing vector embeddings, which finds conceptually related content even without keyword overlap. The system uses both: FTS5 for precision and low latency, Qdrant for conceptual recall across large histories.

### How does OmniRoute ensure memory persists across server restarts?

SQLite databases are stored in the local data directory (`~/.omniroute/`) as ordinary files. Qdrant runs as a separate service with its own disk-backed storage—the collection is created on first use via `ensureCollection`. Both mechanisms survive process termination and server reboots without data loss.

### When should I use hybrid retrieval versus single-backend queries?

**Use hybrid retrieval** when you need both conceptual breadth and keyword precision—first retrieving semantically similar candidates with Qdrant, then filtering with FTS5. **Use single-backend queries** for specialized cases: pure FTS5 for exact tag lookups or compliance auditing, pure Qdrant for open-ended "find similar ideas" exploration without keyword constraints.

### How does quantization in Qdrant affect memory search quality?

Quantization reduces RAM usage significantly—**int8** cuts memory by ~4× with minimal accuracy loss, while **binary** achieves ~32× reduction with faster scoring but potential precision degradation. OmniRoute's `searchQuantizationParams` enables rescoring to recover quality on quantized collections, making binary viable for high-throughput scenarios with acceptable recall trade-offs.