Retrieval.

Three strategies with different failure modes, run together and fused — because no single one of them is reliable across the range of questions people actually ask.

Why not just one

Lexical search is exact and useless for paraphrase. Vector search handles paraphrase and cheerfully returns something plausible when the corpus has nothing. Graph traversal finds relationships and says nothing about text. Each is a good answer to a different question, so the planner picks a mix per query rather than committing to one.

Question/v1/askClassify + planpick retrieversLexicalfull-textSemanticpgvectorGraphedges + pivotsFuse + rankone contextANSWERevery retained hit keeps its source, fetch time and confidence — that is what the evidence chain reports
Fig. A question is classified, retrievers are planned, and their results fused into one ranked context.

The strategies

StrategyGood atFails at
LexicalExact identifiers — a CVE id, a hash, a domainParaphrase, synonyms, questions
SemanticNatural-language questions, conceptual similarityPrecision on identifiers; returns near-misses confidently
GraphPivots and relationships between entitiesAnything textual

Lexical runs over Postgres full-text. Semantic runs over kb_chunks embeddings in pgvector. Graph walks graph_edges.

Fusion

Results from each retriever are merged and re-ranked into one context. What survives is what the answer is generated from, and every surviving hit keeps its document id — which is how the citation later resolves.

  • Duplicates across strategies collapse to one hit, keeping the best score.
  • Quarantined documents are excluded before ranking, not demoted.
  • Tenant scope is applied by row-level security, so it cannot be forgotten in the query.

The trace

Every response reports which retrievers ran, how many hits each returned and how long each took. This is the difference between a system you can debug and one you have to trust.

What to look for in a trace

A semantic-only trace with few hits on a question containing an exact identifier usually means the identifier was not matched lexically — worth checking the corpus actually contains it. A graph trace with zero hits means no relationships were materialised for that entity yet, not that none exist upstream.

Live fetch

Some questions are about something not in the corpus. Where a live lookup is available, retrieval can fall back to fetching — but anything it uses is retained first, so the citation still points at a stored document rather than at a URL that may have changed since.

Retrieval — OmniIntel docs · OmniIntel