App dev: RAG over governed knowledge

The problem

Most RAG stacks are a vector database bolted next to the system of record: embeddings live in one place, access control lives somewhere else (if it exists at all), and by the time a document is chunked and embedded, its provenance and permissions are usually lost. That's fine for a demo. It's not fine for an agent answering questions over data that has row-level access rules, or for a product that needs to say why it gave the answer it gave.

The scenario

A developer is building an internal assistant that answers questions over a governed knowledge base — policy documents, case notes, whatever the tenant has ingested — and needs every answer to (a) respect the same ACL that protects the underlying rows, (b) come back with a source citation, and (c) persist what the agent learns across sessions, not just within one conversation.

Retrieve with governance built in, not bolted on

HYBRID_SEARCH (aliased as RAG_RETRIEVE for a RAG-shaped surface) fuses BM25 and vector similarity with reciprocal-rank fusion, runs through the same ACL and cell-masking path as every other query, and returns results a caller can cite.

PURPOSE 'investigation'
RAG_RETRIEVE FROM Document QUERY 'what were the Q3 findings on vendor risk' LIMIT 10

Typed SDK snippet

from relata import RelataClient, SearchBuilder, Memory
 
with RelataClient(
    "http://localhost:9090",
    bearer_token="relata-dev",
    purpose="product_research",
) as client:
    # Governed retrieval: BM25 + vector, ACL-filtered before results are returned
    results = client.search(
        SearchBuilder("Q3 findings on vendor risk")
        .types(["Document"])
        .limit(10)
        .highlight(True)
    )
 
    context = "\n\n".join(
        f"[{hit.id}] {hit.highlight or ''}" for hit in results.hits
    )
    # ... pass `context` to your LLM call; each chunk carries its own
    # source id, so the model's answer can cite exactly what it used ...
 
# Give the agent memory that persists across sessions
with Memory("http://localhost:9090", purpose="agent-notes") as mem:
    mid = mem.add("User's team owns vendor-risk review for Q3")
    recalled = mem.search("who owns vendor risk", top_k=3)
    # recalled entries are re-ranked by confidence x recency, not just similarity

Why this is more than "add a vector column"

  • Retrieval and access control are the same query. HYBRID_SEARCH / RAG_RETRIEVE runs through the same ACL-aware path as any other read — a principal never gets a chunk back that a direct SELECT on the same row would have denied. See Hybrid Search for the pre-filter vs. post-filter strategy that keeps this fast even for narrow principals.
  • Every result is citable. Hits carry id, score, and highlight — an agent's answer can point back to the exact source row, not a black-box embedding.
  • Three signals, not one. BM25 catches exact jargon and account numbers an embedding blurs past; vector similarity catches paraphrase; identity matching catches the same entity referenced under a different surface form. Fused with RRF, not manually weighted.
  • Memory is rows in the same governed store, not a separate cache. The Memory client's add / search / forget verbs are governed writes and reads — bi-temporal, purpose-scoped, and erasable — not an ungoverned side-channel next to your real database. See Agent Memory.

See also

  • Hybrid SearchHYBRID_SEARCH, RAG_RETRIEVE, RERANK, and the BM25 + HNSW/DiskANN internals
  • Agent Memory — the remember / recall / forget / justify verb surface
  • Governance — ACL-aware pre-filtering and why retrieval can't bypass it
  • Python SDK quickstart — full RelataClient, SearchBuilder, and Memory setup