evo-ai — The multi-tenant platform
One deployment, many customers, provably separate.
How one evo-ai deployment safely serves many customers: identity, data isolation, per-tenant configuration, automatic onboarding, and the two ways an application integrates with it.
The shape of the problem
A single evo-ai deployment is meant to serve many organizations at once. That demands three things that usually fight each other:
- Hard isolation — no tenant can ever see another's data, even through a bug or a cleverly worded question.
- Zero-touch onboarding — new customers appear without an operator wiring them up by hand.
- Per-tenant flexibility — each customer can bring their own model, data sources, and access rules.
evo-ai addresses all three with a small number of composable mechanisms.
Identity: who is asking, and for whom
Every request is authenticated one of two ways:
- User JWTs — issued by the identity provider (EvoPlatform), signed RS256, verified against its published JWKS. The token carries a
tenant_idclaim. evo-ai reads the tenant only from these verified claims — never from the request — so a caller cannot assert a tenant they weren't granted. - Service keys — for trusted application backends that query on behalf of their own users. A backend presents a service key (a
svc_-prefixed bearer token, stored server-side only as a SHA-256 hash) and asserts the target tenant via anX-Data-Tenantheader. This lets a product like an EHS SaaS add an "Ask AI" panel that queries each site's data under one trusted credential, while still scoping every request to the right tenant.
Identity tenant vs data tenant: tenant links
A subtle but important distinction: the tenant a customer authenticates as (their identity-provider account) is not always the same id as the tenant their data lives under (a downstream system may key data by its own ids).
Tenant links map an identity tenant to one or more data tenants. The design is default-deny: no link, no access. Creating a link is an administrative action, gated to platform administrators. This cleanly separates "who you are" from "what data you're allowed to reach," and makes cross-system integrations explicit and auditable rather than implicit.
Data isolation: multiple independent layers
Isolation is never trusted to a single check. For any tenant's data:
- Physical vector separation — each
(tenant, collection)is its own physical Qdrant collection. - Metadata filtering — plus a
tenant_idfilter on every query, so even within shared infrastructure the query is scoped. - Server-bound analytics scope — the SQL analytics path binds the tenant id server-side inside the curated views; generated SQL cannot widen it.
Two mechanisms would have to fail simultaneously to cross a tenant boundary — and the analytics path adds a third, independent one.
Per-tenant configuration
Each tenant configures the platform to its needs, independently:
- Model choice — any cloud provider (via LiteLLM) or a local model (via Ollama), selected per tenant. Provider API keys are encrypted at rest.
- Embeddings — recorded per collection at creation time and fixed for that collection's life (changing embeddings means a new collection, never a silent reinterpretation of existing vectors).
- Data sources — each tenant's own connectors (files, wikis, tickets, databases, documentation sites), synced on independent schedules.
Automatic onboarding: source auto-discovery
Customers create their own accounts, so the platform must notice them with no human in the loop. When a discovery source is configured, every scheduler tick reconciles the upstream list of tenants against what evo-ai has provisioned:
- New active tenant → a data source is created and begins syncing.
- Suspended / past-due → syncing pauses (respecting billing state).
- Reactivated → syncing resumes.
- Deleted → the tenant's documents are purged from the index and the source removed. Deletion means deletion — an offboarding guarantee, not a best-effort.
The same mechanism can provision multiple sources per tenant — for example, a records source (the tenant's live data) and a documentation source (the product manual), each with its own cadence.
Role-scoped answers within a tenant
Isolation isn't only between tenants — it can extend within one. A trusted application can tell evo-ai which datasets the asking user is allowed to see (by passing the permitted source types with the request). evo-ai then restricts both vector retrieval and the analytics SQL views to those datasets: the planning model never even sees the disallowed views, they're omitted from the generated query, and any stray reference is rejected before execution. A user without access to a given dataset gets the same "not covered" response they'd get if the data didn't exist — enforced server-side, so it can't be bypassed from the client.
The two integration models
An application integrates with the platform one of two ways, depending on trust:
- Direct user tokens — end users (or a thin client) call evo-ai with their own JWTs. Simplest; the token is the authorization.
- Trusted backend + service key — the application's server calls evo-ai with a service key and asserts each user's tenant (and, optionally, that user's permitted datasets). This is how an existing SaaS embeds an AI panel: the product keeps control of authentication and authorization, evo-ai provides the grounded answers, and every request is still tenant-scoped and auditable.
Each mechanism is small and independently verifiable — verified-claims identity, default-deny links, physical + metadata + SQL isolation, encrypted per-tenant config, reconciling auto-discovery, server-side role scoping. Together they let a single deployment onboard customers automatically, keep each one's data provably separate, and still give every tenant its own model, sources, and access rules.