Multi-tenancy
An agency runs many clients on one deployment. OpenRTC makes that safe: each tenant gets its own provider keys, its own resource budget, and a blast radius that stops one client’s bad code from hurting the others. This is the agency rung. A tenant is named by the dispatch metadata keytenant (matching what
voicegateway reads). A session with no tenant runs under "default", so
single-tenant deployments work unchanged.
The tenant flows through the whole session via a contextvar. Agent code can read
it with
from openrtc.context import current_tenant_id (or session.tenant_id).
It is validated (1-128 chars of letters, digits, dashes, underscores); a malformed
tenant rejects the session rather than silently mislabeling it.The four guarantees
| Guarantee | Mechanism | Ticket |
|---|---|---|
| Config isolation | Per-tenant STT/LLM/TTS providers and keys, resolved at session start. One tenant’s key is never used for another’s session. | tenant_config |
| Resource fairness | Per-tenant session caps: a tenant at its cap is rejected while siblings keep accepting. | max_sessions_per_tenant |
| Blast-radius isolation | A per-tenant circuit breaker opens when a tenant’s failure rate trips, rejecting its new sessions for a cooldown, then auto-recovers. | enable_tenant_circuit_breaker |
| Tagging | Tenant on every worker-internal signal (openrtc top --tenant, scoped logs, runtime_snapshot().sessions_by_tenant) and on the observer payload. | always on |
tenant_config can also be a callable (tenant -> config, e.g. a DB load); its
result is cached per tenant, so a tenant’s later sessions reuse the same client
objects.
What is shared, and what is not
OpenRTC’s density comes from one process hosting every tenant’s sessions. That sets the isolation boundary honestly: Shared (by design, this is the density win):- The worker process and its Python heap.
- The prewarmed VAD and turn detector (loaded once).
- The event loop. A tenant that blocks the loop synchronously affects scheduling for everyone until the slow-session detector flags it. That is why per-tenant code should stay async (see the density runbook).
- Provider clients and API keys (
tenant_config). - Session state and conversation history.
- Resource budget (
max_sessions_per_tenant) and the circuit breaker. - Every attribution tag (metrics, logs,
openrtc top).
The voicegateway boundary
Per-tenant cost and quality attribution lives in voicegateway, not here. OpenRTC carries the tenant onSessionInfo.metadata["tenant"], and voicegateway’s
VoiceGatewayObserver reads it to attribute cost per client with no extra work.
OpenRTC owns the runtime side (config, caps, blast radius, tenant-tagged
introspection); it does not build a parallel cost/telemetry layer.
Next steps
- Onboarding a tenant: the add-a-client flow.
- Tenant incident: identify, isolate, and escalate a misbehaving tenant.
