Skip to content

Semantic Search

guirguispierre edited this page Mar 30, 2026 · 1 revision

Semantic Search

MemoryVault supports three search modes: lexical, semantic, and hybrid (default). Semantic search uses Cloudflare Vectorize and Workers AI embeddings.

Search Modes

Lexical

Traditional substring matching against memory content, title, key, and source. Fast, no AI required. Works locally without --remote.

Semantic

Vector similarity search using embeddings. Finds conceptually related memories even when keywords don't match. Requires Workers AI and Vectorize bindings.

Hybrid (Default)

Combines lexical and semantic results using Reciprocal Rank Fusion (RRF). Best of both worlds — catches exact matches and conceptual similarity.

Embedding Model

  • Model: @cf/baai/bge-base-en-v1.5 (Cloudflare Workers AI)
  • Dimensions: 768
  • Max input: 8,000 characters per memory

What Gets Embedded

Each memory is converted to embedding text by concatenating (in order):

type: note
title: My Memory Title
key: my-fact-key
source: api
tags: tag1, tag2
content: The actual memory content...

Empty fields are skipped. The result is capped at 8,000 characters.

How Hybrid Search Works

Step 1: Parallel Retrieval

Both search backends run simultaneously:

  1. Lexical: SQL LIKE query against content, title, key, source fields
  2. Semantic: Embed the query, then query Vectorize for nearest neighbors

Step 2: Reciprocal Rank Fusion (RRF)

Each result gets a fused score based on its rank in each result set:

K = 60  (fusion constant)

lexical_score  = 1 / (K + lexical_rank)
semantic_score = 1 / (K + semantic_rank)
similarity     = normalized_cosine_similarity * 0.25

Final score by mode:

Mode Formula
lexical lexical_score
semantic semantic_score + similarity
hybrid semantic_score * 0.7 + lexical_score * 0.3 + similarity * 0.15

Results are sorted by fused score descending, with updated_at as tiebreaker.

Step 3: Deduplication

If a memory appears in both result sets, scores are merged (not duplicated).

Vector Index

Storage

Vectors are stored in Cloudflare Vectorize with:

  • Namespace: brain_id (tenant isolation)
  • Vector ID: m_{memory_id} (prefixed)
  • Metadata: brain_id, memory_id, type, key, title, source, tags, timestamps, archived flag

Sync

Memory write operations (save, update, delete, consolidate, forget, snapshot restore) automatically sync vectors. The sync:

  1. Deletes vectors for archived/deleted memories
  2. Generates embeddings for active memories (batch size: 16)
  3. Upserts vectors (batch size: 500)

Reindexing

Use memory_reindex to rebuild vectors for existing memories:

{
  "name": "memory_reindex",
  "arguments": {
    "limit": 500,
    "wait_for_index": true,
    "wait_timeout_seconds": 180
  }
}

The wait_for_index option (default true) blocks until Vectorize has processed all mutations, ensuring subsequent searches return up-to-date results.

Configuration

Constant Value Description
EMBEDDING_MODEL @cf/baai/bge-base-en-v1.5 Workers AI model
EMBEDDING_BATCH_SIZE 16 Memories per embedding call
VECTORIZE_QUERY_TOP_K_MAX 20 Max semantic results
VECTORIZE_UPSERT_BATCH_SIZE 500 Vectors per upsert call
VECTORIZE_DELETE_BATCH_SIZE 500 Vectors per delete call
MEMORY_SEARCH_FUSION_K 60 RRF fusion constant
MEMORY_SEARCH_DEFAULT_LIMIT 20 Default search result limit

Requirements

Semantic search requires:

  • AI binding in wrangler.toml (Workers AI)
  • MEMORY_INDEX binding (Vectorize index with 768 dimensions, cosine metric)

Without these bindings, memory_search falls back to lexical-only mode.

Creating the Vectorize Index

npx wrangler vectorize create ai-memory-semantic-v1 --dimensions=768 --metric=cosine

For dev environment:

npx wrangler vectorize create ai-memory-semantic-v1-dev --dimensions=768 --metric=cosine

Clone this wiki locally