# How OmniRoute Memory Handles Persistent Conversational Storage Using FTS5 and Qdrant

> Discover how OmniRoute Memory uses SQLite FTS5 and Qdrant for efficient conversational storage. Achieve fast keyword and semantic search for your chat history.

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

---

**OmniRoute implements a hybrid persistent memory system that stores conversational data in SQLite FTS5 for fast keyword retrieval and Qdrant for semantic vector search, orchestrated through a unified manager that enables both exact-match and similarity-based recall across chat history.**

The OmniRoute open-source routing layer (diegosouzapw/OmniRoute) provides persistent conversational memory by combining two specialized storage backends. This architecture ensures that chat contexts survive server restarts while supporting both precise keyword lookups and intelligent semantic retrieval.

## Dual-Backend Architecture for Conversational Memory

OmniRoute splits conversational storage across complementary backends optimized for different retrieval strategies. This dual approach balances the speed of exact text matching with the flexibility of embedding-based similarity search.

### SQLite FTS5 for Full-Text Search

The system uses **SQLite FTS5** (Full-Text Search version 5) to power fast keyword-based lookup of recent chats and user-provided notes. According to [`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), the migration creates an FTS5 virtual table that indexes the `content` column of the `memory` table.

The SQLite-based backend ([`src/lib/memory/sqliteBackend.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/memory/sqliteBackend.ts)) implements CRUD operations including `upsert`, `search`, and `delete`. These operations write to the regular `memory` and `memory_vec` tables while automatically updating the FTS5 index. Retrieval uses the SQLite `MATCH` operator, returning rows sorted by the built-in **BM25 relevance score**.

Data persists in the local filesystem under `~/.omniroute/`, ensuring conversational history survives process restarts without external dependencies.

### Qdrant for Semantic Vector Storage

For semantic similarity search, OmniRoute integrates with **Qdrant**, a remote vector database. The configuration logic in [`src/lib/memory/qdrant.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/memory/qdrant.ts) builds connection parameters from the Settings table or environment variables (`QDRANT_HOST`, `QDRANT_PORT`).

When storing memory, the `upsertSemanticMemoryPoint` function first calls the embedding service (`createEmbeddingResponse`) to generate a dense vector, then stores the vector with payload metadata via the `/collections/<collection>/points` endpoint. Searches execute through `searchSemanticMemory`, which embeds the query text and issues a `/points/search` request, supporting filtering by API-key or session ID.

The system supports optional quantization (`none`, `int8`, `binary`) via `buildQuantizationConfig` and `searchQuantizationParams`, reducing RAM usage while enabling fast rescoring for quantized collections.

### Memory Manager Orchestration

The **Memory Manager** ([`src/lib/memory/manager.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/memory/manager.ts)) unifies both backends behind a single API. It exposes `retrieveMemory`, `upsertMemory`, and `cleanupMemory` methods that internally route requests based on the `type` flag—directing keyword queries to SQLite and semantic queries to Qdrant.

For hybrid retrieval, the manager can execute a combined strategy: first performing a semantic top-K search in Qdrant, then refining results with FTS5 keyword filters. This delivers both broad similarity matching and precise relevance filtering.

## Implementation Details and Source Code

### Database Schema and Migrations

The FTS5 integration begins with the migration file [`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 establishes the virtual table structure. This virtual table indexes content from the primary memory storage, enabling efficient text search without duplicating the underlying data store.

The Qdrant collection is created lazily on first use through the `ensureCollection` function, which verifies the collection exists before attempting vector operations.

### Automatic Cleanup and Retention

The system includes **automatic cleanup** via `cleanupSemanticMemoryPoints`, which removes expired or stale entries based on configurable retention policies. This keeps storage size bounded across both SQLite files and Qdrant collections.

## Practical Usage Examples

### CLI Memory Commands

You can add memory entries directly from the command line using the CLI implementation in `bin/cli/commands/memory.mjs`:

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

```

### Programmatic Memory Operations

**Upserting semantic memory** via the Qdrant backend ([`src/lib/memory/qdrant.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/memory/qdrant.ts)):

```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,
});

```

**Keyword search using FTS5** ([`src/lib/memory/sqliteBackend.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/memory/sqliteBackend.ts)):

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

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

```

**Semantic search using Qdrant**:

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

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

```

**Hybrid retrieval via the Memory Manager** ([`src/lib/memory/manager.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/memory/manager.ts)):

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

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

```

## Chat Core Integration

The memory system integrates with OmniRoute's chat processing pipeline through dedicated handlers. The [`open-sse/handlers/chatCore/memoryExtraction.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/handlers/chatCore/memoryExtraction.ts) module extracts relevant memories from incoming messages, while [`open-sse/handlers/chatCore/memorySkillsInjection.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/handlers/chatCore/memorySkillsInjection.ts) injects retrieved context into the chat flow before routing to language models.

## Summary

- OmniRoute uses **SQLite FTS5** for persistent full-text search with BM25 scoring, storing data locally in `~/.omniroute/`.
- **Qdrant** provides scalable semantic vector storage with configurable quantization (`int8`, `binary`, or `none`) and session-based filtering.
- The **Memory Manager** ([`src/lib/memory/manager.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/memory/manager.ts)) unifies both backends, supporting hybrid retrieval strategies that combine semantic similarity with keyword precision.
- Automatic cleanup through `cleanupSemanticMemoryPoints` enforces retention policies across both storage systems.
- All operations are accessible via REST API, CLI (`bin/cli/commands/memory.mjs`), or direct programmatic integration.

## Frequently Asked Questions

### How does OmniRoute decide between FTS5 and Qdrant for a memory query?

The Memory Manager ([`src/lib/memory/manager.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/memory/manager.ts)) routes requests based on the `type` flag passed to `retrieveMemory`. Keyword-based queries execute against the SQLite FTS5 backend for exact matches, while semantic queries route to Qdrant for vector similarity search. For hybrid retrieval, the system can execute both strategies sequentially—first retrieving semantic candidates from Qdrant, then filtering through the FTS5 index for keyword relevance.

### What quantization options does OmniRoute support for Qdrant vectors?

According to [`src/lib/memory/qdrant.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/memory/qdrant.ts), the system supports three quantization levels via `buildQuantizationConfig`: `none` (full precision), `int8` (8-bit integers), and `binary`. These settings reduce RAM usage and enable fast rescoring during similarity searches, configured through `searchQuantizationParams` when executing queries.

### Where is conversational data stored when Qdrant is not configured?

When Qdrant is unavailable, OmniRoute maintains full functionality using **SQLite FTS5** alone. The SQLite backend ([`src/lib/memory/sqliteBackend.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/memory/sqliteBackend.ts)) persists all conversational data in local database files under `~/.omniroute/`, ensuring durable storage for keyword-based retrieval even without external vector database dependencies.

### How does OmniRoute handle memory cleanup and retention?

The system implements automatic cleanup through the `cleanupSemanticMemoryPoints` function (Qdrant) and corresponding SQLite maintenance routines. These remove expired entries based on configurable retention policies defined in the `expiresAt` field (for Qdrant) or similar timestamp logic, preventing unlimited storage growth while maintaining recent conversation history.