# How OmniRoute's Persistent Memory System Combines FTS5 and Qdrant for Semantic Retrieval

> Discover how OmniRoute's persistent memory system uses SQLite FTS5 and Qdrant for efficient keyword and semantic retrieval, with automatic fallback for vector service outages.

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

---

**OmniRoute stores every conversational memory in SQLite with FTS5 for keyword search and Qdrant for semantic retrieval, automatically falling back through a three-tier hierarchy when vector services are unavailable.**

The OmniRoute persistent memory system implements a hybrid architecture that balances local persistence with high-performance vector search. According to the diegosouzapw/OmniRoute source code, all memory data lives in SQLite while supporting both full-text and semantic retrieval strategies through a pluggable backend system.

## SQLite Foundation with FTS5 Indexing

All memory records reside in the `memories` table, which stores raw text, metadata, TTL values, and unique keys. For fast keyword retrieval, OmniRoute creates an **FTS5 virtual table** named `memory_fts` that indexes the `content` and `key` columns.

The schema is established in 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 both the base table and the virtual FTS5 table. A subsequent migration in [`src/lib/db/migrations/023_fix_memory_fts_uuid.sql`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/db/migrations/023_fix_memory_fts_uuid.sql) stabilizes the FTS5 table to use integer rowids for consistent indexing.

The [`src/lib/db/memoryVec.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/db/memoryVec.ts) module provides CRUD helpers for interacting with the `memories` table and the related `memory_vec_meta` table, ensuring all vector metadata stays synchronized with the persistent SQLite store.

## Three-Tier Vector Store Architecture

The retrieval logic in [`src/lib/memory/retrieval.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/memory/retrieval.ts) implements a tiered backend selection strategy based on the `settings.vectorStore` configuration:

- **Qdrant** – External Qdrant cluster accessed via HTTP for high-performance semantic search
- **sqlite-vec** – In-process SQLite extension for approximate nearest neighbor (ANN) search without external dependencies
- **FTS5** – Pure keyword search against the `memory_fts` virtual table as final fallback

When `settings.vectorStore` equals `"qdrant"`, the system attempts semantic search first. If Qdrant returns no results or fails, the pipeline automatically degrades to sqlite-vec, then finally to FTS5 keyword matching.

## Semantic and Hybrid Retrieval Flow

For **semantic retrieval**, the `retrieveMemories` function in [`src/lib/memory/retrieval.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/memory/retrieval.ts) executes the following resolution path:

```typescript
import { retrieveMemories } from "@/lib/memory/retrieval";

const apiKeyId = "user-api-key-123";
const result = await retrieveMemories(apiKeyId, {
  query: "how to reset my password",
  retrievalStrategy: "semantic",
  maxTokens: 2000,
});
console.log(result.map(m => ({
  id: m.id,
  tier: (m as any).tier,   // "qdrant" | "vector" | "fts5"
})));

```

If Qdrant is enabled, the function calls `searchSemanticMemory` from [`src/lib/memory/qdrant.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/memory/qdrant.ts), then hydrates results from SQLite using `fetchMemoriesByIds`. The returned objects include a `tier` property indicating which backend served the query.

For **hybrid retrieval**, when using the sqlite-vec backend, OmniRoute performs **reciprocal rank fusion (RRF)** to combine vector similarity scores with FTS5 keyword ranking. The `retrievePreview` utility allows dry-run testing of this logic:

```typescript
import { retrievePreview } from "@/lib/memory/retrieval";

const preview = await retrievePreview(null, "project roadmap", {
  strategy: "hybrid",
  maxTokens: 3000,
  limit: 10,
});
console.log(preview.resolution); // shows which backend (qdrant / sqlite-vec) was used

```

## Qdrant Configuration and Operations

The [`src/lib/memory/qdrant.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/memory/qdrant.ts) module handles all external vector store operations. It constructs `QdrantConfig` from environment variables or UI settings, manages collection creation, and handles semantic search with optional quantization modes (`none`, `int8`, or `binary`).

When upserting memories, the `upsertSemanticMemoryPoint` function synchronizes data between SQLite and the Qdrant collection:

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

await upsertSemanticMemoryPoint({
  id: "mem-001",
  apiKeyId: "user-api-key-123",
  sessionId: "sess-456",
  key: "reset-password",
  content: "To reset your password, click the 'Forgot password' link …",
  metadata: {},
  createdAt: new Date().toISOString(),
  expiresAt: null,
});

```

## Engine Observability and Status

The system exposes current backend health through the `/api/settings/memory/engine-status` endpoint, defined in [`src/shared/schemas/memory.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/shared/schemas/memory.ts). This reports whether FTS5, the embedding source, vector store, Qdrant health, and optional rerank service are active.

An optional rerank service running at `http://127.0.0.1:20128/v1/rerank` can reorder results after primary retrieval for improved relevance.

## Summary

- **SQLite as source of truth**: All memories persist in the `memories` table with FTS5 indexing via `memory_fts`
- **Tiered fallback**: Retrieval prefers Qdrant, falls back to sqlite-vec, then degrades to FTS5 keyword search
- **Hybrid scoring**: Reciprocal rank fusion combines vector and keyword results when using sqlite-vec
- **Configurable backends**: Select between external Qdrant, local sqlite-vec, or pure FTS5 via `settings.vectorStore`
- **Full observability**: Engine status endpoint exposes backend health and active retrieval tiers

## Frequently Asked Questions

### What is the fallback order when retrieving memories in OmniRoute?

OmniRoute attempts retrieval in a strict hierarchy: first Qdrant if configured, then sqlite-vec for local vector search, and finally FTS5 keyword search against the `memory_fts` virtual table. This ensures queries always return results even when external vector services are unavailable.

### How does OmniRoute handle full-text search without a vector store?

When vector stores are disabled or unavailable, the system queries the `memory_fts` FTS5 virtual table directly using the `buildFtsRows` helper. This provides fast exact-match and keyword ranking against the indexed `content` and `key` columns without requiring embeddings.

### Can OmniRoute use both Qdrant and sqlite-vec simultaneously?

The architecture selects a single vector store backend per query based on `settings.vectorStore`, but the tiered fallback means sqlite-vec acts as a secondary option when Qdrant fails. However, hybrid retrieval with reciprocal rank fusion specifically requires the sqlite-vec backend to combine vector and FTS5 scores locally.

### Where is the memory schema defined in the codebase?

The database schema is defined 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) for the initial tables and [`src/lib/db/migrations/023_fix_memory_fts_uuid.sql`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/db/migrations/023_fix_memory_fts_uuid.sql) for rowid stabilization. Runtime access patterns are implemented in [`src/lib/db/memoryVec.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/db/memoryVec.ts) with TypeScript interfaces declared in [`src/shared/schemas/memory.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/shared/schemas/memory.ts).