Skip to main content

Multi-tenant architecture

A tenant is the unit that owns things. Users belong to tenants; bots, keys, channels, conversations and apps belong to tenants; and every request is scoped to the tenant the caller is authenticated for.

What a tenant owns

tenant
├── bots
│ ├── channel integrations (Telegram, email)
│ ├── model configuration
│ └── context and archival settings
├── provider API keys
├── authorized channels, grouped
├── conversation history
├── apps
└── quota and usage

Kinds of tenant

Type
personalCreated automatically for an individual user
teamA shared workspace
enterpriseA shared workspace, distinguished for entitlement purposes
systemReserved. Holds platform-level defaults; not a customer workspace.

The two layers

Isolation is defence in depth, and the two layers are in different states. Both are worth understanding before you rely on either.

Layer 1 — tenant-scoped reads. Active.

Tenant scope always comes from the authenticated principal, never from client input. A request cannot ask for another tenant's data by naming it; the tenant is taken from who you are, and reads that touch tenant-owned tables are required to carry it.

This is the layer that is doing the work today, in every deployment.

Layer 2 — row-level security. Dormant unless switched on.

Database row-level security is the backstop: policies on the tenant-owned tables so that a query which somehow omitted its tenant filter returns nothing rather than everything.

It is off by default, and it needs two conditions rather than one:

  1. The feature must be switched on for the deployment, and
  2. the database role the application connects as must not be permitted to bypass row-level security.

A role with bypass permission renders the policies inert no matter what else is configured — which is the failure this design guards against most carefully. The application refuses to start if it is told to enforce row-level security while connected as a role that can bypass it, so the dangerous middle state (believed on, silently bypassed) cannot run.

An operator can confirm which state a deployment is in:

SELECT rolbypassrls FROM pg_roles WHERE rolname = current_user;
-- f = row-level security applies to this role
-- t = it does not, and the policies are inert

If you need the backstop active, treat it as a deployment task with a verification step, not a checkbox.

Configuration is per tenant

Each tenant independently sets its model provider and models, its agent definitions, its context window and archival policy, and its own keys. Nothing falls back to another tenant; where a value is unset it falls back to the platform default.

Encryption

Each tenant's archived content is encrypted under its own key, which buys three things:

  • Crypto-shredding — destroying the key makes that tenant's archives unreadable, without needing to locate every copy across storage and backups. This is the erasure mechanism for tenant offboarding; see Data export & erasure.
  • Independent rotation — one tenant's schedule is not another's.
  • Per-customer commitments — a contractual requirement for separate key material can be met without a separate deployment.

Quota and usage

Usage is recorded per tenant — tokens and cost — against a monthly allowance with a configurable reset day and a warning threshold.

Enforcement happens before the model is called, and it fails closed: a tenant over its allowance is refused rather than served and billed. There are three allowance types: trial, paid and unlimited.

Creating and managing tenants

In the admin console, under Tenants. See the Admin console guide.