Built for multiple tenants
Isolation that holds at the storage layer, not just the query filter.
JWT auth, organizations, users, and memberships, with four roles: owner, admin, developer, viewer. Tenant identity reaches into the vector store itself.
Point id derivation — before
Collides
uuid5(source, parent_idx, child_idx)
# Tenant A: "handbook.pdf" → 3f0c…9a1
# Tenant B: "handbook.pdf" → 3f0c…9a1
# ^ same id, one wins
Point id derivation — after
Isolated
uuid5(tenant_id, source, parent_idx, child_idx)
# Tenant A: "handbook.pdf" → 7b41…e02
# Tenant B: "handbook.pdf" → c8d9…14f
# ^ disjoint by construction
How I found it
Two tenants uploaded a file with the same name. Chunk ids came from the filename and chunk position, so they collided and one tenant's chunks overwrote the other's. Nothing errored. Qdrant saw an upsert, which is what it was.
One tenant's answers started citing text nobody on that team had uploaded. I folded the tenant into the uuid5 seed and wrote a migration to rebuild affected collections. Isolation now comes from how ids are built, not from remembering a filter.
Audit trailBootstrap, login, user creation, document upload and delete, every RAG query, model configuration change, workflow, and eval record.
Model gatewayOrg-scoped routing across OpenAI, Claude, Gemini, DeepSeek, and local providers. No vendor lock-in.
Agents and workflowsDefinitions and runs, with Server-Sent Event streams driving live timelines.