Skip to content

Multi-Tenancy

Hierarchy

Organization
├── Team (with roles: admin, member, viewer)
│   └── Workspace
│       └── Project
│           ├── Tasks
│           └── Workflow Instances
├── Skills (org-scoped)
├── Configs (org-scoped)
└── Secrets (org/team/workspace-scoped)

Default bootstrap creates a single Organization + Team. Operators add complexity only when they need it.

How Scoping Works

TenantMiddleware resolves request.organization and request.project from:

  1. X-Organization / X-Project headers (API clients)
  2. Session (active_organization_id / active_project_id)
  3. First membership (fallback)

Views filter queries by request.organization to prevent cross-tenant data leaks.

Missing filters leak data

Every query that should be tenant-scoped must include .filter(organization=request.organization). A missed filter is a cross-tenant data leak. Audit all routes for this pattern.

Scoping Rules

Model Scoped to Notes
Task Project project_id FK
WorkflowInstance Project project_id FK
Skill Organization Shared across all teams in the org
Config Organization Same as skills
Secret Org / Team / Workspace Narrowest scope applies
DispatcherInstance Global Not tenant-scoped
InfraService Global Registered once, available to all
AgentDefinition Global Shared image catalogue

Auth Stack

  • django-guardian: object-level RBAC
  • django-oauth-toolkit: OAuth2 for external integrations
  • Scoped API keys: per-key scopes, expiry, dispatcher binding
  • HMAC callbacks: signed with CALLBACK_SIGNING_KEY
  • Hashed tokens: callback tokens stored as SHA-256, never plaintext

API Authentication

Client Auth method
External orchestrators OAuth2 client credentials
Operator tools Token auth (DRF)
Dispatcher Long-lived API key (X-API-Key header)
Agents Per-task key (X-Agent-Key header)

Isolation Layers

Multi-tenancy is handled at multiple layers:

Runtime Isolation

Agents are namespaced at the runtime level:

  • K8s -- Separate namespaces per environment. Agents run in dedicated agents namespace.
  • ECS -- Separate clusters or security groups. Dedicated task role with no AWS API permissions by default.

Controller Isolation

Django RBAC provides org/team isolation at the workflow and registry level. Skills, configs, briefs, and workflows are scoped to their owner.

Dispatcher Isolation

The Dispatcher is tenancy-agnostic -- it executes tasks without enforcing tenancy. Isolation is the runtime's and Controller's responsibility.

Secrets Scoping

Secrets scope hierarchically with the narrowest scope winning:

Scope Available to
Organization All teams, workspaces, projects
Team All workspaces in the team
Workspace All projects in the workspace

A workspace-level secret with the same name as an org-level secret overrides it at brief assembly time.