SurrealDB Agent Memory exposes four retrieval modes for document passages (POST .../documents/query: hybrid, vector, bm25, hybrid_graph) and four for the unified read path (POST .../query, which also ranks experiential facts: hybrid, vector, bm25, graph). Each mode trades coverage, precision, and computational cost differently. See Recalling memories for unified recall; this page focuses on mode selection and graph-density signals.
Answer size vs search breadth
k controls how many hits are returned after fusion - the answer size. Default 10, maximum 50; a larger value is clamped down to the maximum.
The candidate pool that vector, BM25, and graph signals search over is sized separately for the deployment (default 256). Raising k returns more fused results but does not widen the internal search. Tier escalation (when confidence is thin) doubles the pool rather than re-running an identical pass.
Structured memories, not hundreds of raw chunks, should answer most queries - see Coherence, retrieval, and cost tiers.
Section expansion
After ranking, SurrealDB Agent Memory can follow chunk.section_ref (and related section pointers) and append same-section siblings into a separate contextHits channel on /query. Those passages are not counted against k and do not displace ranked hits. /chat and /reflect synthesise over both lists; citations can resolve either. Expansion is on by default for the deployment. See Recalling memories.
Query modes
vector
Pure HNSW (hierarchical navigable small world) approximate nearest-neighbour search over dense embeddings. The query is embedded with the same model used at ingestion, and the top-k nearest chunk embeddings are returned.
Vector search excels at paraphrase and semantic similarity - finding chunks that express the same idea in different words. It is weak on exact strings, product codes, proper names, and rare terms that are poorly represented in the embedding model's training data.
The vector leg also searches transcript segments (audio_chunk records) in the same 3072-dim embedding space as text chunks, so spoken content from audio and video documents can surface as passage hits with time-coded provenance - not only the parent chunk spine.
bm25
BM25 full-text search over the chunk corpus. The query is tokenized and matched against the inverted index. Results are ranked by term frequency - inverse document frequency weighted by document length.
BM25 excels at exact terms, product identifiers, model numbers, and specific technical phrases. It is weak on synonyms and paraphrase - if the query uses a term not present in the chunk, BM25 will not find it.
hybrid
Reciprocal rank fusion (RRF) of vector and BM25 results. Both retrieval passes run independently, and their ranked lists are merged into a single ranking using the RRF formula:
score(d) = Σ 1 / (k + rank_i(d))where k is a smoothing constant (default 60) and rank_i(d) is the rank of document d in retrieval pass i. Documents appearing in both lists receive a combined score; documents appearing in only one list are still represented with a lower combined score.
Hybrid mode is the default. It reliably outperforms either mode in isolation across a wide range of query types and is appropriate for most production deployments.
hybrid_graph
Hybrid retrieval plus a graph-density reranking pass. After the initial hybrid retrieval, each candidate chunk is rescored based on its connectivity in the knowledge graph. Chunks that are more densely connected to other relevant content - via keyword co-occurrence, document-level links, typed-knowledge edges, or semantic section similarity - receive a higher rerank score.
The graph-density reranker draws on:
Keyword graph: chunks linked to high-scoring keywords that match query terms
Typed-knowledge graph: chunks adjacent to knowledge nodes relevant to the query
Section vectors: semantic similarity between query and document section headings
Document links: outbound links from the retrieved document to related documents
Document summaries: summary-level similarity as a signal for topic alignment
Personalised PageRank: random-walk authority scores from query-matched seed nodes
hybrid_graph produces the highest-quality results for complex queries requiring multi-hop reasoning, but adds latency proportional to graph depth. For simple factual lookups, hybrid is sufficient.
When tuning graph-density reranking, optional graphEdges on /documents/query selects which structural signals contribute. Each value must be one of the recognised edge kinds - typos or unknown values are rejected with 422 Unprocessable Entity rather than being silently ignored:
| Edge kind | Signal |
|---|---|
knowledge_has_keyword | Chunks linked to query-matching keywords |
section_match | Section-heading similarity |
document_link | Cross-document link density |
document_summary | Document-level summary similarity |
The edgeKind in a hit's graphEvidence may report hybrid_graph when several signals combine; that value describes merged evidence in the result, not a value to send in graphEdges.
Basic query
import os
from surrealdb.memory import AsyncMemory
memory = AsyncMemory(
context="acme-prod",
endpoint=os.environ["AGENT_MEMORY_ENDPOINT"],
api_key=os.environ["AGENT_MEMORY_API_KEY"],
)
response = await memory.documents.query(
"what is the return window for unopened items?",
mode="hybrid",
k=10,
)
for hit in response.results:
print(hit.score)
print(hit.chunk["text"])
print(hit.document["title"])import { AgentMemory } from "@surrealdb/memory";
const memory = new AgentMemory({
context: "acme-prod",
endpoint: process.env.AGENT_MEMORY_ENDPOINT,
apiKey: process.env.AGENT_MEMORY_API_KEY,
});
const { results } = await memory.documents.query({
query: "what is the return window for unopened items?",
mode: "hybrid",
k: 10,
});
for (const hit of results) {
console.log(hit.score, hit.chunk.text, hit.document.title);
}Full query with all options
The request body below sets every option on POST /api/v1/{context_id}/documents/query to its default, except mode, expandGraph, filter and lens. The Python SDK's documents.query accepts only k, mode, threshold, expand_graph and filter; the JavaScript SDK passes the whole body through.
POST /api/v1/{context_id}/documents/query
Authorization: Bearer <key>
Content-Type: application/json
{
"query": "what is the return window for unopened items?",
"mode": "hybrid_graph",
"k": 10,
"threshold": 0.5,
"vectorWeight": 0.5,
"rrfK": 60,
"graphAlpha": 0.3,
"graphDepth": 1,
"expandGraph": true,
"useHyde": false,
"decomposeQuery": false,
"useReranker": false,
"filter": { "mimeType": ["application/pdf"] },
"lens": [["org/acme"]]
}thresholdis the minimum similarity for the vector leg.bm25mode ignores it, and the hybrid modes admit BM25 hits without it.vectorWeightis the relative weight of vector against BM25 in RRF, andrrfKis the RRF smoothing constant.graphAlphais the weight of the graph-density rerank against the base score.expandGraphincludes one-hop keyword and typed-knowledge context.useHyde,decomposeQueryanduseRerankerare described under Advanced options.filteraccepts onlymimeTypeanddocumentIds. Narrow by scope withlens, a DNF scope selector.
Query result structure
The response has a results array and queryMs. Each hit in results contains:
{
"chunk": {
"id": "chunk:01hy2…",
"document": "01hx9…",
"text": "Unopened items may be returned within 30 days of the original purchase date.",
"section": "Eligibility",
"position": 7,
"charStart": 1840,
"charEnd": 1917
},
"score": 0.87,
"document": {
"id": "01hx9…",
"title": "Returns Policy",
"source": "returns.pdf"
}
}chunk.positionis the chunk's ordinal position within the document, useful for retrieving surrounding context.chunk.sectionis the heading of the section containing this chunk, ornullfor documents without section structure.scoreis the normalised relevance score after all reranking passes.documentprovides provenance for display, citation, or follow-up retrieval.
Advanced options
These flags are honoured on POST /documents/query when a request-path LLM provider is configured. The MCP recall tool has no HyDE, decomposition or reranker arguments. On failure or when no LLM is attached, SurrealDB Agent Memory falls back to single-query retrieval without error.
HyDE (Hypothetical Document Embeddings)
When useHyde is true, SurrealDB Agent Memory generates a hypothetical answer to the query using the configured response model, then embeds that hypothetical answer rather than the raw query string. This improves recall for queries phrased as questions rather than document-like statements, at the cost of one LLM call per query.
const { results } = await memory.documents.query({
query: "what is the return window for unopened items?",
mode: "hybrid",
useHyde: true,
});Sub-question decomposition
When decomposeQuery is true, SurrealDB Agent Memory splits complex queries into a set of simpler sub-questions, executes each independently, and merges the results. This is useful when a single query implicitly asks multiple things:
const { results } = await memory.documents.query({
query: "what are the return and warranty policies for AirPods Pro 2?",
mode: "hybrid",
decomposeQuery: true,
});Decomposition adds latency proportional to the number of sub-questions and consumes additional LLM tokens. Use it for explicit multi-topic queries rather than as a default.
Cross-encoder reranking
When useReranker is true, the top-k results from the initial retrieval pass are reranked using a cross-encoder model that jointly encodes the query and each candidate chunk. Cross-encoder reranking is more accurate than bi-encoder (embedding) similarity but significantly slower.
Requires a deployment reranker: the reranker must be enabled for the deployment. Without one, useReranker: true falls through to bi-encoder ordering.
const { results } = await memory.documents.query({
query: "return policy for international purchases",
mode: "hybrid",
useReranker: true,
k: 20, // retrieve more candidates for the reranker to rescore
});Metadata filtering
Apply hard filters before retrieval to restrict results to a subset of the document corpus. filter accepts mimeType and documentIds; any other key is ignored. To restrict by scope, pass lens beside filter instead:
response = await memory.documents.query(
"Q3 revenue",
mode="hybrid",
filter={"mimeType": ["application/pdf"]},
)Filters are applied before scoring, so they do not affect the ranking of results that pass through.
Choosing a mode
| Query pattern | Recommended mode |
|---|---|
| General natural-language questions | hybrid |
| Product codes, model numbers, exact phrases | bm25 |
| Paraphrase, synonym-heavy queries | vector |
| Complex multi-hop reasoning, graph context | hybrid_graph |
| High-recall requirements (academic, legal) | hybrid + useReranker: true |