
Memo's vector search engine runs entirely in-process, backed by SQLite with the sqlite-vec extension. It uses cosine similarity over 768-dimensional float32 embeddings.
For a query vector q and document vectors d₁, d₂, ..., dₙ:
similarity(q, d) = (q · d) / (||q|| × ||d||)
Where:
q · d is the dot product||q|| and ||d|| are the L2 normsThe result is a value between -1.0 (opposite) and 1.0 (identical).
func CosineSimilarity(a, b []float32) float64 {
var dot, normA, normB float64
for i := range a {
dot += float64(a[i]) * float64(b[i])
normA += float64(a[i]) * float64(a[i])
normB += float64(b[i]) * float64(b[i])
}
if normA == 0 || normB == 0 {
return 0
}
return dot / (math.Sqrt(normA) * math.Sqrt(normB))
}
The search pipeline returns the K most similar chunks:
min_similarity is discardedtype SearchResult struct {
Content string `json:"content"`
Source string `json:"source"`
Similarity float64 `json:"similarity"`
ChunkIndex int `json:"chunk_index"`
}
func (s *Store) Search(ctx context.Context, queryVector []float32, topK int, minSimilarity float64) ([]SearchResult, error) {
// 1. ANN pre-filter via vec0
// 2. Exact cosine scoring
// 3. Sort, truncate, threshold
}
Large search spaces are partitioned and processed in parallel using a goroutine worker pool:
Query Vector
│
├─▶ Worker 1 ── Chunks 0–999 ──▶ Result Set A
├─▶ Worker 2 ── Chunks 1000–1999 ──▶ Result Set B
├─▶ Worker 3 ── Chunks 2000–2999 ──▶ Result Set C
└─▶ Worker 4 ── Chunks 3000–3999 ──▶ Result Set D
│
Merge + Top-K
│
Final Results
runtime.NumCPU() by defaultsync.MutexThe search space grows with ingested documents and past conversations. To maintain fast search as data scales:
| Total Chunks | Strategy |
|---|---|
| < 1,000 | Brute-force cosine (single pass) |
| 1,000 – 100,000 | vec0 ANN index + exact rescore |
| > 100,000 | ANN index only, Top-200 candidates before rescore |
Each chunk carries metadata to enable filtering and attribution:
SELECT content, source, chunk_index, created_at
FROM vec_memory
WHERE embedding MATCH ? -- KNN search via vec0
ORDER BY distance
LIMIT ?;
The min_similarity threshold (default 0.7) serves two purposes:
Users can adjust the threshold in Settings:
| Setting | Effect |
|---|---|
0.9 |
Only nearly identical matches (narrow recall) |
0.7 |
Default — good balance of precision and recall |
0.5 |
Broad recall, may include tangentially relevant content |
0.3 |
Very broad — use only for exhaustive search |
| Parameter | Default | When to Increase | When to Decrease |
|---|---|---|---|
top_k |
5 | Need broader context | Context window is full |
min_similarity |
0.7 | Getting irrelevant results | Missing relevant results |
chunk_size |
512 | Documents have long coherent sections | Short Q&A style content |
chunk_overlap |
10% | Content is dense, needs continuity | Content is already self-contained |
Cosine similarity computation is O(n) per comparison. With 768-dim vectors, a single comparison takes ~3 microseconds. The worker pool parallelizes this across all available CPU cores.