> ## Documentation Index
> Fetch the complete documentation index at: https://docs.usesatori.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Memory Isolation

> Understand how Satori keeps your users' data completely separate and secure

## Overview

Satori implements a robust multi-tenant architecture that ensures complete data isolation between tenants and their end users. This page explains how isolation works and why it matters for your application.

## Two-Level Isolation Model

Satori uses a hierarchical isolation model with two levels:

```mermaid theme={null}
graph TB
    subgraph Tenant1[Tenant 1: Developer A]
        APIKey1[API Key: sk_satori_abc...]
        User1A[User: alice]
        User1B[User: bob]
        Mem1A[Alice's Memories]
        Mem1B[Bob's Memories]
        
        APIKey1 --> User1A
        APIKey1 --> User1B
        User1A --> Mem1A
        User1B --> Mem1B
    end
    
    subgraph Tenant2[Tenant 2: Developer B]
        APIKey2[API Key: sk_satori_xyz...]
        User2A[User: charlie]
        User2B[User: diana]
        Mem2A[Charlie's Memories]
        Mem2B[Diana's Memories]
        
        APIKey2 --> User2A
        APIKey2 --> User2B
        User2A --> Mem2A
        User2B --> Mem2B
    end
```

### Level 1: Tenant Isolation (clerkUserId)

Each API key is tied to a specific Clerk user ID (the tenant). All memories created with that key belong to that tenant's account.

```typescript theme={null}
// Your API key → Your Clerk user ID
const apiKey = 'sk_satori_abc...';
// This key is bound to clerkUserId: "user_372Icb..."
```

<Info>
  Tenants are completely isolated from each other. Developer A can never access Developer B's data, even if they know the user IDs.
</Info>

### Level 2: User Isolation (userId)

Within your tenant account, each `userId` you provide gets isolated memory storage:

```typescript theme={null}
// Alice's memories
const aliceMemories = await client.searchMemories('preferences');
// Only searches memories where userId = 'alice'

// Bob's memories (completely separate)
const bobMemories = await client.searchMemories('preferences');
// Only searches memories where userId = 'bob'
```

## Database Schema

Here's how isolation is enforced at the database level:

```sql theme={null}
CREATE TABLE memories (
  id UUID PRIMARY KEY,
  clerk_user_id TEXT NOT NULL,  -- Tenant identifier
  user_id TEXT NOT NULL,         -- End-user identifier
  content TEXT NOT NULL,
  embedding VECTOR(1536),
  metadata JSONB,
  created_at TIMESTAMP,
  updated_at TIMESTAMP
);

-- Composite index for fast tenant + user queries
CREATE INDEX memories_tenant_user_idx 
ON memories (clerk_user_id, user_id);
```

Every query automatically includes both identifiers:

```typescript theme={null}
// When you search memories
const memories = await db
  .select()
  .from(memories)
  .where(
    and(
      eq(memories.clerkUserId, 'user_372Icb...'), // Your tenant
      eq(memories.userId, 'alice')                 // Specific user
    )
  );
```

<Check>
  It's impossible to query memories without both identifiers, ensuring complete isolation.
</Check>

## Isolation Guarantees

<CardGroup cols={2}>
  <Card title="Tenant Isolation" icon="building">
    **Guarantee:** Tenants can never access each other's data

    **Enforced by:**

    * API key verification
    * Database-level filtering
    * Automatic query scoping
  </Card>

  <Card title="User Isolation" icon="user">
    **Guarantee:** Users within a tenant can never access each other's memories

    **Enforced by:**

    * Required userId parameter
    * Composite database indexes
    * Query-level filtering
  </Card>

  <Card title="Cross-Tenant Protection" icon="shield-halved">
    **Guarantee:** Even with a valid userId, cross-tenant access is impossible

    **Enforced by:**

    * API key binding to clerkUserId
    * Middleware authentication
    * Database constraints
  </Card>

  <Card title="Data Encryption" icon="lock">
    **Guarantee:** Data is encrypted at rest and in transit

    **Enforced by:**

    * PostgreSQL encryption
    * TLS/HTTPS connections
    * Secure key storage
  </Card>
</CardGroup>

## Practical Examples

### Example 1: Multi-User Application

You're building a chat application with 1000 users:

```typescript theme={null}
// User Alice logs in
const aliceSession = await auth.getSession();
const aliceUserId = aliceSession.userId; // "alice"

// Create memory tools for Alice
const aliceTools = memoryTools({
  apiKey: process.env.SATORI_API_KEY!, // Your tenant key
  baseUrl: process.env.SATORI_URL!,
  userId: aliceUserId, // Alice's ID
});

// User Bob logs in (different session)
const bobSession = await auth.getSession();
const bobUserId = bobSession.userId; // "bob"

// Create memory tools for Bob
const bobTools = memoryTools({
  apiKey: process.env.SATORI_API_KEY!, // Same tenant key
  baseUrl: process.env.SATORI_URL!,
  userId: bobUserId, // Bob's ID
});
```

**Result:** Alice and Bob's memories are completely separate, even though they use the same API key (your tenant).

### Example 2: Multi-Tenant SaaS

You're building a SaaS where each company gets their own account:

```typescript theme={null}
// Company A's API key
const companyAKey = 'sk_satori_companyA...';

// Company A's users
const aliceTools = memoryTools({
  apiKey: companyAKey,
  baseUrl: process.env.SATORI_URL!,
  userId: 'alice',
});

// Company B's API key (different tenant)
const companyBKey = 'sk_satori_companyB...';

// Company B's users
const charlieTools = memoryTools({
  apiKey: companyBKey,
  baseUrl: process.env.SATORI_URL!,
  userId: 'charlie',
});
```

**Result:** Company A and Company B's data is completely isolated at the tenant level, and their users are isolated within each tenant.

<Warning>
  In a multi-tenant SaaS, each company should have their own API key. Don't share API keys across companies.
</Warning>

## User ID Best Practices

<AccordionGroup>
  <Accordion title="Use stable, unique identifiers">
    Use IDs from your authentication system (Auth0, Clerk, Firebase, etc.):

    ```typescript theme={null}
    // ✅ Good: Stable user ID
    userId: user.id // "user_2abc123..."

    // ❌ Bad: Email (can change)
    userId: user.email

    // ❌ Bad: Username (can change)
    userId: user.username
    ```
  </Accordion>

  <Accordion title="Never share user IDs across different users">
    Each user must have a unique identifier:

    ```typescript theme={null}
    // ❌ NEVER DO THIS
    const tools = memoryTools({
      apiKey: process.env.SATORI_API_KEY!,
      baseUrl: process.env.SATORI_URL!,
      userId: 'shared-user', // All users share memories!
    });
    ```
  </Accordion>

  <Accordion title="Consider namespacing for complex scenarios">
    If you have multiple contexts per user, use namespaced IDs:

    ```typescript theme={null}
    // Different workspaces for the same user
    userId: `${user.id}:workspace:${workspaceId}`
    // Example: "user_123:workspace:acme-corp"

    // Different conversation threads
    userId: `${user.id}:thread:${threadId}`
    // Example: "user_123:thread:support-2024-01"
    ```
  </Accordion>

  <Accordion title="Document your user ID strategy">
    Keep a record of how you generate user IDs for consistency:

    ```typescript theme={null}
    /**
     * User ID format: {authProvider}_{userId}
     * Examples:
     * - clerk_user_2abc123
     * - auth0_auth0|123456
     * - firebase_uid123abc
     */
    function getSatoriUserId(user: User): string {
      return `${user.provider}_${user.id}`;
    }
    ```
  </Accordion>
</AccordionGroup>

## Security Considerations

### API Key Security

<Tabs>
  <Tab title="Server-Side Only">
    Always use API keys on the server:

    ```typescript theme={null}
    // ✅ Server-side API route
    export async function POST(req: Request) {
      const tools = memoryTools({
        apiKey: process.env.SATORI_API_KEY!,
        // ... safe on server
      });
    }
    ```
  </Tab>

  <Tab title="Never Client-Side">
    Never expose API keys to the client:

    ```typescript theme={null}
    // ❌ Client component
    'use client';

    function ChatPage() {
      const tools = memoryTools({
        apiKey: 'sk_satori_...', // EXPOSED!
      });
    }
    ```
  </Tab>

  <Tab title="Environment Variables">
    Store keys in environment variables:

    ```bash .env.local theme={null}
    SATORI_API_KEY=sk_satori_...
    ```

    ```typescript theme={null}
    // Access in server code
    process.env.SATORI_API_KEY
    ```
  </Tab>
</Tabs>

### User ID Validation

Always validate user IDs before using them:

```typescript theme={null}
export async function POST(req: Request) {
  // Get authenticated user from your auth system
  const session = await getSession(req);
  
  if (!session?.userId) {
    return new Response('Unauthorized', { status: 401 });
  }
  
  // Use the authenticated user's ID
  const tools = memoryTools({
    apiKey: process.env.SATORI_API_KEY!,
    baseUrl: process.env.SATORI_URL!,
    userId: session.userId, // Verified by your auth system
  });
}
```

<Warning>
  Never trust user IDs from client requests. Always verify the user's identity through your authentication system.
</Warning>

## Testing Isolation

Verify isolation in your tests:

```typescript theme={null}
import { MemoryClient } from '@usesatori/tools';

describe('Memory Isolation', () => {
  const config = {
    apiKey: process.env.SATORI_API_KEY!,
    baseUrl: process.env.SATORI_URL!,
  };
  
  it('isolates memories between users', async () => {
    // Create clients for two users
    const aliceClient = new MemoryClient({ ...config, userId: 'alice' });
    const bobClient = new MemoryClient({ ...config, userId: 'bob' });
    
    // Alice saves a memory
    await aliceClient.addMemory('Alice likes TypeScript');
    
    // Bob searches for memories
    const bobMemories = await bobClient.searchMemories('TypeScript');
    
    // Bob should not see Alice's memory
    expect(bobMemories).toHaveLength(0);
  });
});
```

## Compliance and Privacy

Satori's isolation model helps you comply with privacy regulations:

<CardGroup cols={2}>
  <Card title="GDPR Compliance" icon="shield-check">
    User data is isolated and can be deleted per user with `deleteMemory()`
  </Card>

  <Card title="Data Residency" icon="globe">
    Choose your deployment region to comply with data residency requirements
  </Card>

  <Card title="Right to be Forgotten" icon="eraser">
    Delete all memories for a user to fulfill deletion requests
  </Card>

  <Card title="Data Portability" icon="download">
    Export all memories for a user with `getAllMemories()`
  </Card>
</CardGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Authentication" icon="key" href="/concepts/authentication">
    Learn about API key management
  </Card>

  <Card title="How It Works" icon="brain" href="/concepts/how-it-works">
    Understand the memory lifecycle
  </Card>

  <Card title="Integration Guide" icon="code" href="/guides/vercel-ai-sdk">
    Implement isolation in your app
  </Card>

  <Card title="API Reference" icon="book" href="/api-reference/introduction">
    Explore the complete API
  </Card>
</CardGroup>
