Module references
Module reference / Answer it
Lens
- Atlas
Fuse graph neighborhoods with lexical and vector retrieval.
- Warrant
Apply ACL and certification policy before ranking and generation.
- Conduit
Join entitled live rows as Data Citations.
- Ports
Deliver grounded results to people, APIs, MCP clients, and agents.
Lens is the module that turns a question into an answer you can check. It is one of eight capability modules and it is adoptable on its own: two React components, two primary HTTP endpoints, and three read-only observability endpoints.
What it does
Lens searches a workspace's documents and answers questions from them. Every answer comes back with its evidence attached: the atomic claims the model made, the [Sn] sources each claim cites, a retrieval-confidence summary, and an answer-policy verdict. The policy is the part that makes the output trustworthy — before generating it can decide to ask a clarifying question or abstain with pointers to the closest documents, and after generating it attaches a calibrated confidence and caveats rather than rewriting the model's prose.
The raw retrieval channel is also exposed on its own. GET /api/v1/search ranks passages with no model invoked at all, which makes it usable in a workspace that has no chat model configured.
Lens query and search paths
Two entry points. /query runs retrieval, the policy pre-gate, generation, and the post-gate, recording every gate decision. /search stops after retrieval and never calls a model.
Downloads
Concepts
- Lens flow
- Modules
- Lens
Keywords
- clarify · abstain_with_pointers
- retrieval pipeline
- policy pre-gate
- deterministic answer
- no chat-model call
- enforce only
- chat model
- cited generation
- hybrid → graph-fuse → rerank → assemble
- confidence · caveats · conflicts
- retrieve() + optional rerank
- ranked chunks · no model
- policy_decisions
- /api/v1/query
- /api/v1/search
- POST
- agents:run
- proceed
- post-gate
- StructuredAnswer
- append-only
- GET
- kb:read
- SearchResponse
Source and generation provenance
Status: current
Generated at: 2026-08-17T18:34:49.950Z
Source hash: 35ea12fd82471167699568b860a714dcec1169cc665682f976b5670a0cdf7943
Metadata payload hash: 8c929abfb1f90eabddeec11c881b0213d297b56473ace5b1b8cfbc90edb166fa
Canonical appearance
src/app/(docs)/docs/modules/lens/page.tsx:47 route /docs/modules/lens
All appearances
canonical—src/app/(docs)/docs/modules/lens/page.tsx:47route/docs/modules/lens
No mirrored appearances.
Generation versions
App: eli-ai 0.1.0
Mermaid: 11.16.0 · Mermaid CLI: 11.16.0
Node: v26.3.1 · Yarn: 4.17.1
Renderer config hash: 68c10966fe84406ee626034d58bfabd555df9f65f691204b7c46db24038da101
Renderer theme hash: c80287a78d80ad63d27bd5ca348b2ef9a7e2f44da289e436be6484ea28a1b033
Adapter versions: diagramGenerator=2, drawioFlowchart=1, drawioGantt=1, drawioSequence=1, drawioState=1
Full sidecar JSON: lens-query-and-search-paths-35ea12fd.json
Use it standalone
Adopting Lens alone means one package entry, one API scope, and a workspace that has documents in it. Nothing else in the eight-module surface has to be wired up.
Issue a key with the right scope
POST /api/v1/queryrequires theagents:runscope — the call invokes the workspace's chat model, so it gates like an agent run, and that scope is also what licenses the per-callpolicyModeoverride.GET /api/v1/searchrequireskb:read. A key with both covers the whole module. The three read-only surfaces below acceptkb:reador the legacyruns:read.Install only what this module needs
@eli-ai/react/queryimports@eli-ai/client/query,@eli-ai/contracts/query, and the shared provider/hooks. No other capability subpath is pulled in.installbash Wire the provider and drop in the component
EliProviderhas no API-key prop by design. It takes an already-built transport, so credential handling stays outside the render tree — in the browser, point the transport'sfetchat a same-origin route of yours that injects the Bearer key server-side.
What Lens does NOT require
No other module. With includeData left at its default (false) Lens never touches a data connector. Entity detection and graph fusion each degrade to a no-op when the graph is empty — detection failures are swallowed and retrieval simply proceeds without them. No golden set, connector, report, or agent is involved.
No embeddings provider. The vector channel degrades to absence on any failure (unconfigured provider, retired space, network error); full-text alone still answers.
No reranker key. The reranker setting resolves to a no-op passthrough when no provider key is present.
No Next.js. The React package uses ordinary React, URLs, callbacks, and the injected transport.
It does require documents in the workspace, and POST /api/v1/query requires a configured chat model — without one it returns 409 no-model-configured. GET /api/v1/search has no model dependency at all.
React components
@eli-ai/react/query exports exactly two components. Both render into the package's scoped eli-* classes and set eli-root on the outer element, so the optional @eli-ai/react/styles.css export styles them without leaking into your own CSS. Both are client components and must be rendered inside an EliProvider.
EliQuery
A single surface with two tabs. In search mode it renders a list of ranked passages — document title, path, snippet, a percentage badge and a relevance meter, each row a button that fires onDocumentSelect. In ask mode it renders the answer text, a badge for the policy verdict, a verified/total grounded badge when groundedness was computed, and the list of sources. Errors render an inline retry button. A search that matched nothing renders an empty-state card; ask mode renders a placeholder card until the first answer arrives.
EliQuery props
EliSearch
A convenience wrapper that renders EliQuery with allowAsk={false} and initialMode="search" pinned. It accepts every EliQuery prop except those two, so it is the component to reach for when you want retrieval with no model spend and no way for a user to trigger one.
Props the components deliberately do not expose
EliQuery calls ask({ question }) and search({ q, limit }) — nothing more. That means includeData, maxSources and policyMode on the query body, and rerank on search, are not reachable through these components. Use the headless createQueryClient with useEliMutation from @eli-ai/react/hooks when you need them, or set the workspace-level retrieval and policy defaults.
HTTP API
Five routes. The first two are the module; the last three are its read-only observability surface. Every request carries Authorization: Bearer …, plus X-Workspace-Id when the key is a multi-workspace user key. The apex domain 308-redirects, so non-GET curl examples pass -L.
/api/v1/queryscope: agents:runRun the full Lens pipeline — retrieval, entity detection, the policy pre-gate, one chat-model generation, a best-effort groundedness check, and the post-gate — and return a single structured answer. User keys additionally need the agents:execute capability. Returns 409 with the error code no-model-configured when the workspace has no chat model configured.
Request body
Reading the answer
Four fields on the response are nullable and mean something specific when they are null:
retrievalisnullwhen nothing was retrieved at all. Otherwise it is the fused summary:topScore,meanScore,chunkCount, andchannels, aRecord<string, number>keyed by channel name (fts,vector).groundednessisnullwhen no judge model resolved or the check failed. It is a two-field shape and nothing more:{ verified, total }, both integers — counts of claims the judge could verify against the evidence.policyisnullwhen the effective mode isoff, or when a gate failed (a policy fault never fails the answer). Theverdictis one of six values —proceed,clarify,abstain_with_pointersfrom the pre-gate;answer,answer_with_caveat,abstainfrom the post-gate. On a pre-gate short-circuit the block also carriesclarifyandpointers.usageisnullonly on a served cache hit. A pre-gate short-circuit reports zero tokens rather than null, because no model ran.
retrievalDebug is optional and purely informational — it never changes the sources or the verdict. See Retrieval pipeline for what each stage count means, and Answer policy for how confidence is computed.
/api/v1/searchscope: kb:readHybrid retrieval — full-text and pgvector, reciprocally fused — over the workspace's chunks. No model is invoked. User keys additionally need the documents:read capability. An empty or absent q returns an empty result set rather than an error.
Query parameters
Two details the shape hides
snippet is the chunk content truncated to 240 characters with an ellipsis appended — it is not a highlighted extract. rerankScore is omitted entirely (not null) when reranking did not run, which is why the contract types it as optional.
/api/v1/policy/decisionsscope: kb:read or runs:readThe append-only answer-policy decision log, newest first. Read-only — rows are written by the answer paths, never through this API. User keys additionally need the governance:read capability.
Query parameters
/api/v1/policy/statsscope: kb:read or runs:readDecision counts by verdict and by surface over the last 30 days — the rollout dial for moving a workspace from shadow to enforce. Same gate as the decision log.
/api/v1/cache/statsscope: kb:read or runs:readHealth of the semantic answer cache that sits in front of the /query path. Read-only; entries are written by the answer path and invalidated by document changes. User keys additionally need the documents:read capability.
hitRate is an estimate, and the client for these three lives elsewhere
hitRate is computed as totalHits / (totalHits + total) — each stored entry stands in for at least one prior miss. It is null when there is no data.
These three read surfaces are not on @eli-ai/client/query. They live on createQualityClient from @eli-ai/client/quality as listPolicyDecisions, getPolicyStats and getCacheStats, and the React surface for them is EliQuality in @eli-ai/react/quality — a component shared with Crucible, whose eval tabs you get whether or not you want them.
Data elements
Every table below is tenant-scoped: each carries a workspace_id column and a row-level-security policy that constrains both reads and writes to the workspace of the surrounding transaction. Lens never selects across workspaces, and a key resolved for workspace A cannot read workspace B's rows even through a crafted id.
| Table | Lens | Columns that matter | What a row means |
|---|---|---|---|
| documents | read | id · path · title · verification · acl_visibility · acl_principals · deleted_at | One source document. Lens filters on deleted_at and the two ACL columns before ranking, and reads verification into the policy's authority features. |
| chunks | read | id · doc_id · ord · heading_path · content · content_hash · token_count | The retrievable unit. The full-text channel searches a generated tsvector column on this table; content becomes the [Sn] snippet. |
| chunk_embeddings | read | chunk_id · space_id · embedding | The pgvector cosine channel. Absent or unconfigured, retrieval degrades to full-text only rather than failing. |
| embedding_spaces | read | id · provider · model_id · dim · status | Which embedding model the vector channel queries. At most one row per workspace has status 'active'. |
| entities | read | id · authority · lifecycle_status | Governance labels for entities detected in the question. Rows in draft or pending_review are dropped from the answer; a certified entity feeds the policy's hasCertifiedConcept feature. |
| policy_calibrations | read | version · coeffs · active | The fitted confidence coefficients the gates score with. At most one active row per workspace; a missing or unreadable row falls back to the built-in bootstrap defaults. |
| policy_decisions | write · read | id · surface · question · stage · verdict · mode · confidence · calibration_version · features · reasons · caveats · created_at | One append-only row per gate decision — a pre row and, when generation ran, a post row. surface is 'structured' for /api/v1/query. This is what GET /policy/decisions and /policy/stats read. |
| evidence_conflicts | write | claim_hash · claim_text · chunk_a · chunk_b · doc_a · doc_b · kind · stance_a · stance_b · detected_by | A detected disagreement between two cited chunks, deduped on the canonically ordered chunk pair. Written only when conflict detection is on and a judge model resolves. |
| semantic_cache_entries | write · read | exact_key · scope_key · question · embedding · answer · model_id · prompt_version · hit_count · expires_at · invalidated_at | A replayable StructuredAnswer minus usage, keyed by question + model + prompt version + effective config. Off by default; never written for includeData answers. |
| semantic_cache_dependencies | write | entry_id · doc_id · doc_content_hash | The [Sn] documents a cached answer depends on, with the content hash at store time — the lookup that invalidates an entry when a source document changes. |
The two rows you will actually query
policy_decisions is the audit artifact: it answers "why did the system decline this question?" with the exact feature snapshot the gate scored, and it is written in shadow mode too — which is what lets you measure a policy before you let it change any answer. semantic_cache_entries is the cost artifact: it is off by default, and answers with includeData: true are never cached because their [Dn] rows are non-deterministic.
Feature map
The surfaces above resolve to six feature areas. Each names the concrete components that deliver it — the same names the code map and the invariants below pin to repository paths — and where a consumer actually meets it.
| Feature area | What it provides | Delivered by | Surfaces |
|---|---|---|---|
| Hybrid retrieval | Full-text and pgvector channels run in parallel, reciprocal-rank fused with a both-channels bonus, adjacent same-document chunks merged. | Retrieval primitive over the chunks, chunk_embeddings, and embedding_spaces tables; the embedding model for the vector channel. | GET /api/v1/search, MCP kb_search, first stage of every answer. |
| Pipeline orchestration | Query understanding, bounded sub-query union, graph fusion, cross-encoder rerank, and token-budget assembly — each stage optional and individually degradable. | Pipeline orchestrator with the understanding, graph-fusion, rerank, and assembly stages. | POST /api/v1/query, search with rerank=true, retrievalDebug stage counts. |
| Grounded answer generation | One chat-model generation over assembled [Sn] context, claims parsed with their citations, a best-effort groundedness verdict, and optional [Dn] live rows. | Structured-answer orchestrator, chat and judge model slots, the data bridge. | POST /api/v1/query, MCP eli_query, EliQuery ask mode. |
| Answer policy | A pre-gate that can clarify or abstain before any model spend, and a post-gate that scores calibrated confidence, attaches caveats, and detects evidence conflicts. | Policy router and confidence model; coefficients from the policy_calibrations table; decisions into policy_decisions. | policy block on every answer, GET /api/v1/policy/decisions and /policy/stats. |
| Semantic answer cache | Tiered reuse — exact key, cosine similarity, then an LLM equivalence check — with serve-time revalidation against every dependency document. | Semantic cache service over the semantic_cache_entries and semantic_cache_dependencies tables. | Transparent on the query path; GET /api/v1/cache/stats. |
| Access and governance filtering | Content-ACL and deleted-document filtering before ranking; verification and lifecycle labels folded into policy features and entity traces. | Content-ACL helpers applied inside retrieval; governance columns on the documents and entities tables. | Every retrieval and cache-serve path, including MCP. |
System architecture
Two entry points share one pipeline. The search route stops after retrieval; the query route continues through the policy gates, the chat model, and the decision log. The cache fronts only the answer path, and the observability routes are pure reads over what the gates and the cache already persisted.
Lens — system architecture
Rectangles are API routes and services, rounded nodes are model dependencies resolved per call, and cylinders are workspace tables under row-level security. The pipeline stages degrade independently; the policy gates and the decision log sit only on the answer path.
Downloads
Concepts
- System architecture
- Public surface
- Retrieval pipeline
- Answer path
- Modules
- Lens
Keywords
- eli_query + kb_search (MCP tools)
- POST (API)
- GET (API)
- GET * + (API)
- model: judge
- Graph fusion (service)
- model: reranker
- model: chat
- model: embeddings
- store, append-only
- Hybrid retrieve: fts + vector (service)
- Context assembly: token budget + MMR (service)
- Structured-answer orchestrator (service)
- Semantic cache (service)
- Policy pre-gate (service)
- Policy post-gate (service)
- Data bridge to governed queries (service)
- /api/v1/query
- /api/v1/search
- /api/v1/policy
- /api/v1/cache/stats
- store
Source and generation provenance
Status: current
Generated at: 2026-08-17T18:34:57.016Z
Source hash: ad1f57d8f0572c48796c97b8702c4ec7b4817807226d0f74d53d278d27bc6199
Metadata payload hash: da1ed071e039931c3c3650042bab391050cf541bd913adac9c67ce5c5ddb22c2
Canonical appearance
src/app/(docs)/docs/modules/lens/page.tsx:59 route /docs/modules/lens
All appearances
canonical—src/app/(docs)/docs/modules/lens/page.tsx:59route/docs/modules/lens
No mirrored appearances.
Generation versions
App: eli-ai 0.1.0
Mermaid: 11.16.0 · Mermaid CLI: 11.16.0
Node: v26.3.1 · Yarn: 4.17.1
Renderer config hash: 68c10966fe84406ee626034d58bfabd555df9f65f691204b7c46db24038da101
Renderer theme hash: c80287a78d80ad63d27bd5ca348b2ef9a7e2f44da289e436be6484ea28a1b033
Adapter versions: diagramGenerator=2, drawioFlowchart=1, drawioGantt=1, drawioSequence=1, drawioState=1
Full sidecar JSON: lens-system-architecture-ad1f57d8.json
How to read the Lens architecture
- Split the two entry points: The search route calls the retrieval pipeline in listing mode and returns ranked chunks; the query route wraps the same pipeline inside the structured-answer orchestrator.
- Follow the pipeline stages: Understanding rewrites the question, hybrid retrieve fuses the full-text and vector channels, graph fusion adds structurally related chunks, the reranker re-scores, and assembly fits a token budget.
- Watch the gates around the model: The pre-gate can short-circuit before the chat model runs; the post-gate scores the draft with calibrated confidence and writes the decision either way.
- Note what is read-only: The three observability routes only read policy_decisions and the cache tables — nothing on the public surface writes them directly.
- Trust boundary
- Content-ACL filtering happens inside retrieval, before ranking, so no downstream stage — reranker, cache, or model — ever sees a chunk the caller could not read.
- Durable state
- policy_decisions is the append-only audit trail of every gate verdict; semantic_cache_entries and semantic_cache_dependencies hold replayable answers pinned to source content hashes.
Failure paths
- Vector channel unavailable degrades retrieval to full-text only
- Reranker missing resolves to a no-op passthrough
- Judge failure returns groundedness null, never an error
- No chat model configured returns 409 before retrieval
Signals
- Verdict mix in policy stats (answer / caveat / abstain)
- retrievalDebug stage counts and reranker kind
- Cache hit rate and invalidation counts
- Groundedness verified/total ratio
Where the logic lives
The code map pins each component to its module. Table names stay in the notes; all of them are defined in the schema module with workspace row-level security.
| Component | Kind | Lives at | Notes |
|---|---|---|---|
| Structured query route | API | src/app/api/v1/query | queryWorkspace — validates the body, resolves the key, and returns 409 no-model-configured when the workspace has no chat model. |
| Search route | API | src/app/api/v1/search | searchDocuments — listing-mode retrieval honoring the caller's limit; opt-in rerank; never invokes a chat model. |
| Observability routes | API | src/app/api/v1/policy · src/app/api/v1/cache | listPolicyDecisions, getPolicyStats, and getCacheStats — read-only projections of policy_decisions and the cache tables. |
| Structured-answer orchestrator | service | src/server/query/structured.ts | answerStructured — retrieval, entity detection, both gates, generation, groundedness, cache store, and the [Sn]/[Dn] envelope. Also called directly by MCP eli_query. |
| Hybrid retrieval primitive | service | src/server/retrieval/index.ts | Parallel full-text and vector channels, reciprocal-rank fusion, broadened OR retry when the strict pass misses, adjacent-chunk merging. |
| Pipeline orchestrator | service | src/server/retrieval/pipeline.ts | Stage toggles from workspace settings; per-stage try/catch degradation; listing versus answer-context assembly. Stages live beside it in src/server/retrieval/understand.ts, src/server/retrieval/graph-fusion.ts, and src/server/retrieval/assemble.ts. |
| Rerankers | model | src/server/retrieval/rerank | Voyage, Cohere, and a self-hosted ONNX cross-encoder, plus the no-op fallback the pipeline resolves when no provider key is present. |
| Policy gates | service | src/server/policy/router.ts | preGate (retrieval floor, deterministic ambiguity clarify, optional one-call LLM pre-check) and postGate (confidence thresholds, unverified-only cap). |
| Confidence and calibration | service | src/server/policy/confidence.ts | Pure logistic model over thirteen named features; active coefficients resolved from the policy_calibrations table by src/server/policy/calibration.ts; decisions persisted by src/server/policy/decisions.ts. |
| Semantic cache | service | src/server/cache/semantic-cache.ts | Exact, cosine, and judge-verified tiers with serve-time revalidation; exact and scope keys built in src/server/cache/keys.ts. |
| Model resolution | model | src/server/ai | resolveModel for the chat and judge slots and resolveEmbeddings for the vector channel, with usage accounting middleware. |
| Content ACL | service | src/server/authz/content-acl.ts | Visibility predicates applied inside both retrieval channels and at cache serve time. |
| Table definitions | store | src/server/db/schema.ts | documents, chunks, chunk_embeddings, embedding_spaces, entities, policy_calibrations, policy_decisions, evidence_conflicts, semantic_cache_entries, semantic_cache_dependencies. |
| React surface | UI | packages/react/src/query/index.tsx | EliQuery and EliSearch. |
| Headless client + contracts | SDK | packages/client/src/query.ts · packages/contracts/src/query.ts | createQueryClient with ask and search; StructuredAnswer and SearchResponse wire shapes. The three read surfaces live on packages/client/src/quality.ts. |
Primary runtime flow
The highest-value path is queryWorkspace: one question in, one gated, cited, logged answer out. The pre-gate can finish the request deterministically before any chat-model spend; the post-gate scores whatever the model produced and records both decisions.
Lens — primary runtime flow
One queryWorkspace call. Retrieval and detection run in short read transactions, every model call happens outside any transaction, and each gate evaluation appends a policy_decisions row whether the mode is shadow or enforce.
Downloads
Concepts
- Primary runtime flow
- Modules
- Lens
Keywords
- API caller (agents:run)
- POST (API)
- policy_decisions (store)
- clarify or abstain_with_pointers, no chat call
- Embedding model (model)
- Chat model (model)
- Judge model (model)
- Policy gates (service)
- query embedding
- append pre decision row
- Structured-answer orchestrator (service)
- Retrieval pipeline (service)
- understand, retrieve, fuse, rerank, assemble
- embed the rewritten question (vector channel)
- ranked, ACL-filtered chunks + stage counts
- pre-gate over retrieval features
- grounded generation over [Sn] context
- answer draft with claims + citations
- best-effort groundedness check
- post-gate calibrated confidence
- append pre + post decision rows
- StructuredAnswer — sources, policy, usage
- queryWorkspace — question, maxSources, policyMode
- /api/v1/query
- verified/total
- answerStructured
- counts
Source and generation provenance
Status: current
Generated at: 2026-08-17T18:34:47.800Z
Source hash: 52b206dd71cef1c2f057573e51b15c129bda1371cb47bb4337d9c74e4f0eeeba
Metadata payload hash: 95db05f5210b6c672859af4550e078c60d69da7e005a15c873a92ecee0d28f99
Canonical appearance
src/app/(docs)/docs/modules/lens/page.tsx:120 route /docs/modules/lens
All appearances
canonical—src/app/(docs)/docs/modules/lens/page.tsx:120route/docs/modules/lens
No mirrored appearances.
Generation versions
App: eli-ai 0.1.0
Mermaid: 11.16.0 · Mermaid CLI: 11.16.0
Node: v26.3.1 · Yarn: 4.17.1
Renderer config hash: 68c10966fe84406ee626034d58bfabd555df9f65f691204b7c46db24038da101
Renderer theme hash: c80287a78d80ad63d27bd5ca348b2ef9a7e2f44da289e436be6484ea28a1b033
Adapter versions: diagramGenerator=2, drawioFlowchart=1, drawioGantt=1, drawioSequence=1, drawioState=1
Full sidecar JSON: lens-primary-runtime-flow-52b206dd.json
How to read the query flow
- Retrieve before deciding: The pipeline returns ranked, ACL-filtered chunks plus stage counts; those retrieval features are the pre-gate's input.
- Let the pre-gate spend nothing: In enforce mode a clarify or abstain_with_pointers verdict short-circuits with a deterministic answer and zero model tokens.
- Generate, then verify: The chat model writes the draft with [Sn] citations; the judge model checks groundedness best-effort and feeds the result into the post-gate features.
- Score and record: The post-gate computes calibrated confidence, picks answer, answer_with_caveat, or abstain, and the decision rows land in the append-only log.
- Trust boundary
- policyMode can only be overridden by a caller holding agents:run, and shadow mode computes and records every verdict without altering the answer.
- Durable state
- Every gate evaluation appends to policy_decisions with the exact feature snapshot scored; cacheable answers persist with their dependency document hashes.
Failure paths
- Pre-gate abstains with pointers when retrieval is below the floor
- Groundedness check failure yields null, not an error
- Policy fault returns policy null rather than failing the answer
- Cache store is skipped for answers with live data rows
Signals
- Pre-gate short-circuit rate by question class
- Calibrated confidence distribution per verdict
- Token usage and cost per answered question
- Conflict detections written to evidence_conflicts
Internals and invariants
Hybrid retrieval
The primitive runs its two channels in parallel: a websearch-style full-text pass that retries with a broadened OR rewrite when the strict pass misses entirely, and a pgvector cosine pass that embeds the question through the workspace's active embedding space. Reciprocal-rank fusion (k = 60) merges the lists with a bonus for chunks both channels found, and adjacent chunks of the same document collapse into one result.
- Invariant— deleted documents and rows outside the caller's content ACL are excluded inside the channel queries, before ranking, so nothing downstream ever handles an unreadable chunk. Enforced in
src/server/retrieval/index.tswith predicates fromsrc/server/authz/content-acl.ts.
Pipeline orchestration
The orchestrator layers understanding, graph fusion, reranking, and assembly over the primitive. Each stage is wrapped in its own try/catch: a failure skips the stage, records it in the stage counts, and continues. Assembly enforces the token budget with MMR de-duplication for answer context, or returns a plain ranked listing for the search route.
- Invariant — a stage failure never fails retrieval; understanding, graph fusion, and rerank each degrade to a no-op recorded in stages. Enforced in
src/server/retrieval/pipeline.ts. - Invariant — with all stages inactive the pipeline returns the raw retrieve result byte-identical, chunk set and order, which is what keeps eval baselines reproducible. Enforced in
src/server/retrieval/pipeline.ts.
Grounded answer generation
The orchestrator resolves the chat model, assembles the [Sn] context, and requests one generation whose claims must cite their sources. Entity detection explains which graph nodes the question touched; when live data is requested, the data bridge executes fully-resolvable bindings and the answer gains [Dn] rows. Groundedness runs after generation as a best-effort judge pass.
- Invariant — model and provider calls happen outside any workspace database transaction; retrieval and detection use their own short read transactions. Enforced across
src/server/query/structured.tsandsrc/server/retrieval/pipeline.ts.
Answer policy
The pre-gate orders its checks deterministic-first: empty or below-floor retrieval abstains with pointers, a name resolving to multiple live entities clarifies with options, and only then may an optional single LLM pre-check run. The post-gate is pure: it scores the calibrated logistic confidence over the named feature vector and routes to answer, answer_with_caveat, or abstain.
- Invariant — every gate evaluation appends one decision row, in shadow mode as in enforce, and a policy fault degrades to policy null rather than failing the answer. Enforced by
src/server/policy/decisions.tsand the orchestrator's gate wiring. - Invariant — an answer resting only on unverified sources has its confidence hard-capped below the answer floor, so it can never pass as a clean answer. Enforced by postGate in
src/server/policy/router.ts.
Semantic answer cache
Lookup proceeds exact key, then cosine similarity above the direct-serve threshold, then a cheap LLM equivalence check for the band below it. A candidate only serves after revalidation: every dependency document must still be readable under the caller's ACL and its content hash must match the hash captured at store time.
- Invariant — a content change or a permission revocation forces a miss at serve time even before asynchronous invalidation catches up. Enforced in
src/server/cache/semantic-cache.ts. - Invariant — fuzzy tiers are scoped by the model, prompt version, and effective config baked into the scope key, so a paraphrase never matches an entry produced under a different configuration. Enforced by
src/server/cache/keys.ts.
Access and governance filtering
Authorization is a retrieval concern, not a rendering concern: the ACL predicates join into the channel SQL itself, entity governance labels drop draft and pending-review concepts from answers, and document verification feeds the confidence features rather than being displayed only.
For a self-hosting team the supported extension points are the pluggable edges: an additional reranker kind or an extra recall channel contributed through the plugin surface (both stay behind the host's ACL filtering and budgets), fitted policy coefficients written through the calibration surface instead of the bootstrap defaults, and custom UI built on the headless query client. The gate ordering, the decision log, and serve-time cache revalidation are the contract the rest of the platform assumes and are not designed to be swapped out.
How it composes
Lens is the module the other seven point at. It works alone, and each neighbor makes it measurably better at something specific.
What it gains
- Intake— the honest dependency. Lens ranks and cites documents; something has to put them there. Intake's connectors also populate
acl_visibilityandacl_principals, which is what lets restricted documents be retrievable by the right people and invisible to everyone else. Without Intake you can still write documents through any other path — Lens does not care how they arrived. - Atlas — the entity graph turns on two things. Graph fusion pulls in chunks that are structurally related but textually distant, reported as
retrievalDebug.stages.graphAdded. And question-entity detection populates theentitiesarray on the answer. With no graph, both degrade silently to nothing and retrieval proceeds on text alone. - Warrant— governance is what the policy's confidence is partly made of. Document
verificationfeeds the authority features (an answer resting only on unverified sources is capped), entitylifecycle_statuskeeps draft and pending-review concepts out of answers entirely, and Warrant's calibration surface writes thepolicy_calibrationsrow the gates read. Without it every document isunverifiedand the gates score against bootstrap coefficients — the policy still runs, just less sharply. - Conduit — genuinely required for one feature.
includeData: trueexecutes the data bindings of detected entities and adds[Dn]citations to the answer. With no connectors configured the call does not fail: the data layer is caught,dataCallscomes back empty, and the doc-grounded answer ships. Live data also has one policy consequence — anabstain_with_pointerspre-verdict is overridden toproceedwhen data actually ran, because rows can ground an answer that documents could not. - Lineage — Lens hands you
docIdandchunkIdon every source. Lineage is what turns those ids into a provenance trail and a blast radius. Without it the citation is still a real, resolvable document reference. - Crucible — the only way to know whether a retrieval or policy change helped. Because the two non-deterministic stages both have deterministic-off paths, an eval run can pin Lens to a fully reproducible configuration. Crucible also owns
/api/v1/evalsand/api/v1/qrels, which are not part of Lens. - Ports — the same pipeline is reachable over MCP as the
eli_querytool, so an external agent gets identical grounding and the identical policy verdict without going through HTTP.
What still works with none of them
A workspace with documents and a chat model gets the complete Lens contract: fused full-text retrieval, cited claims, a retrieval-confidence summary, groundedness when a judge model is configured, and every policy verdict including clarify and abstain. The parts that need neighbors — graph fusion, live data, verification-weighted confidence, fitted calibration — are the parts that were designed to be absent, and each one is a no-op rather than an error when it is.
Going deeper
Retrieval pipeline explains the five stages and how each degrades. Answer policy covers the verdict router and the confidence model. Tune retrieval quality and Tune the answer policy are the operator loops. Structured query is the endpoint reference, and JavaScript & React SDK covers the transport, workspace selection, and error types shared by every module.
Extending Lens
rerankers adds a reranker kind next to Voyage, Cohere and the self-hosted ONNX model (the host owns the reported kind, so a plugin cannot impersonate voyage in the trace), and candidateChannelsmerges an extra recall channel into the candidate pool under an 800 ms budget. A contributed channel is ACL-filtered downstream like every other channel, so the worst it can do is promote documents the caller could already read. Lens emits answer.produced and cache.invalidated. See the extension model for the contract and the trust boundary, and Build a plugin for a worked example.