Grounding
Grounding means giving an agent evidence from the tenant’s own corpus, so that it
reasons over retrieved records rather than only its prompt. FACE’s grounding layer is
ai.Store. It is built on the platform’s retrieval corpus (store/corpus), which is a
Comet hybrid index combining a dense vector index, BM25 and metadata.
Retrieval tiers
MemoryStore.Ground tries three tiers in order and returns the first that answers:
flowchart TD
Q[Grounding request<br/>name + fingerprint] --> M{Exact memo hit?}
M -- yes --> R1[Cached response]
M -- no --> V{Embedder and<br/>corpus available?}
V -- yes --> C[Comet hybrid search<br/>tenant index + platform index]
C -- hits --> R2[Top results with scores]
C -- no hits --> D
V -- no --> D{DCI corpus<br/>configured?}
D -- yes --> G[DCI keyword grep<br/>inside the corpus root]
D -- no --> N[Empty: nothing cited]
- Memo cache. An exact
(name, fingerprint)hit returns the stored response. No model call is needed. - Comet hybrid search. The query is embedded with the model’s query instruction (see Sovereign models). The search runs vector plus BM25 and returns the top five results with their scores.
- DCI keyword grep.
inference/dciruns a sandboxed keyword search confined to the corpus root named byFACE_DCI_CORPUS_DIR. If that variable is unset, there is no DCI engine at all: the tier doesn’t search an empty directory, it doesn’t search, and nothing is cited as local evidence.
Store.GroundEvidence is the direct evidence lookup. The domain graph uses it for
relevant documents, and the catalogue item resolver uses it too. It keeps two answers
apart:
(nil, nil): the corpus was searched and has nothing to say.ErrRetrievalUnavailable: the corpus could not be searched. The cause is one of no corpus, no embedder, an embedding error, a vector of the wrong size or all zeros, or a search failure.
Callers treat those two differently. “Our data says nothing” and “retrieval is down” lead to opposite actions.
Per-tenant scoping
Tenant isolation is structural:
- The corpus keeps one index per tenant plus a platform-wide index (tenant
""). IndexDocumentwrites to the calling tenant’s index. The tenant comes from the request’s authenticated context. It is never read from document metadata: atenantkey in metadata is dropped, so a document can’t re-scope itself.- A search reads the caller’s own index and the platform-wide index, merges them by score, and cuts to the top k. It never reads another tenant’s index. On equal scores, the tenant’s own hits rank above platform hits.
- A tenant id the corpus can’t use as a path component is hashed into its own index name. It never falls back to the shared index.
The embedding-dimension requirement
The index is built for MODEL_EMBEDDING_DIM dimensions (default 768). A query
vector must match that exactly after the embedding format’s fit:
- At startup, FACE probes the embedder through the same path a query takes, and logs whether semantic grounding is active or inactive.
- When indexing, a size mismatch means every document is dropped, so FACE reports it once as a configuration fault instead of silently skipping each document. All-zero vectors are skipped.
- When querying, a mismatch makes the Comet tier step aside, and grounding falls through to DCI.
Durability
Every write to the corpus goes to a write-ahead log before it is applied, and snapshots
are taken periodically. With an object store configured, the WAL and snapshots live
there. Without one, they go in a local directory with owner-only permissions
(directories 0700, files 0600). The corpus holds document text as well as vectors,
so it’s treated as customer data at rest. Encryption at rest is covered in
Security.
What this does not establish
- A grounded answer is not a correct answer. Retrieval supplies context; the model can still misread it.
- A DCI hit is a keyword match, not a semantic one.
- Grounding is only as good as what was indexed. An empty corpus grounds nothing, and that is reported as an absence, not as a finding.
- FACE can’t verify which embedding model it’s using. Nothing verifies the model
behind
EMBEDDING_URL. See Sovereign models.