v0.5.2 release - Contributors, Sponsors and Enquiries are most welcome 😌

Multi-Tenancy Support

AgentSea provides enterprise-grade multi-tenancy support, enabling you to serve multiple customers with isolated data, quotas, and configurations.

🆕 Enterprise Feature

Multi-tenancy support provides production-ready tenant isolation, API key authentication, and quota management. Types are available from @lov3kaizen/agentsea-types or re-exported from the core package.

Overview

The multi-tenancy system provides:

  • Tenant Isolation: Complete data separation between tenants
  • API Key Authentication: Secure SHA256-hashed API keys per tenant
  • Quota Management: Track and enforce usage limits across hourly, daily, and monthly periods
  • Settings Management: Per-tenant configuration for agents, conversations, rate limits, and more
  • Status Control: Activate, suspend, or deactivate tenants dynamically
  • Memory Isolation: Tenant-scoped conversation history and memory stores

Tenant Manager

The TenantManager class provides complete lifecycle management for tenants.

Creating a Tenant

typescript
import { TenantManager, MemoryTenantStorage } from '@lov3kaizen/agentsea-core';
import type { Tenant, TenantSettings } from '@lov3kaizen/agentsea-types';

const storage = new MemoryTenantStorage();
const tenantManager = new TenantManager(storage);

// Create a new tenant
const tenant = await tenantManager.createTenant({
  name: 'Acme Corporation',
  slug: 'acme-corp',
  metadata: {
    industry: 'Technology',
    plan: 'enterprise',
  },
  settings: {
    maxAgents: 50,
    maxConversations: 1000,
    rateLimit: { requestsPerMinute: 100 },
    dataRetentionDays: 90,
    allowedProviders: ['anthropic', 'openai'],
  },
});

console.log('Tenant created:', tenant.id);

Generating API Keys

Each tenant can have one or more API keys with scoped permissions and expiration dates.

typescript
// Generate a new API key for the tenant
const apiKey = await tenantManager.generateApiKey(tenant.id, {
  scopes: ['agents:read', 'agents:write', 'conversations:read'],
  expiresAt: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000), // 1 year
});

console.log('API Key:', apiKey.key); // Save this - it won't be shown again!
console.log('Key ID:', apiKey.id);

// Verify an API key
const verified = await tenantManager.verifyApiKey(apiKey.key);
if (verified) {
  console.log('Valid API key for tenant:', verified.tenantId);
}

⚠️ Security Note

API keys are hashed using SHA256 before storage. The plaintext key is only available during generation. Store it securely!

Tenant Configuration

Tenant Settings

Each tenant has customizable settings that control resource limits and behavior:

SettingTypeDescription
maxAgentsnumberMaximum number of agents allowed
maxConversationsnumberMaximum concurrent conversations
rateLimitobjectRequests per minute limit
dataRetentionDaysnumberHow long to retain conversation data
allowedProvidersstring[]Permitted LLM providers

Tenant Status

Tenants can have one of three statuses:

  • ACTIVE: Fully operational, can use all features
  • SUSPENDED: Temporarily disabled, typically for non-payment or policy violations
  • INACTIVE: Permanently disabled, retains data but no access
typescript
// Suspend a tenant
await tenantManager.updateTenant(tenant.id, {
  status: 'SUSPENDED',
});

// Reactivate a tenant
await tenantManager.updateTenant(tenant.id, {
  status: 'ACTIVE',
});

Quota Management

Track and enforce usage quotas across different time periods.

Recording Usage

typescript
// Record API usage
await tenantManager.recordQuotaUsage(tenant.id, {
  resource: 'api_calls',
  amount: 1,
  period: 'hourly',
});

// Record token usage
await tenantManager.recordQuotaUsage(tenant.id, {
  resource: 'tokens',
  amount: 5000,
  period: 'daily',
});

// Check quota status
const quotas = await storage.getQuotas(tenant.id);
for (const quota of quotas) {
  console.log(`${quota.resource}: ${quota.used}/${quota.limit} (${quota.period})`);
}

Quota Periods

Quotas can be tracked across three time periods:

  • hourly: Resets every hour
  • daily: Resets every day
  • monthly: Resets every month

Tenant-Aware Memory

Use TenantBufferMemory to ensure conversation data is isolated per tenant.

typescript
import { TenantBufferMemory } from '@lov3kaizen/agentsea-core';

const memory = new TenantBufferMemory({ maxMessages: 50 });

// Save conversation for a specific tenant
await memory.save('tenant-123', 'conv-456', [
  { role: 'user', content: 'Hello!' },
  { role: 'assistant', content: 'Hi! How can I help?' },
]);

// Load only this tenant's conversations
const history = await memory.load('tenant-123', 'conv-456');

// Clear tenant data
await memory.clear('tenant-123', 'conv-456');

Storage Backends

AgentSea provides multiple storage implementations for tenant data:

Memory Storage (Development)

In-memory storage, ideal for development and testing.

typescript
import { MemoryTenantStorage, TenantManager } from '@lov3kaizen/agentsea-core';

const storage = new MemoryTenantStorage();
const tenantManager = new TenantManager(storage);

Database Storage (Production)

For production deployments, implement the TenantStorage interface with your preferred database.

typescript
import { TenantStorage, Tenant } from '@lov3kaizen/agentsea-core';

class PostgresTenantStorage implements TenantStorage {
  async create(tenant: Omit<Tenant, 'id' | 'createdAt' | 'updatedAt'>): Promise<Tenant> {
    // Implement with your database
  }

  async get(id: string): Promise<Tenant | null> {
    // Implement with your database
  }

  async update(id: string, updates: Partial<Tenant>): Promise<Tenant> {
    // Implement with your database
  }

  async delete(id: string): Promise<void> {
    // Implement with your database
  }

  async getBySlug(slug: string): Promise<Tenant | null> {
    // Implement with your database
  }

  async list(): Promise<Tenant[]> {
    // Implement with your database
  }

  // ... implement remaining methods
}

NestJS Integration

AgentSea provides decorators and guards for seamless multi-tenancy in NestJS applications.

Tenant Guard

typescript
import { Controller, Get, UseGuards } from '@nestjs/common';
import { TenantGuard, Tenant } from '@lov3kaizen/agentsea-nestjs';

@Controller('agents')
@UseGuards(TenantGuard)
export class AgentsController {
  @Get()
  async listAgents(@Tenant() tenant: TenantContext) {
    // tenant.id, tenant.name, tenant.settings are available
    return this.agentService.getAgentsByTenant(tenant.id);
  }
}

Tenant Decorator

Extract tenant context from requests using the @Tenant() decorator.

typescript
import { Tenant } from '@lov3kaizen/agentsea-nestjs';

@Post('execute')
async executeAgent(
  @Tenant() tenant: TenantContext,
  @Body() dto: ExecuteAgentDto,
) {
  // Use tenant.id for data isolation
  return this.agentService.execute(tenant.id, dto);
}

Complete Example

typescript
import {
  TenantManager,
  MemoryTenantStorage,
  TenantBufferMemory,
  Agent,
  AnthropicProvider,
  ToolRegistry,
} from '@lov3kaizen/agentsea-core';

// Initialize multi-tenancy system
const storage = new MemoryTenantStorage();
const tenantManager = new TenantManager(storage);

// Create tenant
const tenant = await tenantManager.createTenant({
  name: 'Acme Corp',
  slug: 'acme-corp',
  settings: {
    maxAgents: 10,
    maxConversations: 100,
    allowedProviders: ['anthropic'],
  },
});

// Generate API key
const apiKey = await tenantManager.generateApiKey(tenant.id);
console.log('Save this API key:', apiKey.key);

// Setup tenant-isolated memory
const memory = new TenantBufferMemory();

// Create agent for tenant
const agent = new Agent(
  {
    name: 'support-agent',
    model: 'claude-sonnet-4-20250514',
    provider: 'anthropic',
    systemPrompt: 'You are a helpful customer support assistant.',
  },
  new AnthropicProvider(process.env.ANTHROPIC_API_KEY),
  new ToolRegistry(),
  { type: 'custom', store: memory },
);

// Execute with tenant context
const conversationId = 'conv-1';
const response = await agent.execute('How do I reset my password?', {
  conversationId,
  sessionData: { tenantId: tenant.id },
  history: await memory.load(tenant.id, conversationId),
});

// Save tenant conversation
await memory.save(tenant.id, conversationId, [
  { role: 'user', content: 'How do I reset my password?' },
  { role: 'assistant', content: response.content },
]);

// Record usage
await tenantManager.recordQuotaUsage(tenant.id, {
  resource: 'api_calls',
  amount: 1,
  period: 'hourly',
});

await tenantManager.recordQuotaUsage(tenant.id, {
  resource: 'tokens',
  amount: response.metadata.tokensUsed || 0,
  period: 'daily',
});

console.log('Response:', response.content);

Best Practices

  • API Key Security: Always use HTTPS and never log plaintext API keys
  • Quota Monitoring: Set up alerts when tenants approach quota limits
  • Data Retention: Respect tenant data retention policies and automate cleanup
  • Resource Limits: Set conservative defaults and allow upgrades
  • Audit Logging: Track all tenant operations for compliance
  • Graceful Degradation: Handle quota exceeded scenarios with clear error messages
  • Testing: Test with multiple tenants to ensure complete isolation

Next Steps