Search
Query a store with SKIM's reasoning loop — writing good queries, tuning max_hops and max_results, metadata filters, reading traces, and branching on confidence.
Search runs SKIM's multi-hop reasoning loop, not a single lookup. Each hop is informed by what the previous hop read, so the engine can follow chains, reconcile conflicting facts, and connect documents that share no keywords — in ~4 ms on a CPU.
Write real questions
Ask the question you actually want answered. The engine follows reasoning chains, not keyword overlap, so questions that require connecting facts are exactly where it shines.
Good vs shallow queries
- Good: "which supplier contract conflicts with the 2026 policy?" — needs two documents connected.
- Good: "what evidence supports migrating off the legacy billing system?" — aggregates across docs.
- Shallow (works, but wastes the engine): "net-90 payment terms" — a keyword lookup a vector DB would also handle.
Run a search
rekall search "which supplier contract conflicts with the 2026 policy?" \
--store demo --hops 4 --max-results 5 --trace● 2 passages · 2 hops · confidence 0.87 · 4.2 ms
1. supplier_a.pdf score 0.92 hop 1
"…Supplier A payment terms are net-90 as amended in §4.2…"
2. policy_2026.md score 0.81 hop 2
"…all supplier payment terms must not exceed net-60…"
reasoning trace
hop 1 → supplier_a.pdf (matches query)
hop 2 → policy_2026.md (conflict found, given hop 1)
✓ sufficient — stopped after 2 of 4 max hopsAdd --json to emit the SearchResult verbatim; -i opens an interactive REPL.
const result = await store.search(
"which supplier contract conflicts with the 2026 policy?",
{ maxResults: 5, trace: true },
);
for (const p of result.passages) console.log(p.docId, p.score, p.hop);
console.log(result.confidence, result.hopsUsed, result.latencyMs, result.engine);result = store.search(
"which supplier contract conflicts with the 2026 policy?",
max_results=5, trace=True,
)
for p in result.passages:
print(p.doc_id, p.score, p.hop)
print(result.confidence, result.hops_used, result.latency_ms, result.engine)Parameters
Prop
Type
max_hops is a ceiling
Leave it at the default of 4 for most corpora. The engine caps the loop at max_hops
but usually stops earlier — a lookup ends in one hop, a two-document contradiction in two.
The response's hops_used reports the actual effort, so you can see how hard the question
was without ever telling the engine.
Out of range
max_hops must be in [1, 8]. A value like 12 returns 400 invalid_request with the
valid range in the fix field. See Errors.
Metadata filters
filter restricts the candidate set to documents whose metadata matches all the given
pairs (equality only).
rekall search "late payment penalties" --store demo \
--filter team=procurement --filter year=2026await store.search("late payment penalties", {
filter: { team: "procurement", year: 2026 },
});store.search("late payment penalties",
filter={"team": "procurement", "year": 2026})Reading the result
Every response is a SearchResult:
passages— the supporting passages, each tagged with thehopthat found it.confidence— the sufficiency-head signal (0..1). Branch on it — see below.hops_used— actual effort taken; ≤max_hops.trace— the hop-by-hop reasoning path (whentraceis on).latency_ms— engine-only, measured server-side.engine— the backend that served the query.
Branch on confidence
The idiomatic pattern: take the 4 ms path when confidence is high, and escalate to an LLM only when it's low.
const r = await store.search(q);
const context = r.confidence >= 0.6
? r.passages // fast path — most queries end here
: await expensiveAgenticSearch(q); // rare fallbackr = store.search(q)
context = r.passages if r.confidence >= 0.6 else expensive_agentic_search(q)Full pattern in the LLM fallback recipe.
The engine field
Every response echoes the backend that served it:
skim-v1— the SKIM neural reasoning loop (the default when the model is loaded).lexrank— an always-available lexical-hybrid fallback, so search never hard-fails if the neural engine is unavailable.
Check GET /v1/health (or rekall --version) to see which engine a server is running.
Next steps
The reasoning trace
Walk a real multi-hop query, hop by hop.
LLM fallback
Use confidence to escalate only when needed.
Search API
The raw endpoint, schemas, and errors.
Verified judgment
With a model version that ships a cross-verifier tower (v0.1 does), every returned passage is judged: the verifier reads the query and the passage together — token-level attention across both — and scores whether the passage actually answers. Passage scores become the average of that judgment and term evidence (measured better than either alone), and the loop stops the moment a read document is judged to answer (gate 0.9 ≈ 0.8 precision).
Judgment costs one encoder forward per judged document. Measured on the 25-capability benchmark store: verified p50 ~380 ms vs ~18 ms unverified. Disable per query when latency matters more than judged scores:
rekall search "..." --no-verify # CLI{ "query": "...", "verify": false } // API — SDKs: verify: false / verify=FalseWhat score means
Each passage's score is its relevance to the query — how strongly the
passage's content answers what was asked (idf-weighted term evidence today; a
trained answerability head takes over when a model version ships one that
measures better). It is the sort key of passages and the value min_score
filters on. The per-hop
address probability (the navigation signal: "where should I hop next?") is a
different number and lives on each trace step's score — so the reasoning
path keeps its own telemetry.
Read passages vs. ranked candidates
The loop reads one document per hop and often stops early. To honour
max_results, the remaining slots are filled with the final hop's best
unread candidates — each carries its relevance score but no hop
field (the CLI labels them candidate). This is deliberate recall
insurance: when the model's top pick at a hop is a near-miss, the runner-up
is one result away instead of invisible.
Read passages informed the controller's reasoning; candidates did not — treat a high-scoring candidate as "also strongly relevant, unexplored".
Ingestion
Add documents to a store — folders, globs, formats, upsert semantics, --watch, streaming big corpora, and metadata. Whole files, no chunking, queryable immediately.
The reasoning trace
Why did it find that? Walk a real multi-hop query hop by hop — the trace is the proof that Rekall reasons, and explainability vector search structurally cannot offer.