Authorization chooses the corpus. Ranking chooses within it.

A permission-aware support agent authenticates the caller, turns their role into a vector filter, and retrieves only the handbook records they are allowed to read. The question text can never widen the filter, and a similarity score is never an authorization decision.

From handbook to grounded answer.

Visibility is attached when documents are embedded, and enforced again on every request.

  1. 01

    Embed the handbook

    Validate documents at startup and embed them with flat visibility metadata.

  2. 02

    Build the store

    Load the embeddings into a vector store your app owns.

  3. 03

    Authenticate the caller

    Check the bearer token and the knowledge:read permission before anything else runs.

  4. 04

    Filter, then retrieve

    Derive a vectorFilter from the principal’s role and attach createVectorContext to a per-request agent.

  5. 05

    Answer with a source

    The agent cites the handbook, or says the handbook is insufficient instead of inventing policy.

Built to refuse the wrong answer.

Role-scoped retrieval

The filter comes from trusted server state, so a prompt cannot reach documents outside the caller’s role.

Cited answers

Every answer names its handbook source, or admits the handbook does not cover the question.

Closed failures

A bad token returns 401 before parsing, search, or completion. Invalid JSON, extra fields, and oversized bodies are rejected.

Your vector store

Start in memory, then move to pgvector, Qdrant, Chroma, or LanceDB for production.

A per-request agent with a role filter.

The filter is decided by the principal, not the question.

agent.ts
1import { Agent, createVectorContext } from '@anvia/core/agent'2import { vectorFilter } from '@anvia/core/vector-store'34export function createSupportAgent(principal: Principal) {5  const filter = principal.role === 'manager'6    ? vectorFilter.or(7        vectorFilter.eq('visibility', 'agent'),8        vectorFilter.eq('visibility', 'manager'),9      )10    : vectorFilter.eq('visibility', 'agent')1112  return new Agent({13    id: 'customer-support-rag',14    model: completionModel,15    instructions: 'Answer only from the retrieved handbook documents.',16    context: [createVectorContext({17      store: knowledgeStore, model: embeddingModel, topK: 4, filter,18    })],19  })20}

The example uses an in-memory store, which is not a production persistence strategy. For strong tenant isolation, add namespaces, database policy, or separate indexes on top of filters.

Build it.

Start from the guide, run the example, or look at what else Anvia handles.

Anvia