Rekalldocs
Guides

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.
rekall search "which supplier contract conflicts with the 2026 policy?" \
  --store demo --hops 4 --max-results 5 --trace
Output
● 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 hops

Add --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=2026
await 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 the hop that 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 (when trace is 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 fallback
r = 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

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=False

What 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".

On this page