How to Build a Multi-Tenant AI Agent on a Single Content Lake
A support agent built for your enterprise customers happily answers a question for Tenant A using a document that belongs to Tenant B.
A support agent built for your enterprise customers happily answers a question for Tenant A using a document that belongs to Tenant B. Nobody notices until a compliance review finds the leak, and now every multi-tenant deployment in the company is frozen pending an audit. This is the failure mode that haunts teams who bolt a single retrieval index onto a shared agent: isolation that exists in the application layer but evaporates the moment a query reaches the data.
Sanity Context (previously Agent Context) is built to keep that boundary where it belongs, inside the content backend rather than the prompt. It is the AI Content Operating System for the AI era, an intelligent backend designed to ground agents in real, structured content while keeping each tenant's data governed, queryable, and isolated. When tenancy is a property of the content model and the query, not an afterthought stitched on top of a generic vector store, a single Content Lake can serve many customers without the cross-tenant bleed that sinks naive architectures.
This guide walks through how to design that single-Content-Lake, multi-tenant agent: how to model tenancy, how to enforce it at retrieval time, how to govern per-tenant agent behaviour, and where the usual vector-DB-plus-glue approach quietly breaks.
Why multi-tenant agents fail at the retrieval layer, not the prompt
Most teams discover their multi-tenancy is fake the hard way. The agent's system prompt politely instructs it to only answer from the current customer's documents, and for a while it does. Then a slightly reworded question, a long conversation that pushes the instruction out of the context window, or a retrieval call that returns the top-k nearest neighbors across the entire index surfaces a competitor's pricing doc or another tenant's support ticket. Prompt-level isolation is a suggestion; data-level isolation is a guarantee, and only one of those survives an adversarial user or a tired model.
The root cause is architectural. When you stand up a single shared vector database and write all tenants' embeddings into one namespace, similarity search has no concept of who is asking. The math finds the closest vectors, full stop. You can retrofit metadata filters, but now correctness depends on every query path remembering to apply the filter, every ingestion job remembering to stamp the tenant ID, and every developer understanding that a missed filter is not a bug but a breach. That is a lot of discipline to ask of a system whose whole job is to be queried in novel ways by an LLM.
The reframe this guide argues for is simple: tenancy should be a first-class property of your content, enforced by the query engine, not a label you hope every code path respects. With Sanity Context, the boundary lives in the Content Lake and in the GROQ query itself, so isolation is structural rather than procedural. The model you build your business on, the first of Sanity's three pillars, is where tenant separation gets decided, long before a single token of agent output is generated.
Modeling tenancy in the Content Lake
Start by deciding what a tenant actually is in your content model, because everything downstream inherits that decision. The two common shapes are a tenant reference field on every document and a dataset-per-tenant split. A tenant reference (for example, a `tenant` field pointing at a tenant document) keeps everything in one dataset and lets you query across tenants when you legitimately need to, for analytics or shared knowledge, while still filtering down to one tenant for any agent-facing call. A dataset-per-tenant approach gives you the hardest possible wall between customers at the cost of cross-tenant queries and shared-content reuse becoming awkward.
Most multi-tenant agents land on a hybrid. Shared, non-sensitive knowledge such as product documentation, policy boilerplate, and onboarding content lives in a common dataset, while each customer's private data, their tickets, their account-specific configuration, their uploaded PDFs, is tagged with a tenant reference or held in a per-tenant dataset. Modeling this explicitly in Sanity Studio means the boundary is visible to the humans who govern the system, not buried in application code that only three engineers understand.
Because Sanity treats content as queryable structured data in the Content Lake, the tenant relationship is just another part of the schema you can traverse, validate, and enforce. You can require the tenant field at the schema level, so a document literally cannot be saved without an owner, and you can drive Roles and Permissions off the same model. This is the difference between hoping every ingestion path stamps the right ID and making the absence of an ID a validation error. The content model becomes the contract.

Enforcing isolation at query time with GROQ
This is where a single Content Lake earns its keep. In a naive vector stack, the tenant filter is a parameter you pass to a separate search service and pray nobody forgets. In Sanity Context, retrieval is a GROQ query against the Content Lake, and the tenant predicate sits inside that same query as a non-negotiable clause. A request scoped to one customer never widens its blast radius because the filter and the search are the same expression.
For relevance, hybrid retrieval lives natively inside the Content Lake rather than being assembled from external parts. You blend semantic and lexical signals in a single GROQ query: `text::semanticSimilarity()` for meaning-based matching against dataset embeddings, a BM25-style `match()` for exact keyword and identifier hits, and `score()` with `boost()` to weight the blend. Critically, the tenant filter constrains the candidate set before scoring ever runs, so a customer's question is ranked only against documents that customer is allowed to see. There is no window in which another tenant's vectors are even eligible.
Those dataset embeddings are tied to the content itself, so when a tenant updates a document the embedding refreshes within minutes, with no separate vector pipeline to keep in sync. In a glue-stack architecture, the moment your CMS and your vector DB disagree about a tenant assignment, you have a correctness gap that is also a security gap. Collapsing retrieval into the content backend removes that whole class of drift, because there is only one source of truth and one query path to govern.
Building agent-readable knowledge per tenant
Tenants rarely hand you clean, pre-structured content. They give you support databases, exported PDFs, help-center URLs, and a folder of policy documents that has not been reorganized since 2019. The multi-tenant question is how to turn each customer's messy inputs into agent-readable knowledge without losing the tenant boundary in the process.
Knowledge Bases turn datasets, websites, PDFs, and support databases into agent-readable documents that share the Sanity Context retrieval path. The important word is share: a tenant's ingested PDF and a tenant's native structured content end up reachable through the same GROQ-and-embeddings path, governed by the same tenant predicate, rather than living in a parallel store with its own access rules. You are not running one retrieval system for structured data and a separate, less-governed one for uploaded files. That convergence is what keeps the security model coherent as tenants pour in heterogeneous content.
For the writing and transformation side, Agent Actions provide schema-aware APIs for LLM-driven workflows such as generate, transform, and translate. A tenant onboarding a multilingual support corpus can have content translated and normalized into the schema, tenant field intact, before it ever becomes retrievable. Because the actions are schema-aware, they operate inside the same tenancy model rather than around it, so an automated transform cannot quietly produce a document with no owner. This is the second pillar, automate everything, applied so that scaling the number of tenants scales output rather than headcount.
Governing per-tenant agent behaviour in the Studio
Isolation of data is only half of multi-tenancy. The other half is behaviour: Tenant A wants a terse, compliance-cautious assistant that refuses to speculate, while Tenant B wants a chatty onboarding guide. If those agent instructions live as string literals in application config, changing one tenant's behaviour means a code deploy, and there is no review trail for what the agent was told to do on the day it gave a bad answer.
Sanity Context treats agent instructions as content. They live in the Studio, where they can be edited by the people accountable for tone and policy, versioned, and reviewed, instead of buried in a config file only engineering can touch. Per-tenant instruction documents sit under the same Roles and Permissions model as the rest of the content, so the boundary that protects tenant data also protects tenant configuration.
Content Releases let you stage agent behaviour the same way you stage a website launch. You can prepare a change to one tenant's instructions, review it, and ship it on a schedule, rather than hot-editing a live agent and hoping. When a customer asks why the assistant behaved a certain way last Tuesday, the answer is a reviewable history, not a guess. This is what it means for governance to be native: the shared foundation that legacy CMSes fragment into silos is, here, a single place where humans decide what every tenant's agent is allowed to say. Production agents reach all of this through the Sanity Context MCP endpoint, so the governed, tenant-scoped behaviour you define in the Studio is exactly what the running agent connects to.
Where the vector-DB-plus-glue stack breaks at scale
The standard alternative is a vector database for embeddings, a content backend for the source of truth, a sync job to keep them aligned, and application code to enforce tenancy across all three. Each piece is reasonable in isolation. The failure is in the seams. Multi-tenancy in this architecture means every one of those layers has to agree, on every write and every read, about who owns what, and the layers are maintained by different teams on different release cadences.
Consider what breaks as tenant count climbs. The sync job lags, so a document a tenant deleted for compliance reasons is still retrievable from the vector index for the twenty minutes it takes the pipeline to catch up. A new query path ships without the metadata filter and now returns cross-tenant results, and nobody catches it because the test suite only covered the original endpoint. A reindex doubles your vector store cost per tenant, and you are paying to store the same embeddings your CMS already implies. None of these are exotic; they are the ordinary entropy of keeping four systems in lockstep.
Sanity Context collapses the stack. Embeddings are tied to content so there is no sync job to lag, hybrid retrieval is a GROQ query so there is no second search service to forget to filter, and tenancy is a property of the model so there is one place to get the boundary right. This is the difference between a CMS that stops at publishing and a Content Operating System that operates content end-to-end, including the moment an agent retrieves it on behalf of a specific customer. Fewer moving parts is not just operational convenience; for multi-tenancy it is the security posture.