Govern

Multi-tenant keys

Flat tenant_id scoping. Client keys see one tenant. Admin sees every tenant. There is no tenant CRUD UI — a tenant exists when resources are tagged with it.

What it is

On api_key entries set tenant_id. On JWT set tenant_claim (default tenant_id). Agents, threads, runs, store, cron are filtered by tenant. Cross-tenant ID access returns 404.

Why it is here

Enterprises need isolation without waiting on a full org/workspace hierarchy. Flat claim-driven tenancy ships today; hierarchy can layer later.

How to implement

  1. Pick stable tenant ids (acme, default). Avoid : inside ids (checkpoint encoding).
  2. Map each client key to a tenant; issue JWTs with the claim for SSO.
  3. Bootstrap graphs from config land in default — create per-tenant agents via API under that tenant’s credential.
  4. When client auth + runner tokens are on, set RUNNER_TENANTS_<kind> allow-lists.
  5. Operators use Admin (admin permission) for cross-tenant visibility — not a tenant-scoped key.
  6. Optional hardening: set RUNKITE_POSTGRES_RLS=true so Postgres FORCE RLS also filters by app.tenant_id even if an application query forgets WHERE tenant_id. Tenant requests SET ROLE runkite_app (no BYPASSRLS; DSN needs CREATEROLE and Postgres 16+ GRANT … SET TRUE) so this still works when the DSN is a superuser. After Init, missing role membership fails closed. Admin / system paths use app.is_system and keep the login role. Turning the flag off and restarting drops Runkite's policies so FORCE does not stick. Postgres only; off by default.
"keys": {
  "sk-acme": {"name": "acme-app", "permissions": ["read", "write"], "tenant_id": "acme"},
  "sk-ops": {"name": "ops", "permissions": ["read", "write", "admin"], "tenant_id": "default"}
}

In the product

Admin lists show tenant_id per row across tenants
Runkite Admin Threads with tenants

What to expect