Multi-Tenancy

Isolate data across organizations, teams, and environments with two layers of separation.


How It Works

Nocturnus provides two levels of data isolation via HTTP headers:

Header Purpose Default
X-Database Selects the logical database — completely separate Hexastore instances with independent rules and facts "default"
X-Tenant-ID Selects the tenant within a database — data-level isolation sharing the same rules engine required*
*X-Tenant-ID is required on most REST endpoints (the server throws a ValidationException if missing). MCP routes are the exception — they default to "default" if not specified.
When to use which? Use databases to separate completely independent knowledge domains (e.g., "production" vs "staging"). Use tenants to isolate customer data within the same domain (e.g., "acme_corp" vs "globex_inc" within "production"). Use scopes for logical partitions within a tenant (hypothetical reasoning, versioning, A/B testing) — scopes are NOT isolation boundaries.

Creating a Database

Create a new logical database:

curl -X POST http://localhost:9300/databases \
  -H "Content-Type: application/json" \
  -d '{"name": "production"}'

List all databases:

curl http://localhost:9300/databases

Using Tenants

Pass the tenant and database headers with every request:

# Assert a fact for Acme Corp in the production database
curl -X POST http://localhost:9300/tell \
  -H "Content-Type: application/json" \
  -H "X-Database: production" \
  -H "X-Tenant-ID: acme_corp" \
  -d '{"predicate": "plan", "args": ["enterprise"]}'

# Query facts for a different tenant — sees only its own data
curl -X POST http://localhost:9300/ask \
  -H "Content-Type: application/json" \
  -H "X-Database: production" \
  -H "X-Tenant-ID: globex_inc" \
  -d '{"predicate": "plan", "args": ["?tier"]}'

# Returns empty — Globex has no facts yet
{ "results": [] }

Context Optimization Isolation Pattern

For context-heavy agents, separate concerns like this:

curl -X POST http://localhost:9300/context/optimize \
  -H "Content-Type: application/json" \
  -H "X-Database: production" \
  -H "X-Tenant-ID: acme_corp" \
  -d '{
    "scope": "conversation_2026_04_03",
    "sessionId": "acme-support-thread-42",
    "maxFacts": 25
  }'
# Same tenant+scope, later turn: request only changes
curl -X POST http://localhost:9300/context/diff \
  -H "Content-Type: application/json" \
  -H "X-Database: production" \
  -H "X-Tenant-ID: acme_corp" \
  -d '{
    "scope": "conversation_2026_04_03",
    "sessionId": "acme-support-thread-42",
    "maxFacts": 25
  }'

# End-of-thread cleanup for this tenant/session
curl -X POST http://localhost:9300/context/session/clear \
  -H "Content-Type: application/json" \
  -H "X-Database: production" \
  -H "X-Tenant-ID: acme_corp" \
  -d '{"sessionId":"acme-support-thread-42"}'

Common Patterns

SaaS Multi-Tenant

One database, one tenant per customer:

# Each customer gets isolated data
X-Database: production
X-Tenant-ID: customer_123   # Customer A
X-Tenant-ID: customer_456   # Customer B

Environment Separation

Separate databases per environment:

# Completely independent knowledge bases
X-Database: development
X-Database: staging
X-Database: production

Per-Agent Isolation

Each agent gets its own tenant so knowledge doesn't leak:

# Agent-specific knowledge stores
X-Database: agents
X-Tenant-ID: agent_support_bot
X-Tenant-ID: agent_sales_assistant

Multi-Tenancy with MCP

When using MCP, the database and tenant can be configured via headers in the MCP client config:

{
  "mcpServers": {
    "nocturnus": {
      "url": "http://localhost:9300/mcp/sse",
      "transport": "sse",
      "headers": {
        "X-Database": "production",
        "X-Tenant-ID": "acme_corp"
      }
    }
  }
}

Tenant Management API

Manage tenants programmatically via the admin API:

# List tenants in a database
curl http://localhost:9300/admin/databases/production/tenants \
  -H "Authorization: Bearer $ADMIN_KEY"

# Create a tenant
curl -X POST http://localhost:9300/admin/databases/production/tenants \
  -H "Authorization: Bearer $ADMIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "acme_corp"}'

# Delete a tenant and all its data
curl -X DELETE http://localhost:9300/admin/databases/production/tenants/acme_corp \
  -H "Authorization: Bearer $ADMIN_KEY"

Scope Management

Scopes are logical partitions within a tenant — they are NOT isolation boundaries (use tenants for that). Scopes enable Git-like knowledge branching for hypothetical reasoning, versioning, and A/B testing.

# Fork a scope
curl -X POST http://localhost:9300/scope/fork \
  -H "Content-Type: application/json" \
  -H "X-Database: production" \
  -H "X-Tenant-ID: acme_corp" \
  -d '{"source": "main", "target": "hypothesis-1"}'

# Diff two scopes
curl -X POST http://localhost:9300/scope/diff \
  -H "Content-Type: application/json" \
  -H "X-Database: production" \
  -H "X-Tenant-ID: acme_corp" \
  -d '{"source": "hypothesis-1", "target": "main"}'

# Merge scopes (strategies: SOURCE_WINS, TARGET_WINS, KEEP_BOTH, REJECT)
curl -X POST http://localhost:9300/scope/merge \
  -H "Content-Type: application/json" \
  -H "X-Database: production" \
  -H "X-Tenant-ID: acme_corp" \
  -d '{"source": "hypothesis-1", "target": "main", "strategy": "SOURCE_WINS"}'

# List all scopes
curl http://localhost:9300/scopes \
  -H "X-Database: production" \
  -H "X-Tenant-ID: acme_corp"
Scope query behavior. Querying with scope=null matches facts from ALL scopes. Set an explicit scope to restrict results to that partition only.

Scoped API Keys

Combine multi-tenancy with Security & Auth to create API keys restricted to specific databases and tenants:

# Create a key that can only access Acme Corp's data
curl -X POST http://localhost:9300/auth/keys \
  -H "Authorization: Bearer $ADMIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "role": "writer",
    "databases": ["production"],
    "tenants": ["acme_corp"],
    "description": "Acme Corp writer key"
  }'

What's Next?

Security & Auth →

API keys, roles, and encryption

Operations →

Monitoring, persistence, and deployment

API Reference →

Headers and endpoint documentation