Skip to documentation

Lens

Retrieval & grounded chat

eli.ai grounds generated factual answers in your knowledge base, and answer claims point back to a source. Clarification and abstention may return guidance without inventing a factual answer. This is GraphRAG: hybrid retrieval fuses lexical, semantic, and graph signals; the answer streams with clickable [Sn] citations; and a groundedness check verifies the claims against the evidence after the fact.

The pipeline

Authorize before recall → retrieve → fuse and expand only through readable evidence → assemble → generate → verify.

Document ACLs are a pre-filter, not a post-filter

Every content entry point carries a ContentAclContext. Workspace-visible documents pass; restricted documents require an intersection between the document's normalized principals and the authenticated actor's application id, email, or trusted provider/group mapping. The predicate is applied in SQL before FTS/vector ranking and is threaded through direct document reads, entity discovery, graph hops, graph manifests, chat, structured query, agents, REST, and MCP. Restricted rows with no matching principal fail closed. Shared workspace keys and named system jobs are explicit service authorities; delegated user keys keep the owner's live actor identity.

Hybrid retrieval + RRF

retrieve(workspace, query, { limit }) runs two channels and fuses them:

  • FTS channelwebsearch_to_tsquery over the chunk tsvector, ranked by ts_rank_cd. Precise on exact terms, names, and jargon.
  • Vector channel — the query is embedded (the embedding call runs outside any transaction) and matched by cosine distance in the active embedding_space. Recovers paraphrases and synonyms. Returns nothing when no embedding provider is configured, in which case retrieval degrades cleanly to FTS-only.

The two ranked lists are combined with reciprocal-rank fusion(RRF, k = 60): a chunk's fused score is the sum of 1/(k + rank) across the channels it appears in, plus a small bonus for chunks that surface in both. RRF needs no score calibration between channels, which is exactly why it survives a heterogeneous FTS-vs-cosine mix. Each returned RetrievedChunk reports which channels it came from.

A third graph channel expands from entities linked in the query out over canonical_relations(1–2 hops, per-hop caps, inverse-degree damping so hub nodes like "the company" don't drown everything), joining structurally related context that pure text search would miss. See Entity graph. Seed documents, entity mentions, neighboring document ids, relation evidence, and the final scoped retrieval all carry the same ACL; an entity grounded only in inaccessible documents cannot leak through its name, counts, edges, or manifest.

Semantic-cache exact and similarity scopes include a hash of the effective content authority. Two actors with different principal sets cannot share a cached answer even when their question, model, prompt, and retrieval settings match. Store is also all-or-nothing for cited dependencies: if any cited document is no longer live or ACL-readable, the answer is not cached. Serve reloads every citation under the current ACL and content hash, so a permission-only revoke forces regeneration even when the bytes did not change.

Context assembly & Source Citations

The fused, de-duplicated chunks are assembled into a token-budgeted context block. Each source is labeled [S1], [S2], … with a server-side map from label to chunkId/docId. The system prompt mandates that the model cite the sources it uses. Crucially, sources stream to the client before the answer tokens, so citations are clickable the moment they appear; a small hold-back buffer stops half-written [S1 labels from flickering.

A grounded answertext
Northwind's FY26 budget freezes headcount in Operations while
funding a warehouse-automation pilot [S2], a decision the CFO tied
directly to the Q1 margin miss [S1][S4].

Retrieved content is untrusted

Chunk text is treated as hostile input. It is wrapped in a delimiter with a random per-request boundary id (a static tag is defeated by a document that contains the literal closing tag) and placed in a user-role block — never interpolated into the system prompt. The renderer blocks remote images and restricts link protocols, closing the retrieved-chunk → rendered-markdown → attacker-host exfiltration path.

Refusal gate & gap events

Before generating, a channel-agreement gate decides whether the retrieved evidence actually supports an answer. When it doesn't, eli.ai refuses rather than hallucinate, and emits a canonicalized gap_eventthat distinguishes "the KB genuinely lacks this" from "retrieval failed to find it." Those gap signals feed the coverage dashboard's interview-next recommendations. Chit-chat and meta questions bypass retrieval and reuse prior sources.

Groundedness check

After the answer completes, an asynchronous check verifies the claims against the cited evidence using the shared judgeAssertionschecker — the same groundedness engine the evals runner uses. The result surfaces as a "verified n/m claims" badge. It is flag-only in interactive chat: a noisy judge must never hard-block or silently rewrite an answer in front of a client. The strict, claim-by-claim NLI version runs in eval runs.

The same retrieval powers agents

The agent harness exposes this exact retrieval through the kb_search tool, and the graph channel through kb_graph_query. An agent grounds its reasoning in the same fused, citable chunks a chat answer does. See Agents & the harness and Tools, MCP & HITL.

Document sources are half the story: when a question touches an entity bound to a live data source, the answer additionally carries [Dn] data-call citations backed by persisted provenance rows. See Semantic ↔ data layer.