Multi-Tenancy
How Weave scopes collections, documents, and chunks to app and tenant contexts.
Weave is tenant-scoped by design. Every entity — collection, document, chunk — carries a TenantID and AppID. These are automatically stamped from the context at every operation, and all store queries enforce them.
Setting the scope
Use the weave.WithTenant and weave.WithApp helpers to inject scope into a context:
import "github.com/xraph/weave"
ctx = weave.WithTenant(ctx, "tenant-1")
ctx = weave.WithApp(ctx, "myapp")Both values are propagated automatically — you do not need to pass them to individual engine.* calls. Every ingestion, retrieval, collection create, and delete operation reads the tenant and app from the context.
Scope enforcement
The engine calls weave.TenantFromContext(ctx) and weave.AppFromContext(ctx) before every store operation and injects the values as WHERE clause filters:
-- MetadataStore query (internal)
SELECT * FROM weave_collections
WHERE tenant_id = $1 AND app_id = $2 AND id = $3If TenantID is empty, the engine returns ErrInvalidInput — operations without a tenant scope are rejected.
If a caller passes a collection ID that belongs to a different tenant, the store returns no rows, and the engine surfaces ErrCollectionNotFound. No cross-tenant data is accessible, even if the caller knows an ID from another tenant.
AppID and TenantID
| Field | Purpose |
|---|---|
TenantID | Isolates data between tenants in a multi-tenant SaaS |
AppID | Further namespace within a tenant (e.g. multiple apps per account) |
Both are strings — use whatever format suits your application (UUID, slug, etc.). A typical setup:
// SaaS: per-customer isolation
ctx = weave.WithTenant(ctx, customer.ID)
ctx = weave.WithApp(ctx, "weave-service")
// Mono-tenant: use a constant tenant
ctx = weave.WithTenant(ctx, "default")
ctx = weave.WithApp(ctx, "myapp")Vector store isolation
The vector store layer also enforces tenant isolation. When upserting vectors, Weave stores tenant_id and collection_id as metadata on each vector entry. The VectorStore.Search call filters by both fields:
// Internal — callers never call this directly
vectorStore.Search(ctx, queryVector, &vectorstore.SearchOptions{
Filter: map[string]string{
"tenant_id": tenantID,
"collection_id": collectionID,
},
TopK: topK,
})This ensures that even if two tenants have documents with identical content, their vector searches do not cross-contaminate.
Background jobs
For background jobs (reindexing, cleanup), inject the tenant scope explicitly from a stored tenant ID rather than relying on an HTTP request context:
func reindexTenant(engine *weave.Engine, tenantID, colID string) error {
ctx := context.Background()
ctx = weave.WithTenant(ctx, tenantID)
ctx = weave.WithApp(ctx, "myapp")
return engine.Reindex(ctx, colID)
}