Multi-Tenant Patterns with Access Scopes

Real-world patterns for using Access Scopes to build multi-tenant applications.

Pattern 1: One Scope per Tenant Customer

The most common pattern. When a new customer signs up for your SaaS product, create a scope for them. All actors belonging to that customer are assigned to the scope, and their communication is automatically isolated from other tenants.

This eliminates the need to create separate apps or projects per tenant. A single app serves all tenants, with scopes providing the isolation boundary.

When to use

  • B2B SaaS where each customer organization needs isolated communication
  • White-label products where tenants must not see each other's data
  • Marketplace platforms where vendors operate independently

Implementation

// When a new customer signs up, create a scope for them
async function onboardTenant(tenantSlug: string, tenantName: string) {
  // 1. Create the scope
  const scope = await api.post(
    `/v1/organizations/${orgId}/projects/${projectId}/scopes`,
    { slug: tenantSlug, name: tenantName }
  );

  // 2. Create actors for this tenant's users
  const actor = await api.post(
    `/v1/organizations/${orgId}/projects/${projectId}/actors`,
    {
      name: "user-alice",
      accessScopeId: scope.accessScopeId,
    }
  );

  // 3. Grant the actor access to your apps
  await api.post(
    `/v1/organizations/${orgId}/projects/${projectId}/apps/${appId}/actors`,
    { actorId: actor.actorId }
  );

  // Alice's topics are now namespaced under the tenant slug:
  //   my-app/acme-corp/chat/messages
  //   my-app/acme-corp/notifications/alerts
  return { scope, actor };
}

Pattern 2: Scopes for Environment Isolation

Use scopes to isolate environments within the same project. Staging actors and production actors share the same app configuration but operate in completely separate topic namespaces.

This is useful during development and testing. You can run integration tests against a staging scope without any risk of interfering with production traffic.

When to use

  • Separating staging, preview, and production traffic
  • Running automated tests against a shared project
  • Blue-green deployment patterns

Implementation

// Create scopes for environment isolation
await api.post(`/v1/.../scopes`, { slug: "staging", name: "Staging" });
await api.post(`/v1/.../scopes`, { slug: "production", name: "Production" });

// Assign actors to environments
await api.patch(`/v1/.../actors/${testActorId}`, {
  accessScopeId: stagingScope.accessScopeId,
});

await api.patch(`/v1/.../actors/${prodActorId}`, {
  accessScopeId: productionScope.accessScopeId,
});

// Staging actors publish to: my-app/staging/room/topic
// Production actors publish to: my-app/production/room/topic
// They never interfere with each other.

Pattern 3: Scopes for Team Isolation

Use scopes to partition communication by team or department within a single organization. Each team gets its own isolated set of topics while sharing the same app infrastructure.

When to use

  • Enterprise applications with departmental isolation requirements
  • Compliance scenarios where teams must not share communication channels
  • Internal tools with per-team dashboards and notifications

Implementation

// Create scopes for team isolation
const teams = ["engineering", "sales", "support"];

for (const team of teams) {
  await api.post(`/v1/.../scopes`, {
    slug: team,
    name: team.charAt(0).toUpperCase() + team.slice(1),
  });
}

// Assign actors to their teams
await api.patch(`/v1/.../actors/${engineerActorId}`, {
  accessScopeId: engineeringScope.accessScopeId,
});

// Each team gets isolated communication channels:
//   my-app/engineering/standup/messages
//   my-app/sales/pipeline/updates
//   my-app/support/tickets/notifications

Agent Multi-Tenancy

Access Scopes are particularly powerful when combined with AI Agents. You can deploy the same agent workflow for multiple tenants, with each tenant's agents operating in complete isolation.

The agent coordination topics (_handoff/dispatch, _blackboard/state, etc.) are automatically namespaced under the scope. An orchestrator in tenant A cannot dispatch tasks to workers in tenant B.

Implementation

// Multi-tenant agent deployment
// Each tenant gets their own scoped agents

async function deployAgentForTenant(tenantSlug: string, scopeId: string) {
  // Create an orchestrator actor scoped to this tenant
  const orchestrator = await api.post(`/v1/.../actors`, {
    name: `${tenantSlug}-orchestrator`,
    accessScopeId: scopeId,
  });

  // Create worker actors scoped to this tenant
  const worker = await api.post(`/v1/.../actors`, {
    name: `${tenantSlug}-worker`,
    accessScopeId: scopeId,
  });

  // Grant both actors access to the agents app
  await api.post(`/v1/.../apps/${agentsAppId}/actors`, {
    actorId: orchestrator.actorId,
  });
  await api.post(`/v1/.../apps/${agentsAppId}/actors`, {
    actorId: worker.actorId,
  });

  // These agents can only coordinate within their tenant's scope.
  // Acme's orchestrator dispatches tasks to Acme's workers.
  // Beta Corp's agents are in a completely separate namespace.
  //
  // Acme:      agents-app/acme-corp/_handoff/dispatch
  // Beta Corp: agents-app/beta-corp/_handoff/dispatch
}

// Deploy for each tenant
await deployAgentForTenant("acme-corp", acmeScopeId);
await deployAgentForTenant("beta-corp", betaScopeId);

Key Advantage

With Access Scopes, you deploy one app, one set of rooms, one set of topics. The scopes handle isolation automatically. Without scopes, you would need to create a separate app (or even a separate project) for each tenant, duplicating configuration and complicating management.

Next Steps