Short answer: Each tenant lives on its own shard; queries name a tenant—they never scan a shared pool and hope a user_id filter held.
Multi-tenancy keeps one customer’s memories out of another’s result set. Application-only filters are weak isolation: someone forgets the predicate on a background job or tool call, and the leak is silent while the agent still answers. Tenant states—ACTIVE, INACTIVE, OFFLOADED—trade readiness for cost so quiet tenants stay cheap. Engram maps users and groups onto that model: user_id enforces hard isolation; groups keep use cases apart; property scopes are softer cuts that organize without becoming a second security boundary. Soft scopes organize; hard scopes isolate. A letterpress-shop pattern shows two presses writing similar chase notes that still fail to read each other. Pair multi-tenancy with RBAC so operators can open the right bay without weakening the isolation primitive.
Multi-tenancy in Weaviate is the isolation primitive that keeps one customer’s memories out of another customer’s result set. Each tenant lives on its own shard inside a shared collection. Queries name a tenant. They never scan a shared pool and hope a filter held. This chapter explains why shard-per-tenant isolation beats application-only filters, how tenant states (ACTIVE, INACTIVE, OFFLOADED) trade readiness for cost, how Engram maps users and groups onto that model, and what that looks like when two letterpress shops share one Engram project without leaking chase notes.
Why Is a Filter Alone a Weak Isolation Boundary?
Many early agent stacks store every user’s memories in one index and add a user_id predicate on search. That works until someone forgets the predicate on a background job, a cache key, or a tool call that rebuilds the query. The leak is silent. The agent still answers. It just answers with someone else’s facts.
Weaviate’s multi-tenancy treats the tenant as a storage boundary, not a convention. When a collection enables multi-tenancy, each tenant gets its own shard. Objects for tenant A never sit in tenant B’s shard. A query must name the tenant. There is no cross-tenant candidate set to filter down. Delete a tenant and you delete its shard. That is the shape auditors want for offboarding and for proof that one workspace cannot read another.
If isolation is physical at the shard layer, the next design question is how you keep thousands of quiet tenants from burning hot memory forever.
How Do Tenant States Keep Quiet Tenants Cheap?
Not every tenant is online at once. A SaaS memory product may have tens of thousands of workspaces. Only a fraction query in any hour. Weaviate’s Tenant Controller moves each tenant between states so idle shards stop consuming hot resources.
An ACTIVE tenant is loaded for reads and writes. An INACTIVE tenant stays on local disk but rejects access until you reactivate it. An OFFLOADED tenant moves to cold cloud storage for long dormancy. Transient OFFLOADING and ONLOADING states cover the move itself. Only ACTIVE tenants serve traffic. Access against any other state returns an error until the tenant is brought back. State changes are eventually consistent across a cluster, so reactivation is not always instantaneous on every node. Plan for a short warm-up after you restore a cold workspace.
That lifecycle is why multi-tenancy scales past “one collection per customer.” You share schema and ops. You still get per-tenant delete, per-tenant indexes, and the option to park dormant tenants without tearing down infrastructure.
Agent memory products still need a higher-level vocabulary than raw shard ids. Engram supplies that layer on top of the same primitive.
How Does Engram Map Users and Groups Onto Weaviate Tenants?
Engram does not ask your application to manage shard names. It exposes scopes and groups. User-scoped topics require a user_id on every add and search. Hard isolation between users is enforced with Weaviate multi-tenancy underneath. You cannot omit user_id on a user-scoped topic and accidentally search the whole project. Groups isolate distinct use cases the same way. Memories in one group do not mix with another group’s topics, even when topic names look similar.
Property scopes add a softer cut inside a user. A conversation_id or shop-floor id can narrow extraction and search without becoming a second security boundary. Soft scopes organize. Hard scopes isolate. Engram keeps that distinction clear so product teams do not confuse a filter preference with a tenancy guarantee.
Those rules are easiest to trust when you see two tenants write similar text and still fail to read each other. Here is that check in a letterpress shop scenario.
What Does Cross-Tenant Isolation Look Like in Engram Code?
Imagine a shared Engram project for a cooperative print shop. Press A and Press B both store chase notes, ink mixes, and customer reprint preferences. The content can sound alike. The tenants must not.
import os
from engram import EngramClient, HybridRetrieval
client = EngramClient(api_key=os.environ["ENGRAM_API_KEY"])
press_a = "letterpress-chase-4"
press_b = "letterpress-chase-9"
group = "print_shop"
run_a = client.memories.add(
"Chase 4 runs 12-point Caslon with a 0.3mm packing sheet for the museum postcard job. "
"Ink mix stays cool gray with a drop of transparent white for the second pass.",
user_id=press_a,
group=group,
)
client.runs.wait(run_a.run_id)
run_b = client.memories.add(
"Chase 9 locks the wedding suite on soft packing and a warmer black. "
"Do not reuse museum postcard settings on this form.",
user_id=press_b,
group=group,
)
client.runs.wait(run_b.run_id)
# Press A asks about packing for postcards — must stay inside Press A memories
hits_a = client.memories.search(
query="packing sheet for museum postcard Caslon",
user_id=press_a,
group=group,
retrieval=HybridRetrieval(alpha=0.55),
)
# Same phrasing under Press B must not surface Press A's chase notes
hits_b = client.memories.search(
query="packing sheet for museum postcard Caslon",
user_id=press_b,
group=group,
retrieval=HybridRetrieval(alpha=0.55),
)
assert any("Caslon" in m.content for m in hits_a)
assert not any("museum postcard" in m.content.lower() for m in hits_b)
Both presses share the Engram project and the print_shop group. Isolation rides on user_id, which Engram maps onto Weaviate tenant boundaries for user-scoped topics. Press B’s search uses the same words. It still cannot pull Press A’s postcard packing note. That is storage-layer isolation, not a forgotten WHERE clause.
Once you trust the boundary, the remaining product question is who may open which tenant in the first place. That is access control on top of tenancy, not a replacement for it.
When Should You Still Combine Multi-Tenancy with Application Auth?
Multi-tenancy answers “which shard may this query touch.” It does not invent the caller’s identity. Your gateway must derive user_id from a verified session or service credential. Never accept a tenant key from model output, a free-form tool argument, or an untrusted request body. If the model can choose the tenant string, the boundary is theater.
The same rule applies to ops paths. Admin impersonation, evaluation harnesses, and batch reindex jobs need an explicit, audited tenant context. Cross-tenant probes belong in CI. Seed two canary memories under distinct user_id values. Query as one identity with terms that only match the other. Assert empty results. That test catches regressions filters never catch.
Multi-tenancy also pairs with Weaviate role-based permissions when human operators share a cluster. Tenant-scoped roles can grant read on one hospital or one press bay without opening the rest. Isolation of data and authorization of who may name a tenant are complementary controls. You want both before you call the system production-ready for customer memory.
Our next chapter, How does role-based access control work in Weaviate?, picks up that authorization layer. It shows how roles and permissions sit beside tenant shards so operators can open the right bay without weakening the isolation primitive you just set.