Multi-Tenancy โ
Use createTenantContext() to isolate sessions, rate limits, and run context per tenant โ without separate databases. All stores are wrapped and all keys are automatically prefixed.
ts
import { createTenantContext, TenantRegistry } from 'confused-ai/production';Quick start โ
ts
import { createAgent } from 'confused-ai';
import { createTenantContext } from 'confused-ai/production';
import { createSqliteSessionStore } from 'confused-ai/session';
// Single shared session store
const sessionStore = await createSqliteSessionStore('./agent.db');
// In your request handler, scope to the authenticated tenant:
async function handleRequest(req: Request) {
const tenantId = req.headers.get('x-tenant-id')!;
const ctx = createTenantContext(tenantId, { sessionStore });
const agent = createAgent({
name: 'support',
instructions: 'Help users with support requests.',
model: 'gpt-4o-mini',
apiKey: process.env.OPENAI_API_KEY!,
sessionStore: ctx.sessionStore, // all keys are prefixed with 'tenantId:'
});
return agent.run(req.body.message, ctx.runContext);
}createTenantContext โ
ts
const ctx = createTenantContext('tenant-acme', {
sessionStore: baseSessionStore, // wrapped with 'tenant-acme:' prefix
rateLimitConfig: { maxRequests: 100, intervalMs: 60_000 }, // per-tenant limiter
});
// ctx fields:
// ctx.tenantId โ 'tenant-acme'
// ctx.sessionStore โ TenantScopedSessionStore (auto-prefixes all keys)
// ctx.rateLimiter โ RateLimiter scoped to this tenant
// ctx.runContext โ { tenantId: 'tenant-acme' } (pass to agent.run())Key isolation in practice โ
ts
// Tenant A and Tenant B share the same Postgres session store,
// but their sessions never overlap:
const ctxA = createTenantContext('tenant-a', { sessionStore });
const ctxB = createTenantContext('tenant-b', { sessionStore });
// Session IDs stored as 'tenant-a:sess-123' vs 'tenant-b:sess-123'
const sessionId = await ctxA.sessionStore.create({ agentId: 'support' });
// โ stored as 'tenant-a:<generated-id>'TenantRegistry โ per-tenant configuration โ
Use TenantRegistry to define configuration for each tenant (rate limits, allowed models):
ts
import { TenantRegistry } from 'confused-ai/production';
const registry = new TenantRegistry();
registry.register({
tenantId: 'tenant-acme',
maxRpm: 100, // max 100 requests per minute
maxUsdPerDay: 5.00, // max $5/day spend
allowedModels: ['gpt-4o-mini', 'gpt-4o'],
});
registry.register({
tenantId: 'tenant-enterprise',
maxRpm: 1000,
maxUsdPerDay: 50.00,
allowedModels: ['gpt-4o', 'claude-3-5-sonnet'],
});
// Lookup in request handler
const config = registry.get(tenantId);
if (config?.allowedModels && !config.allowedModels.includes(requestedModel)) {
return Response.json({ error: 'Model not available on your plan.' }, { status: 403 });
}Namespace each layer explicitly โ
For full tenant isolation, scope every stateful layer:
ts
const ctx = createTenantContext(tenantId, { sessionStore: baseSessionStore });
const agent = createAgent({
name: 'support',
instructions: '...',
model: 'gpt-4o-mini',
apiKey: process.env.OPENAI_API_KEY!,
// Session: auto-namespaced by createTenantContext
sessionStore: ctx.sessionStore,
// Memory: namespace manually
memoryStore: createDbMemoryStore({ db, namespace: tenantId }),
// Storage: prefix keys manually
storage: createStorage({ driver: 'file', basePath: `./data/${tenantId}` }),
});TenantContext interface โ
ts
interface TenantContext {
readonly tenantId: string;
readonly sessionStore: SessionStore; // TenantScopedSessionStore
readonly rateLimiter: RateLimiter;
readonly runContext: { tenantId: string; userId?: string };
}Where to go next โ
- Session โ underlying session stores.
- Production โ
BudgetEnforcerandRateLimiter. - Secret manager โ per-tenant credential isolation.