Skip to main content

Semantic Search

What is Semantic Search?

Semantic search understands meaning, not just keywords. Search for "what's the user's favorite color?" and find memories about color preferences—even if they don't contain the exact words.

Tip

v0.30.0: Facts now support native semantic search. Configure embedding in your SDK config and embeddings are auto-generated for recall() queries - no manual embedding code needed!


Quick Start

// Configure embedding once at SDK init (v0.30.0+)
const cortex = new Cortex({
convexUrl: process.env.CONVEX_URL!,
embedding: { provider: 'openai', apiKey: process.env.OPENAI_API_KEY },
});

// Search with semantic understanding - embeddings auto-generated!
const result = await cortex.memory.recall({
memorySpaceId: "user-123-space",
query: "what are the user's dietary preferences?",
userId: "user-123",
limit: 5,
});

Featurerecall()search()
Primary UseUnified orchestrated retrievalVector-focused search
Sources QueriedVector + Facts + GraphVector only
RankingMulti-signal scoringVector similarity only
LLM ContextBuilt-in formattingManual formatting
Best ForRAG, context buildingSimple searches, performance

Unified retrieval across all memory layers:

const result = await cortex.memory.recall({
memorySpaceId: "user-123-space",
query: "user preferences",
embedding: await embed("user preferences"),
userId: "user-123",
limit: 10,
sources: {
vector: true, // Search vector memories
facts: true, // Search structured facts
graph: true, // Query graph relationships
},
formatForLLM: true,
});

// Ready-to-use LLM context
console.log(result.context);

// Individual items with source info
result.items.forEach(item => {
console.log(`${item.content} (${item.source}: ${item.score})`);
});

Search Strategies

Uses vector embeddings for meaning-based search:

const results = await cortex.memory.search(spaceId, query, {
embedding: await embed(query),
strategy: "semantic",
});
Tip

Best for natural language queries and concept matching. "car" finds "automobile".


Embedding Providers

Info

Cortex is embedding-agnostic. Use any provider that returns vectors.

import OpenAI from 'openai';

const openai = new OpenAI();

async function embed(text: string): Promise<number[]> {
const response = await openai.embeddings.create({
model: "text-embedding-3-small",
input: text,
});
return response.data[0].embedding;
}

Embedding Model Comparison

FeatureDimensionsSpeedAccuracyCost
text-embedding-3-small (OpenAI)1536FastGood$
text-embedding-3-large (OpenAI)3072MediumBest$$
embed-english-v3.0 (Cohere)1024FastGood$
all-MiniLM-L6-v2 (Local)384Very FastFairFree
Dimension Matching

Query embeddings must match stored embeddings. Don't mix 1536-dim queries with 3072-dim stored vectors.


Search Options

ParameterTypeRequiredDefaultDescription
embeddingnumber[]NoQuery vector for semantic search
strategystringNoauto'semantic', 'keyword', or 'auto'
limitnumberNo20Maximum results to return
minScorenumberNo0Minimum similarity score (0-1)
userIdstringNoFilter by user
tagsstring[]NoFilter by tags
tagMatchstringNoany'any' or 'all'
minImportancenumberNoMinimum importance (0-100)
createdAfterDateNoFilter by creation date
createdBeforeDateNoFilter by creation date

Result Boosting

Boost Options

Enhance ranking beyond pure semantic similarity:

const results = await cortex.memory.search(spaceId, query, {
embedding: await embed(query),
boostImportance: true, // Boost high-importance memories
boostRecent: true, // Boost recent memories
boostPopular: true, // Boost frequently accessed
});

Filtering Examples

// User-scoped search with tags
const memories = await cortex.memory.search(spaceId, query, {
embedding: await embed(query),
userId: "user-123",
tags: ["preferences", "verified"],
tagMatch: "all", // Must have BOTH tags
});

// High-importance recent memories
const critical = await cortex.memory.search(spaceId, query, {
embedding: await embed(query),
minImportance: 80,
createdAfter: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000),
minScore: 0.75,
});

// Temporal search
const thisMonth = await cortex.memory.search(spaceId, query, {
embedding: await embed(query),
createdAfter: new Date("2026-01-01"),
createdBefore: new Date("2026-01-31"),
});

Search Result Format

interface SearchResult {
id: string;
memorySpaceId: string;
content: string;
contentType: "raw" | "summarized";

// Source info
source: {
type: "conversation" | "system" | "tool" | "a2a";
userId?: string;
userName?: string;
timestamp: Date;
};

// Link to full conversation (ACID store)
conversationRef?: {
conversationId: string;
messageIds: string[];
};

// Metadata
metadata: {
importance: number; // 0-100
tags: string[];
};

// Search-specific
score: number; // 0-1
strategy: "semantic" | "keyword" | "recent";
highlights?: string[];
}

Best Practices

Always Provide Embeddings + User Scope
// Recommended: semantic search scoped to user
await cortex.memory.search(spaceId, query, {
embedding: await embed(query),
userId: userId,
});

// Works but less accurate (keyword fallback)
await cortex.memory.search(spaceId, query, {
userId: userId,
});
Use Descriptive Queries
// Vague - poor results
await cortex.memory.search(spaceId, "user");

// Specific - better results
await cortex.memory.search(spaceId,
"what are the user's dietary restrictions and food preferences?"
);
Handle Empty Results
let memories = await cortex.memory.search(spaceId, query, {
embedding: await embed(query),
userId,
});

if (memories.length === 0) {
// Broaden search: remove user filter
memories = await cortex.memory.search(spaceId, query, {
embedding: await embed(query),
limit: 5,
});
}

if (memories.length === 0) {
// Final fallback: recent memories
memories = await cortex.memory.search(spaceId, "*", {
userId,
sortBy: "createdAt",
sortOrder: "desc",
limit: 10,
});
}

Performance Tips

Batch Embeddings
// Slow: generate embedding per query
for (const query of queries) {
const embedding = await embed(query);
await cortex.memory.search(spaceId, query, { embedding });
}

// Fast: batch embeddings first
const embeddings = await embedBatch(queries);
for (let i = 0; i < queries.length; i++) {
await cortex.memory.search(spaceId, queries[i], {
embedding: embeddings[i]
});
}
Filter During Search
// Slow: search everything, filter after
const all = await cortex.memory.search(spaceId, query, { limit: 100 });
const highPriority = all.filter(m => m.metadata.importance >= 80);

// Fast: filter during search
const highPriority = await cortex.memory.search(spaceId, query, {
minImportance: 80,
limit: 20,
});

Common Use Cases

Building LLM Context

async function buildContext(spaceId: string, userId: string, message: string) {
const memories = await cortex.memory.search(spaceId, message, {
embedding: await embed(message),
userId,
minScore: 0.7,
minImportance: 40,
limit: 5,
});

if (memories.length === 0) {
return "No prior context found.";
}

return `Relevant context:\n${memories
.map(m => `- ${m.content}`)
.join("\n")}`;
}

Fact Retrieval

async function getFact(spaceId: string, userId: string, question: string) {
const memories = await cortex.memory.search(spaceId, question, {
embedding: await embed(question),
userId,
limit: 1,
minScore: 0.8,
});

return memories.length > 0
? { fact: memories[0].content, confidence: memories[0].score }
: { fact: null, confidence: 0 };
}

Troubleshooting

Causes: Query/content embedding mismatch, low score threshold

Fix:

// Raise score threshold
const memories = await cortex.memory.search(spaceId, query, {
embedding: await embed(query),
minScore: 0.75, // Raise from default
tags: ["relevant-topic"], // Narrow scope
});

Causes: No matching memories, query too specific, wrong space ID

Fix:

// Check if memories exist
const total = await cortex.memory.count(spaceId);
if (total === 0) {
console.log("Memory space is empty");
}

// Try broader query
const broader = await cortex.memory.search(spaceId, "preferences", {
embedding: await embed("preferences"),
minScore: 0.5,
});

Causes: Large result sets, complex filters, no caching

Fix:

// Reduce scope with filters
const memories = await cortex.memory.search(spaceId, query, {
embedding: await embed(query),
createdAfter: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000),
limit: 10,
});

// Cache frequent queries
const cacheKey = `search:${spaceId}:${query}`;
const cached = cache.get(cacheKey);
if (cached) return cached;

Integration with Facts

Search automatically includes facts extracted via remember():

const result = await cortex.memory.recall({
memorySpaceId: spaceId,
query: "user preferences",
embedding: await embed("user preferences"),
});

result.items.forEach(item => {
if (item.fact) {
console.log(`Fact: ${item.fact.fact}`);
console.log(`Confidence: ${item.fact.confidence}%`);
console.log(`Current belief: ${item.fact.isCurrentBelief}`);
}
});

See Fact Integration for details on automatic fact extraction.


Next Steps