# API Reference Source: https://docs.usesatori.sh/api-reference/introduction Complete reference for the Satori tRPC API ## Overview Satori provides a type-safe tRPC API for all memory operations. This API is used internally by the `@usesatori/tools` package and can also be called directly for custom integrations. ## Base URL ``` https://api.usesatori.sh/trpc ``` For self-hosted deployments, use your own server URL. ## Authentication All API requests require authentication via the `x-api-key` header: ```bash theme={null} curl -X POST 'https://api.usesatori.sh/trpc/memory.add' \ -H 'x-api-key: sk_satori_...' \ -H 'Content-Type: application/json' ``` Never expose your API key in client-side code. Always make API calls from your server. ## API Structure The Satori API is organized into two main routers: ### Memory Router Operations for managing memories: * `memory.add` - Save a new memory * `memory.search` - Search for relevant memories * `memory.getAll` - Get all memories for a user * `memory.getById` - Get a specific memory by ID * `memory.delete` - Delete a memory ### Keys Router Operations for managing API keys (dashboard use): * `keys.list` - List all API keys * `keys.create` - Create a new API key * `keys.revoke` - Revoke an API key ## Using tRPC Client ### TypeScript/JavaScript Install the tRPC client: ```bash theme={null} npm install @trpc/client @satori/server ``` Create a typed client: ```typescript theme={null} import { createTRPCClient, httpBatchLink } from '@trpc/client'; import type { AppRouter } from '@satori/server'; const client = createTRPCClient({ links: [ httpBatchLink({ url: 'https://api.usesatori.sh/trpc', headers() { return { 'x-api-key': process.env.SATORI_API_KEY!, }; }, }), ], }); // Use with full type safety const memory = await client.memory.add.mutate({ userId: 'user-123', content: 'User prefers TypeScript', }); ``` ### React Use with React Query: ```typescript theme={null} import { createTRPCReact } from '@trpc/react-query'; import type { AppRouter } from '@satori/server'; export const trpc = createTRPCReact(); // In your component function MyComponent() { const { data: memories } = trpc.memory.search.useQuery({ userId: 'user-123', query: 'preferences', }); return
{/* render memories */}
; } ``` ## Rate Limits | Limit Type | Value | | --------------------- | --------- | | Requests per minute | 100 | | Concurrent requests | 10 | | Max memories per user | Unlimited | Rate limits are per API key. Contact support for higher limits. ## Error Handling All API errors follow this format: ```typescript theme={null} { error: { message: string; code: string; data?: { code: string; httpStatus: number; path: string; }; } } ``` ### Common Error Codes | Code | HTTP Status | Description | | ----------------------- | ----------- | -------------------------- | | `UNAUTHORIZED` | 401 | Invalid or missing API key | | `BAD_REQUEST` | 400 | Invalid request parameters | | `NOT_FOUND` | 404 | Resource not found | | `TOO_MANY_REQUESTS` | 429 | Rate limit exceeded | | `INTERNAL_SERVER_ERROR` | 500 | Server error | ### Example Error Response ```json theme={null} { "error": { "message": "Unauthorized - Invalid API key", "code": "UNAUTHORIZED", "data": { "code": "UNAUTHORIZED", "httpStatus": 401, "path": "memory.add" } } } ``` ## Request/Response Format ### tRPC Queries (GET operations) Queries use GET requests with query parameters: ```bash theme={null} GET /trpc/memory.search?input={"userId":"user-123","query":"preferences"} ``` ### tRPC Mutations (POST operations) Mutations use POST requests with JSON body: ```bash theme={null} POST /trpc/memory.add Content-Type: application/json { "userId": "user-123", "content": "User prefers TypeScript" } ``` ## Batch Requests tRPC supports batching multiple requests into one HTTP call: ```typescript theme={null} const [memories, allMemories] = await Promise.all([ client.memory.search.query({ userId: 'user-123', query: 'preferences' }), client.memory.getAll.query({ userId: 'user-123' }), ]); ``` This sends a single HTTP request with both operations. ## Health Check Check API health: ```bash theme={null} GET /health ``` Response: ```json theme={null} { "status": "ok", "db": "connected", "timestamp": 1705334400000 } ``` ## Next Steps Explore memory management endpoints Learn about key management endpoints See the API in action Use the MemoryClient wrapper # Create API Key Source: https://docs.usesatori.sh/api-reference/keys/create Generate a new API key for your account ## `keys.create` Creates a new API key. This endpoint requires Clerk JWT authentication. ## Authentication ```bash theme={null} Authorization: Bearer ``` ## Parameters Descriptive name for the API key. Use names that indicate the key's purpose. ```typescript theme={null} name: 'Production Key' name: 'Development Key' name: 'Testing Key' ``` ## Response Internal UUID for the API key record Your Clerk user ID Clerk's API key ID The name you provided The actual API key secret. **Only returned on creation.** Save this immediately! You won't be able to see it again. ISO 8601 timestamp of creation ## Examples ```typescript TypeScript (React) theme={null} import { trpc } from '@/lib/trpc'; function CreateKeyButton() { const createKey = trpc.keys.create.useMutation({ onSuccess: (data) => { // Show the secret to the user alert(`Your API key: ${data.secret}\n\nSave this now!`); }, }); const handleCreate = () => { const name = prompt('Enter a name for this key:'); if (name) { createKey.mutate({ name }); } }; return ( ); } ``` ```bash cURL theme={null} curl -X POST 'https://api.usesatori.sh/trpc/keys.create' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{"name":"Production Key"}' ``` ```json Success (200) theme={null} { "id": "550e8400-e29b-41d4-a716-446655440000", "clerkUserId": "user_372Icb...", "clerkKeyId": "key_2abc123...", "name": "Production Key", "secret": "sk_satori_abc123def456...", "createdAt": "2024-01-15T10:30:00.000Z" } ``` ```json Error (400) theme={null} { "error": { "message": "Invalid input: name is required", "code": "BAD_REQUEST" } } ``` The `secret` field is only returned when creating a key. Store it securely immediately. If you lose it, you'll need to create a new key. ## Best Practices ```typescript theme={null} // ✅ Good: Clear purpose await createKey.mutate({ name: 'Production - Web App' }); await createKey.mutate({ name: 'Development - Local' }); await createKey.mutate({ name: 'CI/CD Pipeline' }); // ❌ Bad: Vague names await createKey.mutate({ name: 'Key 1' }); await createKey.mutate({ name: 'Test' }); ``` ```typescript theme={null} // Production const prodKey = await createKey.mutate({ name: 'Production Environment', }); // Staging const stagingKey = await createKey.mutate({ name: 'Staging Environment', }); // Development const devKey = await createKey.mutate({ name: 'Development Environment', }); ``` ```typescript theme={null} // After creating a key const newKey = await createKey.mutate({ name: 'Production Key' }); // ✅ Store in environment variables console.log('Add this to your .env file:'); console.log(`SATORI_API_KEY=${newKey.secret}`); // ❌ Don't commit to version control // ❌ Don't store in client-side code // ❌ Don't share via insecure channels ``` ## Related Endpoints View all your API keys Revoke a key Learn about API key security # List API Keys Source: https://docs.usesatori.sh/api-reference/keys/list Retrieve all API keys for your account ## `keys.list` Lists all active API keys for the authenticated user. This endpoint requires Clerk JWT authentication (used in dashboard). ## Authentication This endpoint uses JWT authentication instead of API key authentication: ```bash theme={null} Authorization: Bearer ``` This endpoint is primarily used in dashboard applications where users are authenticated via Clerk. ## Parameters No parameters required. Returns all keys for the authenticated user. ## Response Returns an array of API key objects: Internal UUID for the API key record Your Clerk user ID (tenant identifier) Clerk's API key ID Descriptive name for the key (e.g., "Production Key") ISO 8601 timestamp of last usage, or null if never used ISO 8601 timestamp of creation ISO 8601 timestamp of revocation, or null if active ## Examples ```typescript TypeScript (React) theme={null} import { trpc } from '@/lib/trpc'; function APIKeysPage() { const { data: keys, isLoading } = trpc.keys.list.useQuery(); if (isLoading) return
Loading...
; return (

Your API Keys

{keys?.map((key) => (

{key.name}

Created: {new Date(key.createdAt).toLocaleDateString()}

Last used: {key.lastUsedAt ? new Date(key.lastUsedAt).toLocaleDateString() : 'Never'}

))}
); } ``` ```bash cURL theme={null} curl -X GET 'https://api.usesatori.sh/trpc/keys.list' \ -H 'Authorization: Bearer ' ```
```json Success (200) theme={null} [ { "id": "550e8400-e29b-41d4-a716-446655440000", "clerkUserId": "user_372Icb...", "clerkKeyId": "key_2abc123...", "name": "Production Key", "lastUsedAt": "2024-01-15T10:30:00.000Z", "createdAt": "2024-01-01T08:00:00.000Z", "revokedAt": null }, { "id": "660e8400-e29b-41d4-a716-446655440001", "clerkUserId": "user_372Icb...", "clerkKeyId": "key_3def456...", "name": "Development Key", "lastUsedAt": "2024-01-14T15:20:00.000Z", "createdAt": "2024-01-05T12:00:00.000Z", "revokedAt": null } ] ``` ## Related Endpoints Generate a new API key Revoke an existing key # Revoke API Key Source: https://docs.usesatori.sh/api-reference/keys/revoke Permanently revoke an API key ## `keys.revoke` Revokes an API key, immediately preventing all access. This endpoint requires Clerk JWT authentication. ## Authentication ```bash theme={null} Authorization: Bearer ``` ## Parameters UUID of the API key to revoke (from `keys.list`) ## Response Returns `true` if the key was successfully revoked ## Examples ```typescript TypeScript (React) theme={null} import { trpc } from '@/lib/trpc'; function RevokeKeyButton({ keyId }: { keyId: string }) { const revokeKey = trpc.keys.revoke.useMutation({ onSuccess: () => { alert('API key revoked successfully'); }, }); const handleRevoke = () => { if (confirm('Are you sure? This cannot be undone.')) { revokeKey.mutate({ id: keyId }); } }; return ( ); } ``` ```bash cURL theme={null} curl -X POST 'https://api.usesatori.sh/trpc/keys.revoke' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{"id":"550e8400-e29b-41d4-a716-446655440000"}' ``` ```json Success (200) theme={null} { "success": true } ``` ```json Error (404) theme={null} { "error": { "message": "API key not found", "code": "NOT_FOUND" } } ``` ```json Error (401) theme={null} { "error": { "message": "Unauthorized - API key does not belong to you", "code": "UNAUTHORIZED" } } ``` Revoking a key immediately stops all applications using that key. Make sure to update your applications with a new key before revoking the old one. ## Key Rotation Workflow ```typescript theme={null} const newKey = await createKey.mutate({ name: 'Production Key (New)', }); console.log('New key:', newKey.secret); ``` Update environment variables in all environments: ```bash theme={null} # .env.production SATORI_API_KEY=sk_satori_new_key... ``` Deploy the updates to all services. Test your application to ensure the new key is working correctly. ```typescript theme={null} await revokeKey.mutate({ id: oldKeyId }); ``` Old key is now revoked and cannot be used. ## Use Cases If you suspect a key has been exposed: ```typescript theme={null} // Immediately revoke the compromised key await revokeKey.mutate({ id: compromisedKeyId }); // Create a new key const newKey = await createKey.mutate({ name: 'Production Key (Rotated)', }); // Update your applications ASAP ``` Clean up keys that are no longer in use: ```typescript theme={null} const keys = await client.keys.list.query(); for (const key of keys) { // If not used in 90 days const ninetyDaysAgo = Date.now() - 90 * 24 * 60 * 60 * 1000; const lastUsed = key.lastUsedAt ? new Date(key.lastUsedAt).getTime() : 0; if (lastUsed < ninetyDaysAgo) { await revokeKey.mutate({ id: key.id }); console.log(`Revoked unused key: ${key.name}`); } } ``` Revoke keys when team members leave: ```typescript theme={null} // Revoke all keys associated with a project const keysToRevoke = [ 'key-id-1', 'key-id-2', 'key-id-3', ]; for (const keyId of keysToRevoke) { await revokeKey.mutate({ id: keyId }); } ``` ## Related Endpoints Find keys to revoke Create a replacement key Learn about key security # Add Memory Source: https://docs.usesatori.sh/api-reference/memory/add Save a new memory to the database ## `memory.add` Saves a new memory with automatic embedding generation for semantic search. ## Parameters User identifier for memory isolation. Must be a stable, unique ID for each user. ```typescript theme={null} userId: 'user-123' ``` The text content to save. Should be a complete, self-contained statement. ```typescript theme={null} content: 'User prefers TypeScript over JavaScript for type safety' ``` Write memories as complete sentences that make sense out of context. Optional metadata for categorization and filtering. ```typescript theme={null} metadata: { category: 'preference', tags: ['programming', 'languages'], importance: 'high', customField: 'value' } ``` ## Response Unique identifier (UUID) for the memory The saved text content User identifier Tenant identifier (API key owner) Custom metadata object ISO 8601 timestamp of creation ISO 8601 timestamp of last update ## Examples ```typescript TypeScript theme={null} import { createTRPCClient, httpBatchLink } from '@trpc/client'; import type { AppRouter } from '@satori/server'; const client = createTRPCClient({ links: [ httpBatchLink({ url: 'https://api.usesatori.sh/trpc', headers: { 'x-api-key': process.env.SATORI_API_KEY!, }, }), ], }); const memory = await client.memory.add.mutate({ userId: 'user-123', content: 'User prefers dark mode in all applications', metadata: { category: 'preference', tags: ['ui', 'theme'], }, }); console.log('Memory saved:', memory.id); ``` ```javascript JavaScript theme={null} const memory = await client.memory.add.mutate({ userId: 'user-123', content: 'User prefers dark mode in all applications', metadata: { category: 'preference', tags: ['ui', 'theme'], }, }); ``` ```bash cURL theme={null} curl -X POST 'https://api.usesatori.sh/trpc/memory.add' \ -H 'x-api-key: sk_satori_...' \ -H 'Content-Type: application/json' \ -d '{ "userId": "user-123", "content": "User prefers dark mode in all applications", "metadata": { "category": "preference", "tags": ["ui", "theme"] } }' ``` ```json Request theme={null} { "userId": "user-123", "content": "User prefers TypeScript over JavaScript for type safety", "metadata": { "category": "preference", "tags": ["programming", "languages"], "importance": "high" } } ``` ```json Success (200) theme={null} { "id": "550e8400-e29b-41d4-a716-446655440000", "content": "User prefers TypeScript over JavaScript for type safety", "userId": "user-123", "clerkUserId": "user_372Icb...", "metadata": { "category": "preference", "tags": ["programming", "languages"], "importance": "high" }, "createdAt": "2024-01-15T10:30:00.000Z", "updatedAt": "2024-01-15T10:30:00.000Z" } ``` ```json Error (401) theme={null} { "error": { "message": "Unauthorized - Invalid API key", "code": "UNAUTHORIZED" } } ``` ```json Error (400) theme={null} { "error": { "message": "Invalid input: content is required", "code": "BAD_REQUEST" } } ``` ## Embedding Generation When you save a memory, Satori automatically: 1. Sends the content to OpenAI's `text-embedding-3-small` model 2. Receives a 1536-dimensional vector 3. Stores both the text and embedding in PostgreSQL This enables semantic search without any additional work on your part. Embedding generation typically takes 50-100ms. The API call returns after the memory is fully saved with its embedding. ## Best Practices ```typescript theme={null} // ✅ Good: Complete sentence with context await client.memory.add.mutate({ userId: 'user-123', content: 'User prefers TypeScript over JavaScript for type safety', }); // ❌ Bad: Too vague await client.memory.add.mutate({ userId: 'user-123', content: 'likes TS', }); ``` ```typescript theme={null} await client.memory.add.mutate({ userId: 'user-123', content: 'User works at Acme Corp as a senior engineer', metadata: { category: 'professional', subcategory: 'employment', verified: true, source: 'user-provided', }, }); ``` ```typescript theme={null} // Search first to avoid duplicates const existing = await client.memory.search.query({ userId: 'user-123', query: 'TypeScript preference', threshold: 0.9, }); if (existing.length === 0) { await client.memory.add.mutate({ userId: 'user-123', content: 'User prefers TypeScript', }); } ``` ## Error Handling ```typescript theme={null} try { const memory = await client.memory.add.mutate({ userId: 'user-123', content: 'Important information', }); console.log('Memory saved:', memory.id); } catch (error) { if (error.message.includes('Unauthorized')) { console.error('Invalid API key'); } else if (error.message.includes('rate limit')) { console.error('Rate limit exceeded, retry later'); } else { console.error('Failed to save memory:', error); } } ``` ## Related Endpoints Find relevant memories using semantic search Retrieve all memories for a user Remove a specific memory See this endpoint in action # Delete Memory Source: https://docs.usesatori.sh/api-reference/memory/delete Remove a specific memory by ID ## `memory.delete` Permanently deletes a memory from the database. ## Parameters UUID of the memory to delete ## Response Returns `true` if the memory was successfully deleted ## Examples ```typescript TypeScript theme={null} await client.memory.delete.mutate({ id: '550e8400-e29b-41d4-a716-446655440000', }); console.log('Memory deleted'); ``` ```bash cURL theme={null} curl -X POST 'https://api.usesatori.sh/trpc/memory.delete' \ -H 'x-api-key: sk_satori_...' \ -H 'Content-Type: application/json' \ -d '{"id":"550e8400-e29b-41d4-a716-446655440000"}' ``` ```json Success (200) theme={null} { "success": true } ``` ```json Error (404) theme={null} { "error": { "message": "Memory not found", "code": "NOT_FOUND" } } ``` Deletion is permanent and cannot be undone. Make sure you have the correct memory ID before deleting. ## Use Cases ```typescript theme={null} // User says "forget that I like TypeScript" const memories = await client.memory.search.query({ userId: 'user-123', query: 'TypeScript preference', threshold: 0.9, }); if (memories.length > 0) { await client.memory.delete.mutate({ id: memories[0].id, }); } ``` ```typescript theme={null} // Delete all memories for a user const memories = await client.memory.getAll.query({ userId: 'user-123', }); for (const memory of memories) { await client.memory.delete.mutate({ id: memory.id, }); } ``` ```typescript theme={null} // Delete old memories const memories = await client.memory.getAll.query({ userId: 'user-123', }); const sixMonthsAgo = Date.now() - 6 * 30 * 24 * 60 * 60 * 1000; for (const memory of memories) { if (new Date(memory.createdAt).getTime() < sixMonthsAgo) { await client.memory.delete.mutate({ id: memory.id }); } } ``` ## Related Endpoints Find memories to delete Find specific memories to delete # Get All Memories Source: https://docs.usesatori.sh/api-reference/memory/get-all Retrieve all memories for a user ## `memory.getAll` Retrieves all memories for a specific user, ordered by creation date (newest first). ## Parameters User identifier for memory isolation Maximum number of memories to return. Range: 1-1000. ## Response Returns an array of memory objects: Memory UUID Memory text content User identifier Tenant identifier Custom metadata ISO 8601 timestamp ISO 8601 timestamp ## Examples ```typescript TypeScript theme={null} const memories = await client.memory.getAll.query({ userId: 'user-123', limit: 50, }); console.log(`Found ${memories.length} memories`); ``` ```bash cURL theme={null} curl -X GET 'https://api.usesatori.sh/trpc/memory.getAll?input={"userId":"user-123","limit":50}' \ -H 'x-api-key: sk_satori_...' ``` ```json Success (200) theme={null} [ { "id": "550e8400-e29b-41d4-a716-446655440000", "content": "User prefers TypeScript over JavaScript", "userId": "user-123", "clerkUserId": "user_372Icb...", "metadata": { "category": "preference" }, "createdAt": "2024-01-15T10:30:00.000Z", "updatedAt": "2024-01-15T10:30:00.000Z" } ] ``` ## Use Cases Display all memories for management: ```typescript theme={null} const memories = await client.memory.getAll.query({ userId: 'user-123', }); return (

All Memories ({memories.length})

{memories.map(m => (
{m.content}
))}
); ```
Export user data for GDPR compliance: ```typescript theme={null} const memories = await client.memory.getAll.query({ userId: 'user-123', limit: 1000, }); const exportData = { userId: 'user-123', exportDate: new Date().toISOString(), memories: memories.map(m => ({ content: m.content, createdAt: m.createdAt, })), }; return JSON.stringify(exportData, null, 2); ``` Analyze memory patterns: ```typescript theme={null} const memories = await client.memory.getAll.query({ userId: 'user-123', }); const categories = memories.reduce((acc, m) => { const cat = m.metadata?.category || 'uncategorized'; acc[cat] = (acc[cat] || 0) + 1; return acc; }, {}); console.log('Memories by category:', categories); ```
## Related Endpoints Find specific memories semantically Remove specific memories # Search Memories Source: https://docs.usesatori.sh/api-reference/memory/search Find relevant memories using semantic search ## `memory.search` Searches for memories using semantic similarity. Converts your query to an embedding and finds the most similar memories using cosine similarity. ## Parameters User identifier for memory isolation Natural language search query. Will be converted to an embedding for semantic matching. ```typescript theme={null} query: 'What programming languages does the user like?' ``` Maximum number of memories to return. Range: 1-100. Minimum similarity score (0-1) for results. Higher values = stricter matching. * `0.9+`: Very similar (almost exact matches) * `0.8-0.9`: Highly relevant * `0.7-0.8`: Relevant (default) * `0.6-0.7`: Somewhat relevant * `<0.6`: Loosely related ## Response Returns an array of memories with similarity scores: Memory UUID Memory text content Cosine similarity score (0-1). Higher = more similar. User identifier Custom metadata ISO 8601 timestamp ## Examples ```typescript TypeScript theme={null} const memories = await client.memory.search.query({ userId: 'user-123', query: 'programming preferences', limit: 5, threshold: 0.75, }); memories.forEach((memory) => { console.log(`${memory.content} (${(memory.similarity * 100).toFixed(0)}% match)`); }); ``` ```bash cURL theme={null} curl -X GET 'https://api.usesatori.sh/trpc/memory.search?input={"userId":"user-123","query":"programming preferences","limit":5}' \ -H 'x-api-key: sk_satori_...' ``` ```json Success (200) theme={null} [ { "id": "550e8400-e29b-41d4-a716-446655440000", "content": "User prefers TypeScript over JavaScript for type safety", "similarity": 0.92, "userId": "user-123", "clerkUserId": "user_372Icb...", "metadata": { "category": "preference" }, "createdAt": "2024-01-15T10:30:00.000Z", "updatedAt": "2024-01-15T10:30:00.000Z" }, { "id": "660e8400-e29b-41d4-a716-446655440001", "content": "User is learning Rust and enjoying it", "similarity": 0.85, "userId": "user-123", "clerkUserId": "user_372Icb...", "metadata": {}, "createdAt": "2024-01-16T14:20:00.000Z", "updatedAt": "2024-01-16T14:20:00.000Z" } ] ``` ## Semantic Search Examples ```typescript theme={null} // Query: "programming languages" // Matches: // - "User prefers TypeScript" ✅ (0.89) // - "Python is my favorite" ✅ (0.87) // - "I'm learning Rust" ✅ (0.82) // - "User likes dark mode" ❌ (0.45) ``` ```typescript theme={null} // Query: "favorite colors" // Matches: // - "User loves blue" ✅ (0.88) // - "Prefers dark themes" ✅ (0.76) // - "Green is beautiful" ✅ (0.74) ``` ```typescript theme={null} // Query: "work information" // Matches: // - "Works at Acme Corp as engineer" ✅ (0.91) // - "Office is in San Francisco" ✅ (0.83) // - "Prefers remote work" ✅ (0.79) ``` ## Tuning Search Results ### Adjusting Threshold ```typescript Strict (0.85+) theme={null} // Only very similar memories const memories = await client.memory.search.query({ userId: 'user-123', query: 'TypeScript', threshold: 0.85, }); // Returns: Exact matches only ``` ```typescript Balanced (0.7) theme={null} // Default - good balance const memories = await client.memory.search.query({ userId: 'user-123', query: 'TypeScript', threshold: 0.7, }); // Returns: Relevant matches ``` ```typescript Broad (0.6) theme={null} // Cast a wider net const memories = await client.memory.search.query({ userId: 'user-123', query: 'TypeScript', threshold: 0.6, }); // Returns: Loosely related matches ``` Start with the default threshold (0.7) and adjust based on your results. Lower for broader matches, higher for precision. ## Performance * **Latency**: 10-50ms for typical datasets * **Scalability**: Handles millions of memories efficiently * **Index**: Uses pgvector IVFFlat for fast similarity search ## Related Endpoints Save new memories to search Retrieve all memories without search Learn about semantic search Use search in your app # Authentication Source: https://docs.usesatori.sh/concepts/authentication Learn how API keys work and how to manage them securely ## Overview Satori uses API keys for authentication, powered by Clerk's API key management system. Each API key is tied to a specific tenant (you, the developer), and all memories stored using that key are isolated to your account. ## API Key Structure Satori API keys follow this format: ``` sk_satori_[random_string] ``` API keys are secrets that grant full access to your memory data. Never commit them to version control or expose them in client-side code. ## Creating an API Key Visit [satori.dev/dashboard](https://satori.dev/dashboard) and sign in with your account. Click on "API Keys" in the sidebar navigation. Click "Create API Key" and give it a descriptive name: ``` Production Key Development Key Testing Key ``` Use different keys for different environments (development, staging, production) to easily revoke access if needed. Your API key will be displayed once. Copy it immediately and store it securely. You won't be able to see the key again after closing this dialog. If you lose it, you'll need to create a new one. Store your API key in your environment variables: ```bash .env.local theme={null} SATORI_API_KEY=sk_satori_... SATORI_URL=https://api.usesatori.sh ``` ## Using API Keys ### Server-Side Usage (Recommended) Always use API keys on the server side, never in client-side code: ```typescript app/api/chat/route.ts theme={null} import { memoryTools } from '@usesatori/tools'; export async function POST(req: Request) { // ✅ Safe: Server-side API route const tools = memoryTools({ apiKey: process.env.SATORI_API_KEY!, baseUrl: process.env.SATORI_URL!, userId: 'user-123', }); // ... rest of your code } ``` ### Client-Side (Never Do This) Never expose your API key in client-side code: ```typescript theme={null} // ❌ NEVER DO THIS const tools = memoryTools({ apiKey: 'sk_satori_...', // Exposed to users! baseUrl: 'https://api.usesatori.sh', userId: 'user-123', }); ``` ## Authentication Headers When making direct HTTP requests to the Satori API, include your API key in the `x-api-key` header: ```typescript TypeScript theme={null} const response = await fetch('https://api.usesatori.sh/trpc/memory.search', { method: 'GET', headers: { 'x-api-key': process.env.SATORI_API_KEY!, 'Content-Type': 'application/json', }, }); ``` ```bash cURL theme={null} curl -X GET 'https://api.usesatori.sh/trpc/memory.search' \ -H 'x-api-key: sk_satori_...' \ -H 'Content-Type: application/json' ``` ```python Python theme={null} import requests response = requests.get( 'https://api.usesatori.sh/trpc/memory.search', headers={ 'x-api-key': os.environ['SATORI_API_KEY'], 'Content-Type': 'application/json' } ) ``` ## Tenant Isolation Model Satori uses a two-level isolation model: ```mermaid theme={null} graph TD APIKey[API Key] --> Tenant[Tenant: clerkUserId] Tenant --> User1[User: alice] Tenant --> User2[User: bob] Tenant --> User3[User: carol] User1 --> Memories1[Alice's Memories] User2 --> Memories2[Bob's Memories] User3 --> Memories3[Carol's Memories] ``` ### Level 1: Tenant (API Key Owner) Your API key identifies you as the tenant. All memories created with your key belong to your account. ```typescript theme={null} // When you create an API key in the dashboard const apiKey = await clerkClient.apiKeys.create({ name: 'Production Key', subject: 'user_372Icb...', // Your Clerk user ID }); ``` ### Level 2: End Users (Your Application's Users) Within your tenant, you can have unlimited end users, each with isolated memories: ```typescript theme={null} // Alice's memories const aliceTools = memoryTools({ apiKey: process.env.SATORI_API_KEY!, baseUrl: process.env.SATORI_URL!, userId: 'alice', // Your app's user identifier }); // Bob's memories (completely separate) const bobTools = memoryTools({ apiKey: process.env.SATORI_API_KEY!, baseUrl: process.env.SATORI_URL!, userId: 'bob', }); ``` Think of it like this: Your API key is your "account", and `userId` is how you separate your users' data within your account. ## API Key Verification Flow Here's what happens when you make a request: Your application sends a request with the `x-api-key` header: ```typescript theme={null} headers: { 'x-api-key': 'sk_satori_...' } ``` The Satori server verifies the key with Clerk: ```typescript theme={null} const verified = await clerkClient.apiKeys.verify(apiKey); // verified.subject = "user_372Icb..." (your tenant ID) ``` All database queries are automatically scoped to your tenant: ```typescript theme={null} const memories = await db .select() .from(memories) .where( and( eq(memories.clerkUserId, 'user_372Icb...'), // Your tenant eq(memories.userId, 'alice') // Specific user ) ); ``` ## Managing API Keys ### List Your Keys View all your active API keys: ```typescript theme={null} import { trpc } from '@/lib/trpc'; function APIKeysPage() { const { data: keys } = trpc.keys.list.useQuery(); return (
{keys?.map((key) => (

{key.name}

Created: {key.createdAt}

Last used: {key.lastUsedAt}

))}
); } ``` ### Revoke a Key Revoke an API key to immediately prevent all access: ```typescript theme={null} const revokeKey = trpc.keys.revoke.useMutation(); await revokeKey.mutateAsync({ id: 'key-uuid' }); ``` Revoking a key immediately stops all applications using that key. Make sure to update your applications with a new key before revoking the old one. ## Security Best Practices Store API keys in environment variables, never hardcode them: ```typescript theme={null} // ✅ Good apiKey: process.env.SATORI_API_KEY! // ❌ Bad apiKey: 'sk_satori_...' ``` Create a new API key and update your applications, then revoke the old key: 1. Create new key in dashboard 2. Update environment variables in all environments 3. Deploy updates 4. Revoke old key Separate keys for development, staging, and production: ```bash theme={null} # .env.development SATORI_API_KEY=sk_satori_dev_... # .env.production SATORI_API_KEY=sk_satori_prod_... ``` Check the "Last used" timestamp in your dashboard to detect unused or compromised keys. Add environment files to `.gitignore`: ```bash .gitignore theme={null} .env.local .env*.local .env.development.local .env.production.local ``` ## Rate Limiting API keys are subject to rate limits to prevent abuse: | Limit Type | Value | | ------------------- | --------- | | Requests per minute | 100 | | Memories per user | Unlimited | | Concurrent requests | 10 | Need higher limits? Contact support to discuss enterprise plans. ## Error Handling Handle authentication errors gracefully: ```typescript theme={null} try { const memories = await client.searchMemories('query'); } catch (error) { if (error.message.includes('Unauthorized')) { console.error('Invalid API key'); // Notify admin, rotate key, etc. } else if (error.message.includes('rate limit')) { console.error('Rate limit exceeded'); // Implement backoff strategy } } ``` ## Next Steps Learn more about tenant and user isolation Explore the API key management endpoints See authentication in action Fix common authentication issues # How It Works Source: https://docs.usesatori.sh/concepts/how-it-works Learn how Satori uses embeddings and semantic search to power AI memory ## Overview Satori provides persistent memory for AI applications through a combination of vector embeddings, semantic search, and intelligent context injection. This page explains the core mechanisms that make memory work. ## Memory Lifecycle ```mermaid theme={null} graph LR UserInput[User Input] --> Search[Semantic Search] Search --> Context[Inject Context] Context --> LLM[LLM Processing] LLM --> Decision{Important Info?} Decision -->|Yes| Save[Save Memory] Decision -->|No| Response[Stream Response] Save --> Response Response --> User[User Sees Response] ``` When a user sends a message, Satori converts it into a vector embedding and searches for similar memories using cosine similarity. ```typescript theme={null} const context = await getContext(config, userMessage, { limit: 5 }); ``` This finds the 5 most relevant memories based on semantic meaning, not just keyword matching. Retrieved memories are formatted and injected into the system prompt: ```typescript theme={null} system: `You are a helpful assistant with memory. What you know about this user: ${memoryContext} Use add_item to save important information.` ``` The language model receives both the current message and relevant historical context, allowing it to provide personalized responses. When the LLM detects important information, it calls the `add_item` tool: ```typescript theme={null} // LLM automatically calls this add_item({ memory: "User prefers TypeScript over JavaScript" }) ``` ## Vector Embeddings Explained Embeddings are numerical representations of text that capture semantic meaning. Similar concepts have similar embeddings. ### How Embeddings Work When you save a memory like "I love TypeScript", Satori: 1. Sends the text to OpenAI's embedding model 2. Receives a 1536-dimensional vector (array of numbers) 3. Stores both the text and vector in PostgreSQL with pgvector Later, when searching for "programming preferences", the query is also converted to a vector and compared using cosine similarity. ```typescript theme={null} // Memory: "I love TypeScript" // Embedding: [0.023, -0.145, 0.891, ..., 0.234] (1536 numbers) // Query: "What languages does the user like?" // Query embedding: [0.019, -0.142, 0.887, ..., 0.229] // Cosine similarity: 0.94 (very similar!) // This memory will be returned as relevant ``` * **Model**: `text-embedding-3-small` (OpenAI) * **Dimensions**: 1536 * **Similarity metric**: Cosine similarity * **Index type**: IVFFlat (pgvector) * **Storage**: PostgreSQL with pgvector extension Embeddings capture meaning, not just keywords. "I prefer TS" and "TypeScript is my favorite" will have similar embeddings even though they share no common words. ## Semantic Search vs Keyword Search **Query:** "programming languages" **Matches:** * "I like programming languages" * "Programming languages are fun" **Misses:** * "I prefer TypeScript" ❌ * "Python is my favorite" ❌ **Query:** "programming languages" **Matches:** * "I like programming languages" * "I prefer TypeScript" ✅ * "Python is my favorite" ✅ * "I'm learning Rust" ✅ ## Search Parameters When searching for memories, you can control the results: The natural language query to search for. This is converted to an embedding and compared against stored memories. Maximum number of memories to return. Range: 1-100. ```typescript theme={null} // Get top 5 most relevant memories const context = await getContext(config, query, { limit: 5 }); ``` Minimum similarity score (0-1) for a memory to be considered relevant. Higher values = stricter matching. ```typescript theme={null} // Only return very similar memories const context = await getContext(config, query, { threshold: 0.85 }); ``` Start with the default threshold (0.7) and adjust based on your needs. Lower values (0.6) cast a wider net, higher values (0.85) are more precise. ## Context Injection Patterns There are two main approaches to using memory context: ### Pattern 1: Pre-fetch and Inject (Recommended) ```typescript theme={null} // Fetch memories before LLM call const memoryContext = await getContext(config, userMessage); // Inject into system prompt const result = await streamText({ model: openai('gpt-4o'), system: `You are a helpful assistant. What you know about this user: ${memoryContext}`, messages, tools, }); ``` **Pros:** Reliable, predictable, works with all models ### Pattern 2: Tool-based Search (Advanced) ```typescript theme={null} // Let the LLM decide when to search const tools = { ...memoryTools(config), search_memory: tool({ description: 'Search for relevant memories', parameters: z.object({ query: z.string() }), execute: async ({ query }) => { return await client.searchMemories(query); }, }), }; ``` This pattern is less reliable because the LLM may not always call the search tool when needed. Use Pattern 1 for production applications. ## Memory Storage Format Each memory is stored with rich metadata: Unique identifier (UUID) for the memory The actual text content of the memory 1536-dimensional vector representation of the content User identifier for memory isolation Tenant identifier (API key owner) Optional custom metadata for filtering and organization ```typescript theme={null} { tags: ['preference', 'language'], category: 'programming', importance: 'high' } ``` ISO 8601 timestamp of when the memory was created ISO 8601 timestamp of the last update ## Performance Considerations ### Embedding Generation * **Latency**: \~50-100ms per embedding * **Cost**: \$0.00002 per 1K tokens (very cheap) * **Caching**: Consider caching embeddings for frequently searched queries ### Vector Search * **Latency**: \~10-50ms for typical datasets * **Scalability**: IVFFlat index performs well up to millions of vectors * **Optimization**: Adjust the `lists` parameter in the index for your dataset size ```sql theme={null} -- Optimize for larger datasets CREATE INDEX memories_embedding_idx ON memories USING ivfflat (embedding vector_cosine_ops) WITH (lists = 1000); -- Increase for more vectors ``` For datasets under 100K memories, the default configuration performs excellently without any tuning. ## Best Practices Explicitly tell the LLM when to save memories: ```typescript theme={null} system: `Save memories when the user: - Shares preferences or opinions - Provides personal information - Mentions important dates or events - Expresses goals or intentions` ``` Always fetch relevant context before calling the LLM, even if you think there might not be relevant memories: ```typescript theme={null} // Always do this const context = await getContext(config, userMessage); ``` Store memories in complete sentences that make sense out of context: ```typescript theme={null} // Good "User prefers TypeScript over JavaScript for type safety" // Bad "prefers TS" // Too vague ``` Periodically review stored memories to ensure quality: ```typescript theme={null} const allMemories = await client.getAllMemories(); console.log('Total memories:', allMemories.length); ``` ## Next Steps Learn how API keys and tenant isolation work Understand multi-tenant data separation See complete integration examples Explore the search API in detail # Memory Isolation Source: https://docs.usesatori.sh/concepts/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..." ``` Tenants are completely isolated from each other. Developer A can never access Developer B's data, even if they know the user IDs. ### 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 ) ); ``` It's impossible to query memories without both identifiers, ensuring complete isolation. ## Isolation Guarantees **Guarantee:** Tenants can never access each other's data **Enforced by:** * API key verification * Database-level filtering * Automatic query scoping **Guarantee:** Users within a tenant can never access each other's memories **Enforced by:** * Required userId parameter * Composite database indexes * Query-level filtering **Guarantee:** Even with a valid userId, cross-tenant access is impossible **Enforced by:** * API key binding to clerkUserId * Middleware authentication * Database constraints **Guarantee:** Data is encrypted at rest and in transit **Enforced by:** * PostgreSQL encryption * TLS/HTTPS connections * Secure key storage ## 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. In a multi-tenant SaaS, each company should have their own API key. Don't share API keys across companies. ## User ID Best Practices 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 ``` 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! }); ``` 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" ``` 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}`; } ``` ## Security Considerations ### API Key Security 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 }); } ``` Never expose API keys to the client: ```typescript theme={null} // ❌ Client component 'use client'; function ChatPage() { const tools = memoryTools({ apiKey: 'sk_satori_...', // EXPOSED! }); } ``` 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 ``` ### 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 }); } ``` Never trust user IDs from client requests. Always verify the user's identity through your authentication system. ## 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: User data is isolated and can be deleted per user with `deleteMemory()` Choose your deployment region to comply with data residency requirements Delete all memories for a user to fulfill deletion requests Export all memories for a user with `getAllMemories()` ## Next Steps Learn about API key management Understand the memory lifecycle Implement isolation in your app Explore the complete API # Chat with Memory Source: https://docs.usesatori.sh/examples/chat-with-memory Complete example of building a chat application with persistent memory ## Overview This example demonstrates a complete chat application that remembers user preferences, context, and conversations using Satori. ## Features * Persistent memory across sessions * Automatic information extraction * Semantic context retrieval * Natural conversation flow ## Complete Implementation ### API Route ```typescript app/api/chat/route.ts theme={null} import { streamText } from 'ai'; import { openai } from '@ai-sdk/openai'; import { memoryTools, getContext } from '@usesatori/tools'; import { auth } from '@clerk/nextjs/server'; export async function POST(req: Request) { try { // Authenticate user const { userId } = await auth(); if (!userId) { return new Response('Unauthorized', { status: 401 }); } // Parse request const { messages } = await req.json(); const userMessage = messages[messages.length - 1].content; // Configure memory const memoryConfig = { apiKey: process.env.SATORI_API_KEY!, baseUrl: process.env.SATORI_URL!, userId, }; // Create memory tools const tools = memoryTools(memoryConfig); // Pre-fetch relevant context const memoryContext = await getContext( memoryConfig, userMessage, { limit: 5 } ); // Stream response with memory const result = await streamText({ model: openai('gpt-4o'), system: `You are a helpful AI assistant with long-term memory. ${memoryContext ? `What you know about this user:\n${memoryContext}\n` : ''} When the user shares important information, use the add_item tool to save it. Important information includes: - Personal preferences (favorite things, dislikes) - Personal details (name, occupation, location) - Goals and intentions - Important dates or events - Opinions and beliefs Be natural and conversational. Don't explicitly mention that you're saving memories unless asked.`, messages, tools, maxSteps: 5, }); return result.toDataStreamResponse(); } catch (error) { console.error('Chat error:', error); return new Response('Internal Server Error', { status: 500 }); } } ``` ### Chat Interface ```typescript app/chat/page.tsx theme={null} 'use client'; import { useChat } from 'ai/react'; import { useUser } from '@clerk/nextjs'; export default function ChatPage() { const { user } = useUser(); const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat(); return (
{/* Header */}

Chat with Memory

I'll remember what you tell me across conversations

{/* Messages */}
{messages.length === 0 && (

Start a conversation!

Try saying:

  • "My name is Alex and I'm a software engineer"
  • "I prefer TypeScript over JavaScript"
  • "I love hiking and photography"
  • "What do you know about me?"
)} {messages.map((message) => (

{message.role === 'user' ? 'You' : 'Assistant'}

{message.content}

{/* Show when memories are saved */} {message.toolInvocations?.map((tool, i) => (
{tool.toolName === 'add_item' && ( <> 💾 Saved to memory )}
))}
))} {isLoading && (
)}
{/* Input */}
); } ``` ## Example Conversations ### Conversation 1: Building Context **User:** "Hi! My name is Alex and I'm a software engineer at Acme Corp." **Assistant:** "Nice to meet you, Alex! It's great to connect with a software engineer. What kind of projects do you work on at Acme Corp?" *Behind the scenes: Saves "User's name is Alex and works as a software engineer at Acme Corp"* **User:** "I really prefer TypeScript over JavaScript. The type safety is just so much better." **Assistant:** "I completely understand! TypeScript's type safety does make a huge difference in catching bugs early and improving code maintainability. Do you use TypeScript for all your projects?" *Behind the scenes: Saves "User prefers TypeScript over JavaScript for type safety"* **User:** "In my free time, I love hiking and photography. I try to get out into nature every weekend." **Assistant:** "That's a wonderful combination! Photography and hiking complement each other perfectly. Do you have a favorite hiking spot where you like to take photos?" *Behind the scenes: Saves "User enjoys hiking and photography, tries to go every weekend"* ### Conversation 2: Recalling Context (New Session) **User:** "Hey, what's up?" **Assistant:** "Hey Alex! How's it going? Did you get a chance to go hiking this weekend?" *Behind the scenes: Retrieved memories about name and hiking hobby* **User:** "I'm looking for a new side project to work on." **Assistant:** "Since you love TypeScript and photography, have you thought about building a photo management app? You could use TypeScript with Next.js for the frontend and maybe add some AI features for automatic tagging. It would combine your technical preferences with your hobby!" *Behind the scenes: Retrieved memories about TypeScript preference and photography hobby* **User:** "What do you remember about me?" **Assistant:** "Let me see... I know that your name is Alex and you work as a software engineer at Acme Corp. You prefer TypeScript over JavaScript because of the type safety. In your free time, you enjoy hiking and photography, and you try to get out into nature every weekend. Is there anything else you'd like me to remember?" *Behind the scenes: Retrieved all relevant memories* ### Conversation 3: Updating Information **User:** "Actually, I just started a new job at TechCo!" **Assistant:** "Congratulations on the new position at TechCo! That's exciting. What will you be working on there?" *Behind the scenes: Saves "User now works at TechCo" (new memory)* **User:** "Can you forget that I work at TechCo? I'd rather not share work details." **Assistant:** "Of course, I've removed that information. Your privacy is important." *Behind the scenes: Searches for and deletes work-related memories* ## Key Features Demonstrated The LLM automatically identifies and saves important information without explicit commands Context is retrieved based on meaning, not just keywords Memory operations happen transparently without disrupting conversation Users can ask what's remembered and request deletions ## Customization Ideas ```typescript theme={null} const tools = { add_preference: tool({ description: 'Save a user preference', parameters: z.object({ preference: z.string() }), execute: async ({ preference }) => { await client.addMemory(preference, { metadata: { category: 'preference' }, }); }, }), add_fact: tool({ description: 'Save a factual piece of information', parameters: z.object({ fact: z.string() }), execute: async ({ fact }) => { await client.addMemory(fact, { metadata: { category: 'fact' }, }); }, }), }; ``` ```typescript theme={null} system: `When saving memories, consider importance: - High: Core identity, strong preferences, critical information - Medium: Casual preferences, general interests - Low: Temporary states, minor details Add importance to metadata when saving.` ``` ```typescript theme={null} // After each conversation const summary = await generateSummary(messages); await client.addMemory(summary, { metadata: { type: 'conversation_summary', date: new Date().toISOString(), }, }); ``` ## Next Steps See how to store and use preferences Build a complete app Explore all memory operations Fix common issues # User Preferences Source: https://docs.usesatori.sh/examples/user-preferences Store and retrieve user preferences with memory ## Overview This example shows how to use Satori to store and retrieve user preferences, enabling personalized experiences across your application. ## Use Case Build an AI assistant that remembers user preferences for: * UI settings (theme, language, layout) * Content preferences (topics, formats, difficulty) * Communication style (formal vs casual, verbosity) * Notification preferences ## Implementation ### Saving Preferences ```typescript app/api/preferences/route.ts theme={null} import { MemoryClient } from '@usesatori/tools'; import { auth } from '@clerk/nextjs/server'; export async function POST(req: Request) { const { userId } = await auth(); if (!userId) { return new Response('Unauthorized', { status: 401 }); } const { preference, category } = await req.json(); const client = new MemoryClient({ apiKey: process.env.SATORI_API_KEY!, baseUrl: process.env.SATORI_URL!, userId, }); // Save preference with metadata await client.addMemory(preference, { metadata: { type: 'preference', category, timestamp: new Date().toISOString(), }, }); return Response.json({ success: true }); } ``` ### Retrieving Preferences ```typescript app/api/preferences/get/route.ts theme={null} import { MemoryClient } from '@usesatori/tools'; import { auth } from '@clerk/nextjs/server'; export async function GET(req: Request) { const { userId } = await auth(); if (!userId) { return new Response('Unauthorized', { status: 401 }); } const { searchParams } = new URL(req.url); const category = searchParams.get('category'); const client = new MemoryClient({ apiKey: process.env.SATORI_API_KEY!, baseUrl: process.env.SATORI_URL!, userId, }); // Search for preferences in a specific category const query = category ? `${category} preferences` : 'user preferences'; const preferences = await client.searchMemories(query, { limit: 20, threshold: 0.7, }); // Filter by metadata if needed const filtered = category ? preferences.filter(p => p.metadata?.category === category) : preferences; return Response.json({ preferences: filtered }); } ``` ### Preferences UI Component ```typescript components/PreferencesPanel.tsx theme={null} 'use client'; import { useState, useEffect } from 'react'; interface Preference { id: string; content: string; category: string; createdAt: string; } export function PreferencesPanel() { const [preferences, setPreferences] = useState([]); const [newPref, setNewPref] = useState(''); const [category, setCategory] = useState('general'); useEffect(() => { loadPreferences(); }, []); async function loadPreferences() { const response = await fetch('/api/preferences/get'); const data = await response.json(); setPreferences(data.preferences); } async function savePreference() { await fetch('/api/preferences', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ preference: newPref, category, }), }); setNewPref(''); loadPreferences(); } return (

Your Preferences

{/* Add Preference */}

Add New Preference

setNewPref(e.target.value)} placeholder="e.g., I prefer dark mode" className="w-full p-2 border rounded" />
{/* Preferences List */}

Saved Preferences

{preferences.length === 0 ? (

No preferences saved yet

) : ( preferences.map((pref) => (

{pref.category}

{pref.content}

{new Date(pref.createdAt).toLocaleDateString()}

)) )}
); } ``` ## Preference Categories ### UI Preferences ```typescript theme={null} // Theme preference await client.addMemory('User prefers dark mode', { metadata: { category: 'ui', subcategory: 'theme' }, }); // Layout preference await client.addMemory('User prefers compact layout with sidebar', { metadata: { category: 'ui', subcategory: 'layout' }, }); // Language preference await client.addMemory('User prefers Spanish language', { metadata: { category: 'ui', subcategory: 'language' }, }); ``` ### Content Preferences ```typescript theme={null} // Topic interests await client.addMemory('User is interested in AI and machine learning', { metadata: { category: 'content', subcategory: 'topics' }, }); // Content format await client.addMemory('User prefers video tutorials over text articles', { metadata: { category: 'content', subcategory: 'format' }, }); // Difficulty level await client.addMemory('User prefers advanced technical content', { metadata: { category: 'content', subcategory: 'difficulty' }, }); ``` ### Communication Preferences ```typescript theme={null} // Tone preference await client.addMemory('User prefers casual, friendly communication', { metadata: { category: 'communication', subcategory: 'tone' }, }); // Verbosity await client.addMemory('User prefers concise responses', { metadata: { category: 'communication', subcategory: 'verbosity' }, }); // Explanation style await client.addMemory('User likes code examples with explanations', { metadata: { category: 'communication', subcategory: 'style' }, }); ``` ## Using Preferences in Chat ```typescript app/api/chat/route.ts theme={null} import { streamText } from 'ai'; import { openai } from '@ai-sdk/openai'; import { MemoryClient } from '@usesatori/tools'; export async function POST(req: Request) { const { userId } = await auth(); const { messages } = await req.json(); const client = new MemoryClient({ apiKey: process.env.SATORI_API_KEY!, baseUrl: process.env.SATORI_URL!, userId: userId!, }); // Fetch user preferences const preferences = await client.searchMemories('user preferences', { limit: 10, }); // Build preferences context const preferencesContext = preferences .map(p => p.content) .join('\n'); const result = await streamText({ model: openai('gpt-4o'), system: `You are a helpful assistant. Adapt your responses based on these user preferences: ${preferencesContext} Follow these preferences in your communication style, content recommendations, and overall interaction.`, messages, }); return result.toDataStreamResponse(); } ``` ## Example Interactions **User:** "I prefer dark mode and a compact layout" **Assistant:** "Got it! I'll remember that you prefer dark mode and a compact layout. Your interface should reflect these preferences." *Saves: "User prefers dark mode" and "User prefers compact layout"* **User:** "Show me some tutorials" **Assistant:** "Based on your preferences for advanced technical content in video format, here are some great video tutorials on AI and machine learning..." *Uses preferences to personalize recommendations* **User:** "Explain how embeddings work" **Assistant:** "Sure! Here's a concise explanation with code: ```python theme={null} # Embeddings convert text to vectors embedding = model.encode('Hello world') # Result: [0.23, -0.45, 0.67, ...] ``` Embeddings capture semantic meaning in numbers that computers can compare." *Adapts to preference for concise responses with code examples* ## Advanced Patterns ### Preference Conflicts ```typescript theme={null} // Handle conflicting preferences const preferences = await client.searchMemories('theme preference'); // If multiple theme preferences exist, use the most recent const sortedByDate = preferences.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime() ); const currentTheme = sortedByDate[0]; ``` ### Preference Hierarchy ```typescript theme={null} // Define preference importance await client.addMemory('User strongly prefers TypeScript', { metadata: { category: 'content', importance: 'high', strength: 'strong', }, }); await client.addMemory('User somewhat likes Python', { metadata: { category: 'content', importance: 'medium', strength: 'moderate', }, }); ``` ### Preference Expiration ```typescript theme={null} // Add expiration to temporary preferences await client.addMemory('User is currently learning React', { metadata: { category: 'content', temporary: true, expiresAt: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000).toISOString(), }, }); // Clean up expired preferences const allPrefs = await client.getAllMemories(); const now = Date.now(); for (const pref of allPrefs) { if (pref.metadata?.expiresAt) { const expiresAt = new Date(pref.metadata.expiresAt).getTime(); if (now > expiresAt) { await client.deleteMemory(pref.id); } } } ``` ## Best Practices ```typescript theme={null} // ✅ Good: Specific and actionable "User prefers dark mode with high contrast" "User wants email notifications only for critical alerts" // ❌ Bad: Vague "User likes dark stuff" "User doesn't want spam" ``` ```typescript theme={null} await client.addMemory('User prefers TypeScript', { metadata: { category: 'programming', subcategory: 'languages', importance: 'high', source: 'explicit', // vs 'inferred' confidence: 0.95, }, }); ``` ```typescript theme={null} // Search for existing preference const existing = await client.searchMemories('theme preference', { threshold: 0.9, }); // Delete old preference if (existing.length > 0) { await client.deleteMemory(existing[0].id); } // Add new preference await client.addMemory('User now prefers light mode'); ``` ## Next Steps See memory in conversations Learn the MemoryClient API Explore search options Learn about metadata # Direct Client Usage Source: https://docs.usesatori.sh/guides/direct-client Use the MemoryClient directly for custom integrations and advanced use cases ## Overview While the `memoryTools()` helper is great for Vercel AI SDK integration, you can also use the `MemoryClient` directly for more control over memory operations. This is useful for: * Custom AI frameworks * Background jobs that manage memories * Admin interfaces for viewing/managing memories * Non-LLM use cases ## Installation ```bash theme={null} npm install @usesatori/tools ``` ## Basic Usage ### Creating a Client ```typescript theme={null} import { MemoryClient } from '@usesatori/tools'; const client = new MemoryClient({ apiKey: process.env.SATORI_API_KEY!, baseUrl: process.env.SATORI_URL!, userId: 'user-123', }); ``` ### Adding Memories ```typescript theme={null} const memory = await client.addMemory( 'User prefers TypeScript over JavaScript' ); console.log('Saved memory:', memory.id); ``` With metadata: ```typescript theme={null} const memory = await client.addMemory( 'User prefers dark mode in all applications', { metadata: { category: 'preference', tags: ['ui', 'theme'], importance: 'high', }, } ); ``` ### Searching Memories ```typescript theme={null} const memories = await client.searchMemories('programming languages'); memories.forEach((memory) => { console.log(`${memory.content} (similarity: ${memory.similarity})`); }); ``` With options: ```typescript theme={null} const memories = await client.searchMemories('preferences', { limit: 5, threshold: 0.8, // Only very similar memories }); ``` ### Getting All Memories ```typescript theme={null} const allMemories = await client.getAllMemories(); console.log(`Total memories: ${allMemories.length}`); ``` With limit: ```typescript theme={null} const recentMemories = await client.getAllMemories({ limit: 10 }); ``` ### Deleting Memories ```typescript theme={null} await client.deleteMemory('memory-uuid'); console.log('Memory deleted'); ``` ## Complete API Reference ### Constructor Your Satori API key Satori server URL (e.g., `https://api.usesatori.sh`) User identifier for memory isolation ### Methods #### addMemory(content, options?) Saves a new memory. The text content to save Optional metadata for organization ```typescript theme={null} { category: 'preference', tags: ['important'], customField: 'value' } ``` **Returns:** `Promise` ```typescript theme={null} { id: 'uuid', content: 'User prefers TypeScript', userId: 'user-123', metadata: { category: 'preference' }, createdAt: '2024-01-15T10:30:00Z', updatedAt: '2024-01-15T10:30:00Z' } ``` #### searchMemories(query, options?) Searches for semantically similar memories. Natural language search query Maximum number of results (1-100) Minimum similarity score (0-1) **Returns:** `Promise` ```typescript theme={null} [ { id: 'uuid', content: 'User prefers TypeScript', similarity: 0.92, userId: 'user-123', metadata: {}, createdAt: '2024-01-15T10:30:00Z', updatedAt: '2024-01-15T10:30:00Z' } ] ``` #### getAllMemories(options?) Retrieves all memories for the user. Maximum number of memories to return **Returns:** `Promise` #### deleteMemory(id) Deletes a specific memory. UUID of the memory to delete **Returns:** `Promise` ## Advanced Use Cases ### Building an Admin Dashboard ```typescript theme={null} import { MemoryClient } from '@usesatori/tools'; export async function GET(req: Request) { const { searchParams } = new URL(req.url); const userId = searchParams.get('userId'); if (!userId) { return new Response('Missing userId', { status: 400 }); } const client = new MemoryClient({ apiKey: process.env.SATORI_API_KEY!, baseUrl: process.env.SATORI_URL!, userId, }); const memories = await client.getAllMemories(); return Response.json({ userId, totalMemories: memories.length, memories: memories.map((m) => ({ id: m.id, content: m.content, createdAt: m.createdAt, metadata: m.metadata, })), }); } ``` ### Background Memory Processing ```typescript theme={null} // Cron job to clean up old memories import { MemoryClient } from '@usesatori/tools'; async function cleanupOldMemories(userId: string) { const client = new MemoryClient({ apiKey: process.env.SATORI_API_KEY!, baseUrl: process.env.SATORI_URL!, userId, }); const memories = await client.getAllMemories(); const sixMonthsAgo = Date.now() - 6 * 30 * 24 * 60 * 60 * 1000; for (const memory of memories) { const createdAt = new Date(memory.createdAt).getTime(); if (createdAt < sixMonthsAgo) { await client.deleteMemory(memory.id); console.log(`Deleted old memory: ${memory.id}`); } } } ``` ### Memory Export ```typescript theme={null} // Export user data for GDPR compliance async function exportUserMemories(userId: string) { const client = new MemoryClient({ apiKey: process.env.SATORI_API_KEY!, baseUrl: process.env.SATORI_URL!, userId, }); const memories = await client.getAllMemories(); const exportData = { userId, exportDate: new Date().toISOString(), totalMemories: memories.length, memories: memories.map((m) => ({ content: m.content, createdAt: m.createdAt, metadata: m.metadata, })), }; return JSON.stringify(exportData, null, 2); } ``` ### Custom AI Framework Integration ```typescript theme={null} import { MemoryClient } from '@usesatori/tools'; import Anthropic from '@anthropic-ai/sdk'; const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY!, }); const memoryClient = new MemoryClient({ apiKey: process.env.SATORI_API_KEY!, baseUrl: process.env.SATORI_URL!, userId: 'user-123', }); async function chatWithMemory(userMessage: string) { // Search for relevant context const relevantMemories = await memoryClient.searchMemories(userMessage, { limit: 5, }); const context = relevantMemories .map((m) => m.content) .join('\n'); // Call Claude with context const response = await anthropic.messages.create({ model: 'claude-3-5-sonnet-20241022', max_tokens: 1024, messages: [ { role: 'user', content: `Context about the user:\n${context}\n\nUser message: ${userMessage}`, }, ], }); // Extract and save any important information const responseText = response.content[0].text; // Simple heuristic: if user said "remember" or "I am/like/prefer" if (/remember|I am|I like|I prefer/i.test(userMessage)) { await memoryClient.addMemory(userMessage); } return responseText; } ``` ## Error Handling Handle errors appropriately: ```typescript theme={null} try { const memories = await client.searchMemories('query'); } catch (error) { if (error instanceof Error) { if (error.message.includes('Unauthorized')) { console.error('Invalid API key'); } else if (error.message.includes('rate limit')) { console.error('Rate limit exceeded'); // Implement exponential backoff } else if (error.message.includes('Not Found')) { console.error('Memory not found'); } else { console.error('Unknown error:', error.message); } } throw error; } ``` ## TypeScript Types The client exports TypeScript types for all operations: ```typescript theme={null} import type { Memory, MemoryWithSimilarity, AddMemoryOptions, SearchOptions } from '@usesatori/tools'; const memory: Memory = { id: 'uuid', content: 'User prefers TypeScript', userId: 'user-123', clerkUserId: 'user_372Icb...', metadata: {}, createdAt: '2024-01-15T10:30:00Z', updatedAt: '2024-01-15T10:30:00Z', }; const searchResult: MemoryWithSimilarity = { ...memory, similarity: 0.92, }; ``` ## Testing Mock the client for testing: ```typescript theme={null} import { MemoryClient } from '@usesatori/tools'; // Mock the client jest.mock('@usesatori/tools', () => ({ MemoryClient: jest.fn().mockImplementation(() => ({ addMemory: jest.fn().mockResolvedValue({ id: 'test-uuid', content: 'Test memory', userId: 'test-user', metadata: {}, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }), searchMemories: jest.fn().mockResolvedValue([]), getAllMemories: jest.fn().mockResolvedValue([]), deleteMemory: jest.fn().mockResolvedValue(undefined), })), })); describe('Memory operations', () => { it('saves a memory', async () => { const client = new MemoryClient({ apiKey: 'test-key', baseUrl: 'http://localhost:3001', userId: 'test-user', }); const memory = await client.addMemory('Test content'); expect(memory.content).toBe('Test memory'); }); }); ``` ## Best Practices Create one client per user and reuse it: ```typescript theme={null} // ✅ Good: Reuse client const client = new MemoryClient(config); await client.addMemory('Memory 1'); await client.addMemory('Memory 2'); // ❌ Bad: Create new client each time await new MemoryClient(config).addMemory('Memory 1'); await new MemoryClient(config).addMemory('Memory 2'); ``` Store complete, self-contained information: ```typescript theme={null} // ✅ Good await client.addMemory('User prefers dark mode in all applications'); // ❌ Bad await client.addMemory('dark mode'); // Too vague ``` Use metadata to categorize and filter memories: ```typescript theme={null} await client.addMemory('User prefers TypeScript', { metadata: { category: 'preference', subcategory: 'programming', tags: ['language', 'typescript'], importance: 'high', }, }); ``` Always wrap operations in try-catch blocks: ```typescript theme={null} try { await client.addMemory(content); } catch (error) { console.error('Failed to save memory:', error); // Fallback behavior } ``` ## Next Steps Learn about the AI SDK integration Explore the complete API documentation See complete implementations Fix common issues # Next.js Integration Source: https://docs.usesatori.sh/guides/nextjs-integration Build a complete Next.js application with Satori memory integration ## Overview This guide walks you through building a complete Next.js application with Satori memory, including: * Chat interface with memory * User authentication * Memory dashboard * API routes with proper error handling ## Project Setup ```bash theme={null} npx create-next-app@latest my-memory-app cd my-memory-app ``` Choose these options: * TypeScript: Yes * ESLint: Yes * Tailwind CSS: Yes * App Router: Yes ```bash theme={null} npm install @usesatori/tools ai @ai-sdk/openai @clerk/nextjs ``` ```bash .env.local theme={null} # Satori SATORI_API_KEY=sk_satori_... SATORI_URL=https://api.usesatori.sh # OpenAI OPENAI_API_KEY=sk-... # Clerk (for authentication) NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_... CLERK_SECRET_KEY=sk_... ``` ## Authentication Setup ### Configure Clerk ```typescript middleware.ts theme={null} import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'; const isPublicRoute = createRouteMatcher(['/sign-in(.*)', '/sign-up(.*)']); export default clerkMiddleware(async (auth, request) => { if (!isPublicRoute(request)) { await auth.protect(); } }); export const config = { matcher: [ '/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)', '/(api|trpc)(.*)', ], }; ``` ### Root Layout ```typescript app/layout.tsx theme={null} import { ClerkProvider } from '@clerk/nextjs'; import './globals.css'; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( {children} ); } ``` ## Chat API Route Create a memory-enabled chat endpoint: ```typescript app/api/chat/route.ts theme={null} import { streamText } from 'ai'; import { openai } from '@ai-sdk/openai'; import { memoryTools, getContext } from '@usesatori/tools'; import { auth } from '@clerk/nextjs/server'; export async function POST(req: Request) { try { // Authenticate user const { userId } = await auth(); if (!userId) { return new Response('Unauthorized', { status: 401 }); } // Parse request const { messages } = await req.json(); if (!messages || !Array.isArray(messages) || messages.length === 0) { return new Response('Invalid request', { status: 400 }); } const userMessage = messages[messages.length - 1].content; // Configure memory const memoryConfig = { apiKey: process.env.SATORI_API_KEY!, baseUrl: process.env.SATORI_URL!, userId, }; // Create memory tools const tools = memoryTools(memoryConfig); // Pre-fetch relevant context let memoryContext = ''; try { memoryContext = await getContext( memoryConfig, userMessage, { limit: 5 } ); } catch (error) { console.error('Failed to fetch memory context:', error); // Continue without context } // Stream response with memory const result = await streamText({ model: openai('gpt-4o'), system: `You are a helpful assistant with long-term memory. ${memoryContext ? `What you know about this user:\n${memoryContext}\n` : ''} When the user shares important information, use the add_item tool to save it. Be natural and conversational. Don't explicitly mention that you're saving memories.`, messages, tools, maxSteps: 5, }); return result.toDataStreamResponse(); } catch (error) { console.error('Chat error:', error); return new Response( JSON.stringify({ error: 'Failed to process message' }), { status: 500, headers: { 'Content-Type': 'application/json' } } ); } } ``` ## Chat Page Create an interactive chat interface: ```typescript app/chat/page.tsx theme={null} 'use client'; import { useChat } from 'ai/react'; import { useUser } from '@clerk/nextjs'; import { useState } from 'react'; export default function ChatPage() { const { user, isLoaded } = useUser(); const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat(); const [showMemorySaved, setShowMemorySaved] = useState(false); if (!isLoaded) { return
Loading...
; } return (
{/* Header */}

Chat with Memory

Signed in as {user?.firstName || user?.emailAddresses[0].emailAddress}

View Memories
{/* Messages */}
{messages.length === 0 && (

Start a conversation!

Try saying: "Remember that I love TypeScript"

)} {messages.map((message) => (

{message.role === 'user' ? 'You' : 'Assistant'}

{message.content}

{/* Show tool calls */} {message.toolInvocations?.map((tool, i) => (
{tool.toolName === 'add_item' && ( 💾 Saved to memory )}
))}
))} {isLoading && (
)}
{/* Input */}
); } ``` ## Memory Dashboard Create a page to view and manage memories: ```typescript app/dashboard/page.tsx theme={null} 'use client'; import { useEffect, useState } from 'react'; import { useUser } from '@clerk/nextjs'; import { MemoryClient } from '@usesatori/tools'; interface Memory { id: string; content: string; createdAt: string; metadata?: Record; } export default function DashboardPage() { const { user, isLoaded } = useUser(); const [memories, setMemories] = useState([]); const [loading, setLoading] = useState(true); const [searchQuery, setSearchQuery] = useState(''); useEffect(() => { if (isLoaded && user) { loadMemories(); } }, [isLoaded, user]); async function loadMemories() { try { const response = await fetch('/api/memories'); const data = await response.json(); setMemories(data.memories || []); } catch (error) { console.error('Failed to load memories:', error); } finally { setLoading(false); } } async function deleteMemory(id: string) { if (!confirm('Are you sure you want to delete this memory?')) { return; } try { await fetch(`/api/memories/${id}`, { method: 'DELETE' }); setMemories(memories.filter((m) => m.id !== id)); } catch (error) { console.error('Failed to delete memory:', error); } } async function searchMemories() { if (!searchQuery.trim()) { loadMemories(); return; } try { const response = await fetch( `/api/memories/search?q=${encodeURIComponent(searchQuery)}` ); const data = await response.json(); setMemories(data.memories || []); } catch (error) { console.error('Failed to search memories:', error); } } if (!isLoaded || loading) { return
Loading...
; } return (
{/* Header */}

Your Memories

{memories.length} {memories.length === 1 ? 'memory' : 'memories'} stored

Back to Chat
{/* Search */}
setSearchQuery(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && searchMemories()} placeholder="Search memories..." className="flex-1 p-3 border rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-600" /> {searchQuery && ( )}
{/* Memories List */}
{memories.length === 0 ? (

No memories yet

Start chatting to create some memories!

) : ( memories.map((memory) => (

{memory.content}

{new Date(memory.createdAt).toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit', })}
)) )}
); } ``` ## Memory API Routes ### Get All Memories ```typescript app/api/memories/route.ts theme={null} import { auth } from '@clerk/nextjs/server'; import { MemoryClient } from '@usesatori/tools'; export async function GET() { try { const { userId } = await auth(); if (!userId) { return new Response('Unauthorized', { status: 401 }); } const client = new MemoryClient({ apiKey: process.env.SATORI_API_KEY!, baseUrl: process.env.SATORI_URL!, userId, }); const memories = await client.getAllMemories(); return Response.json({ memories }); } catch (error) { console.error('Failed to fetch memories:', error); return new Response('Internal Server Error', { status: 500 }); } } ``` ### Search Memories ```typescript app/api/memories/search/route.ts theme={null} import { auth } from '@clerk/nextjs/server'; import { MemoryClient } from '@usesatori/tools'; export async function GET(req: Request) { try { const { userId } = await auth(); if (!userId) { return new Response('Unauthorized', { status: 401 }); } const { searchParams } = new URL(req.url); const query = searchParams.get('q'); if (!query) { return new Response('Missing query parameter', { status: 400 }); } const client = new MemoryClient({ apiKey: process.env.SATORI_API_KEY!, baseUrl: process.env.SATORI_URL!, userId, }); const memories = await client.searchMemories(query); return Response.json({ memories }); } catch (error) { console.error('Failed to search memories:', error); return new Response('Internal Server Error', { status: 500 }); } } ``` ### Delete Memory ```typescript app/api/memories/[id]/route.ts theme={null} import { auth } from '@clerk/nextjs/server'; import { MemoryClient } from '@usesatori/tools'; export async function DELETE( req: Request, { params }: { params: { id: string } } ) { try { const { userId } = await auth(); if (!userId) { return new Response('Unauthorized', { status: 401 }); } const client = new MemoryClient({ apiKey: process.env.SATORI_API_KEY!, baseUrl: process.env.SATORI_URL!, userId, }); await client.deleteMemory(params.id); return Response.json({ success: true }); } catch (error) { console.error('Failed to delete memory:', error); return new Response('Internal Server Error', { status: 500 }); } } ``` ## Running the Application ```bash theme={null} npm run dev ``` Open [http://localhost:3000](http://localhost:3000) in your browser Create an account or sign in with Clerk Try these prompts: * "Remember that I love TypeScript" * "My favorite color is blue" * "What do you know about me?" Click "View Memories" to see all stored memories ## Next Steps Deploy your application to production Explore advanced examples Learn about all available endpoints Fix common issues # Vercel AI SDK Integration Source: https://docs.usesatori.sh/guides/vercel-ai-sdk Complete guide to integrating Satori memory with the Vercel AI SDK ## Overview Satori provides first-class integration with the Vercel AI SDK through the `@usesatori/tools` package. This guide covers everything from basic setup to advanced patterns. ## Installation ```bash npm theme={null} npm install @usesatori/tools ai @ai-sdk/openai ``` ```bash pnpm theme={null} pnpm add @usesatori/tools ai @ai-sdk/openai ``` ```bash yarn theme={null} yarn add @usesatori/tools ai @ai-sdk/openai ``` ## Basic Integration ### Step 1: Create Memory Tools The `memoryTools()` function creates AI SDK-compatible tools that the LLM can use to manage memories: ```typescript theme={null} import { memoryTools, getContext } from '@usesatori/tools'; const tools = memoryTools({ apiKey: process.env.SATORI_API_KEY!, baseUrl: process.env.SATORI_URL!, userId: 'user-123', }); ``` ### Step 2: Pre-fetch Memory Context Fetch relevant memories before calling the LLM: ```typescript theme={null} const memoryContext = await getContext( { apiKey: process.env.SATORI_API_KEY!, baseUrl: process.env.SATORI_URL!, userId: 'user-123', }, userMessage, { limit: 5, threshold: 0.7 } ); ``` ### Step 3: Stream with Memory Use `streamText()` with memory tools and context: ```typescript theme={null} import { streamText } from 'ai'; import { openai } from '@ai-sdk/openai'; const result = await streamText({ model: openai('gpt-4o'), system: `You are a helpful assistant with long-term memory. What you know about this user: ${memoryContext} When the user shares important information, use the add_item tool to save it.`, messages, tools, }); return result.toDataStreamResponse(); ``` ## Complete API Route Example Here's a full Next.js API route with memory: ```typescript app/api/chat/route.ts theme={null} import { streamText } from 'ai'; import { openai } from '@ai-sdk/openai'; import { memoryTools, getContext } from '@usesatori/tools'; export async function POST(req: Request) { try { const { messages } = await req.json(); const userMessage = messages[messages.length - 1].content; // Get user ID from your auth system const session = await getSession(req); if (!session?.userId) { return new Response('Unauthorized', { status: 401 }); } // Create memory configuration const memoryConfig = { apiKey: process.env.SATORI_API_KEY!, baseUrl: process.env.SATORI_URL!, userId: session.userId, }; // Create memory tools const tools = memoryTools(memoryConfig); // Pre-fetch relevant context const memoryContext = await getContext( memoryConfig, userMessage, { limit: 5 } ); // Stream response with memory const result = await streamText({ model: openai('gpt-4o'), system: `You are a helpful assistant with long-term memory. What you know about this user: ${memoryContext} Use the add_item tool when the user: - Shares preferences or opinions - Provides personal information - Mentions important dates or events - Expresses goals or intentions Be natural and conversational. Don't explicitly mention that you're saving memories.`, messages, tools, maxSteps: 5, // Allow multiple tool calls }); return result.toDataStreamResponse(); } catch (error) { console.error('Chat error:', error); return new Response('Internal Server Error', { status: 500 }); } } ``` Set `maxSteps: 5` to allow the LLM to make multiple tool calls in a single response (e.g., search and then save). ## Available Tools The `memoryTools()` function provides two tools: ### add\_item Saves information to memory. The LLM calls this automatically when it detects important information. ```typescript theme={null} // LLM automatically calls this { tool: 'add_item', parameters: { memory: 'User prefers TypeScript over JavaScript for type safety' } } ``` The information to save. Should be a complete, self-contained statement. Optional metadata for categorization: ```typescript theme={null} { memory: 'User prefers TypeScript', metadata: { category: 'preferences', tags: ['programming', 'languages'] } } ``` ### delete\_memory Removes a specific memory by ID. ```typescript theme={null} // LLM calls this when user asks to forget something { tool: 'delete_memory', parameters: { memoryId: 'uuid-of-memory' } } ``` The UUID of the memory to delete. The LLM can get this from the context. ## Advanced Patterns ### Pattern 1: Conditional Context Injection Only inject context when relevant: ```typescript theme={null} const userMessage = messages[messages.length - 1].content; // Check if message might need memory context const needsContext = /what|remember|know|told|said/i.test(userMessage); let memoryContext = ''; if (needsContext) { memoryContext = await getContext(config, userMessage); } const result = await streamText({ model: openai('gpt-4o'), system: `You are a helpful assistant. ${memoryContext ? `\nWhat you know:\n${memoryContext}` : ''}`, messages, tools, }); ``` ### Pattern 2: Category-Based Memory Use metadata to organize memories by category: ```typescript theme={null} const tools = { add_preference: tool({ description: 'Save a user preference', parameters: z.object({ preference: z.string(), }), execute: async ({ preference }) => { await client.addMemory(preference, { metadata: { category: 'preference' }, }); return 'Preference saved'; }, }), add_fact: tool({ description: 'Save a factual piece of information', parameters: z.object({ fact: z.string(), }), execute: async ({ fact }) => { await client.addMemory(fact, { metadata: { category: 'fact' }, }); return 'Fact saved'; }, }), }; ``` ### Pattern 3: Streaming with Tool Call Feedback Show users when memories are being saved: ```typescript theme={null} 'use client'; import { useChat } from 'ai/react'; export default function ChatPage() { const { messages, input, handleInputChange, handleSubmit } = useChat({ onToolCall: ({ toolCall }) => { if (toolCall.toolName === 'add_item') { console.log('Saving memory:', toolCall.args.memory); // Show toast notification } }, }); return (
{messages.map((message) => (

{message.content}

{/* Show tool calls */} {message.toolInvocations?.map((tool, i) => (
{tool.toolName === 'add_item' && ( 💾 Saved to memory )}
))}
))}
); } ``` ### Pattern 4: Multi-Step Reasoning Allow the LLM to search before responding: ```typescript theme={null} const tools = { ...memoryTools(config), search_memory: tool({ description: 'Search for relevant memories', parameters: z.object({ query: z.string().describe('What to search for'), }), execute: async ({ query }) => { const memories = await client.searchMemories(query, { limit: 3 }); return memories.map(m => m.content).join('\n'); }, }), }; const result = await streamText({ model: openai('gpt-4o'), system: `You are a helpful assistant with memory. Use search_memory to find relevant information before answering questions. Use add_item to save important new information.`, messages, tools, maxSteps: 5, // Allow search → respond → save flow }); ``` This pattern is less reliable than pre-fetching context. The LLM may not always call `search_memory` when needed. ## Error Handling Handle errors gracefully in production: ```typescript theme={null} export async function POST(req: Request) { try { const { messages } = await req.json(); // Validate input if (!messages || !Array.isArray(messages)) { return new Response('Invalid request', { status: 400 }); } const memoryConfig = { apiKey: process.env.SATORI_API_KEY!, baseUrl: process.env.SATORI_URL!, userId: session.userId, }; // Try to fetch context, but don't fail if it errors let memoryContext = ''; try { memoryContext = await getContext( memoryConfig, messages[messages.length - 1].content ); } catch (error) { console.error('Failed to fetch memory context:', error); // Continue without context } const tools = memoryTools(memoryConfig); const result = await streamText({ model: openai('gpt-4o'), system: `You are a helpful assistant. ${memoryContext ? `\nWhat you know:\n${memoryContext}` : ''}`, messages, tools, }); return result.toDataStreamResponse(); } catch (error) { console.error('Chat error:', error); // Return user-friendly error return new Response( JSON.stringify({ error: 'Failed to process message' }), { status: 500, headers: { 'Content-Type': 'application/json' } } ); } } ``` ## Testing Test your memory integration: ```typescript theme={null} import { POST } from './route'; describe('Chat API with Memory', () => { it('saves memories when user shares information', async () => { const request = new Request('http://localhost:3000/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ messages: [ { role: 'user', content: 'Remember that I love TypeScript' } ], }), }); const response = await POST(request); expect(response.status).toBe(200); // Verify memory was saved const memories = await client.searchMemories('TypeScript'); expect(memories).toHaveLength(1); expect(memories[0].content).toContain('TypeScript'); }); }); ``` ## Performance Optimization ```typescript theme={null} const embeddingCache = new Map(); async function getCachedContext(query: string) { if (embeddingCache.has(query)) { return embeddingCache.get(query)!; } const context = await getContext(config, query); embeddingCache.set(query, context); return context; } ``` ```typescript theme={null} // Fetch context and start LLM call in parallel const [memoryContext] = await Promise.all([ getContext(config, userMessage), // Other async operations ]); ``` ```typescript theme={null} // Fetch fewer memories for faster responses const context = await getContext(config, userMessage, { limit: 3, // Instead of default 10 }); ``` ## Next Steps Use MemoryClient for custom integrations Build a complete Next.js app with memory Explore the complete API See complete implementations # AI with Long-Term Memory Source: https://docs.usesatori.sh/index Give your AI applications persistent memory that remembers user preferences, context, and conversations across sessions ## What is Satori? Satori is a memory layer for AI applications that enables your agents to remember information across conversations. Built on semantic search with vector embeddings, Satori automatically stores and retrieves relevant context to make your AI more personalized and context-aware. Get your first memory working in under 5 minutes with the Vercel AI SDK Explore the complete tRPC API for memory operations Learn about embeddings, semantic search, and memory isolation See real-world implementations with complete code ## Key Features Find relevant memories using natural language queries powered by vector embeddings Built-in tenant isolation ensures your users' data stays completely separate Drop-in tools for Vercel AI SDK with automatic memory management End-to-end type safety with tRPC from your backend to frontend ## How It Works ```mermaid theme={null} sequenceDiagram participant User participant App participant LLM participant Satori User->>App: "Remember I like TypeScript" App->>Satori: Search relevant memories Satori-->>App: Return context App->>LLM: Message + memory context LLM->>Satori: add_item tool call Satori-->>LLM: Memory saved LLM-->>User: "Got it! I'll remember that." ``` Your application receives input from the user Satori searches for relevant memories using semantic similarity The AI model receives the message along with relevant memories The LLM uses tools to save important information for future conversations ## Installation ```bash npm theme={null} npm install @usesatori/tools ai ``` ```bash pnpm theme={null} pnpm add @usesatori/tools ai ``` ```bash yarn theme={null} yarn add @usesatori/tools ai ``` ## Quick Example ```typescript theme={null} import { streamText } from 'ai'; import { openai } from '@ai-sdk/openai'; import { memoryTools, getContext } from '@usesatori/tools'; // Create memory tools for a user const tools = memoryTools({ apiKey: process.env.SATORI_API_KEY!, baseUrl: 'https://api.usesatori.sh', userId: 'user-123', }); // Pre-fetch relevant memories const context = await getContext( { apiKey: process.env.SATORI_API_KEY!, baseUrl: 'https://api.usesatori.sh', userId: 'user-123', }, userMessage ); // Stream with memory const result = await streamText({ model: openai('gpt-4o'), system: `You are a helpful assistant with memory. What you know about this user: ${context} Use add_item to save important information.`, messages, tools, }); ``` The LLM automatically decides when to save memories using the `add_item` tool. You don't need to manually parse or store information. ## Use Cases Build assistants that remember user preferences, work context, and conversation history to provide increasingly personalized responses over time. Enable support agents to access previous interactions, known issues, and customer preferences without asking repetitive questions. Create tutors that track student progress, learning style preferences, and areas of difficulty to adapt teaching approaches. Build tools that remember project context, team preferences, and historical decisions to provide better recommendations. ## Why Satori? Works seamlessly with Vercel AI SDK and other frameworks Built on PostgreSQL with pgvector for reliable, scalable storage Type-safe API, great DX, and comprehensive documentation ## Next Steps Get your first memory working in 5 minutes Understand how memory and embeddings work See complete implementations you can copy Dive into the complete API documentation # Quickstart Source: https://docs.usesatori.sh/quickstart Add persistent memory to your AI application in under 5 minutes ## Prerequisites Before you begin, make sure you have: * Node.js 18+ installed * An OpenAI API key ([get one here](https://platform.openai.com/api-keys)) * A Satori API key ([sign up at satori.dev](https://satori.dev)) ## Installation Install the Satori tools package along with the Vercel AI SDK: ```bash npm theme={null} npm install @usesatori/tools ai @ai-sdk/openai ``` ```bash pnpm theme={null} pnpm add @usesatori/tools ai @ai-sdk/openai ``` ```bash yarn theme={null} yarn add @usesatori/tools ai @ai-sdk/openai ``` Run `npm list @usesatori/tools` to verify the installation was successful. Create a `.env.local` file in your project root with your API keys: ```bash .env.local theme={null} OPENAI_API_KEY=sk-... SATORI_API_KEY=sk_satori_... SATORI_URL=https://api.usesatori.sh ``` Never commit your API keys to version control. Add `.env.local` to your `.gitignore` file. Create a new file `app/api/chat/route.ts` for your chat endpoint: ```typescript app/api/chat/route.ts theme={null} import { streamText } from 'ai'; import { openai } from '@ai-sdk/openai'; import { memoryTools, getContext } from '@usesatori/tools'; export async function POST(req: Request) { const { messages } = await req.json(); const userMessage = messages[messages.length - 1].content; // Create memory tools scoped to this user const tools = memoryTools({ apiKey: process.env.SATORI_API_KEY!, baseUrl: process.env.SATORI_URL!, userId: 'user-123', // Replace with actual user ID }); // Pre-fetch relevant memories for context const memoryContext = await getContext( { apiKey: process.env.SATORI_API_KEY!, baseUrl: process.env.SATORI_URL!, userId: 'user-123', }, userMessage, { limit: 5 } ); // Stream response with memory const result = await streamText({ model: openai('gpt-4o'), system: `You are a helpful assistant with long-term memory. What you know about this user: ${memoryContext} When the user shares important information, use the add_item tool to save it. When asked what you remember, reference the context above.`, messages, tools, }); return result.toDataStreamResponse(); } ``` Replace `'user-123'` with your actual user identifier. Each user gets their own isolated memory space. Create a simple chat UI in `app/page.tsx`: ```typescript app/page.tsx theme={null} 'use client'; import { useChat } from 'ai/react'; export default function ChatPage() { const { messages, input, handleInputChange, handleSubmit } = useChat(); return (
{messages.map((message) => (

{message.role === 'user' ? 'You' : 'Assistant'}

{message.content}

))}
); } ```
Run your Next.js development server: ```bash theme={null} npm run dev ``` Visit [http://localhost:3000](http://localhost:3000) to see your chat interface. Your application should now be running with memory-enabled chat!
## Test Your Memory Try these example conversations to see memory in action: **You:** "Remember that I prefer TypeScript over JavaScript" **Assistant:** "Got it! I'll remember that you prefer TypeScript over JavaScript." The LLM automatically calls the `add_item` tool to save this information. **You:** "My name is Alex and I'm a software engineer" **Assistant:** "Nice to meet you, Alex! I'll remember that you're a software engineer." **You:** "What do you know about me?" **Assistant:** "Based on what you've told me, I know that your name is Alex, you're a software engineer, and you prefer TypeScript over JavaScript." **You:** "Actually, I've started learning Rust and really enjoying it" **Assistant:** "That's great! I'll remember that you're learning Rust and enjoying it." ## How It Works Here's what happens behind the scenes: 1. **User sends a message** → Your API route receives the message 2. **Fetch relevant context** → `getContext()` searches for relevant memories using semantic similarity 3. **Inject into system prompt** → Memories are added to the system prompt as context 4. **LLM processes** → The model sees both the message and relevant memories 5. **Auto-save important info** → The LLM calls `add_item` tool when it detects important information 6. **Stream response** → The response streams back to the user The LLM decides when to save memories based on the conversation context. You don't need to manually parse or store information. ## Understanding User Isolation Each `userId` you provide gets completely isolated memory storage: ```typescript theme={null} // User Alice's memories const aliceTools = memoryTools({ apiKey: process.env.SATORI_API_KEY!, baseUrl: process.env.SATORI_URL!, userId: 'alice', }); // User Bob's memories (completely separate) const bobTools = memoryTools({ apiKey: process.env.SATORI_API_KEY!, baseUrl: process.env.SATORI_URL!, userId: 'bob', }); ``` Always use unique, consistent user identifiers. Never share the same `userId` across different users. ## Next Steps Understand embeddings, semantic search, and memory lifecycle Learn advanced patterns like streaming, error handling, and optimization Use the MemoryClient directly for custom integrations Explore the complete API documentation ## Troubleshooting Make sure your API key is correctly set in your `.env.local` file and that you've restarted your development server after adding it. ```bash theme={null} # Verify your environment variables are loaded console.log('API Key:', process.env.SATORI_API_KEY?.substring(0, 10) + '...'); ``` Check that: 1. The `tools` are passed to `streamText()` 2. Your system prompt instructs the LLM to use the `add_item` tool 3. The conversation contains information worth remembering You can also manually test memory storage using the direct client. Verify that `getContext()` is being called before `streamText()` and that the result is included in your system prompt. ```typescript theme={null} console.log('Memory context:', memoryContext); ``` Check out our comprehensive troubleshooting guide # Troubleshooting Source: https://docs.usesatori.sh/troubleshooting Common issues and solutions for Satori integration ## Common Issues **Symptoms:** * `Unauthorized - Invalid API key` error * 401 status code on API requests **Solutions:** 1. **Verify your API key is correct:** ```typescript theme={null} console.log('API Key:', process.env.SATORI_API_KEY?.substring(0, 15) + '...'); ``` 2. **Check environment variables are loaded:** ```bash theme={null} # Make sure .env.local exists cat .env.local # Restart your development server after adding env vars npm run dev ``` 3. **Verify the key format:** ```typescript theme={null} // Should start with 'sk_satori_' if (!process.env.SATORI_API_KEY?.startsWith('sk_satori_')) { console.error('Invalid API key format'); } ``` 4. **Check if the key is revoked:** * Log into your dashboard * Go to API Keys * Verify the key status is "Active" **Symptoms:** * LLM doesn't call `add_item` tool * No memories appear in database **Solutions:** 1. **Verify tools are passed to streamText:** ```typescript theme={null} const tools = memoryTools(config); const result = await streamText({ model: openai('gpt-4o'), messages, tools, // ← Make sure this is included }); ``` 2. **Check system prompt instructs LLM to save:** ```typescript theme={null} system: `You are a helpful assistant with memory. When the user shares important information, use the add_item tool to save it. Important information includes: - Personal preferences - Personal details - Goals and intentions` ``` 3. **Verify maxSteps is set:** ```typescript theme={null} const result = await streamText({ model: openai('gpt-4o'), messages, tools, maxSteps: 5, // ← Allow tool calls }); ``` 4. **Test with explicit command:** ``` User: "Remember that I love TypeScript" ``` If this doesn't work, check your API logs for errors. **Symptoms:** * LLM doesn't reference saved memories * Responses don't seem personalized **Solutions:** 1. **Verify context is fetched:** ```typescript theme={null} const context = await getContext(config, userMessage); console.log('Memory context:', context); // Should output something like: // "- User prefers TypeScript // - User loves hiking" ``` 2. **Check context is in system prompt:** ```typescript theme={null} system: `You are a helpful assistant. What you know about this user: ${memoryContext} // ← Make sure this is included Use this information to personalize responses.` ``` 3. **Verify memories exist:** ```typescript theme={null} const client = new MemoryClient(config); const all = await client.getAllMemories(); console.log('Total memories:', all.length); ``` 4. **Check search threshold:** ```typescript theme={null} // Lower threshold for broader matches const context = await getContext(config, userMessage, { threshold: 0.6, // Default is 0.7 }); ``` **Symptoms:** * `Too Many Requests` error * 429 status code **Solutions:** 1. **Implement exponential backoff:** ```typescript theme={null} async function retryWithBackoff(fn: () => Promise, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { try { return await fn(); } catch (error) { if (error.message.includes('rate limit') && i < maxRetries - 1) { const delay = Math.pow(2, i) * 1000; await new Promise(resolve => setTimeout(resolve, delay)); } else { throw error; } } } } // Usage await retryWithBackoff(() => client.addMemory('content')); ``` 2. **Batch operations:** ```typescript theme={null} // Instead of multiple individual calls const memories = ['memory1', 'memory2', 'memory3']; await Promise.all(memories.map(m => client.addMemory(m))); ``` 3. **Cache context fetches:** ```typescript theme={null} const contextCache = new Map(); async function getCachedContext(query: string) { if (contextCache.has(query)) { return contextCache.get(query)!; } const context = await getContext(config, query); contextCache.set(query, context); return context; } ``` 4. **Contact support for higher limits** **Symptoms:** * API calls take several seconds * Chat feels sluggish **Solutions:** 1. **Reduce context limit:** ```typescript theme={null} // Fetch fewer memories const context = await getContext(config, userMessage, { limit: 3, // Instead of 10 }); ``` 2. **Parallel operations:** ```typescript theme={null} // Fetch context and start LLM call in parallel const [memoryContext] = await Promise.all([ getContext(config, userMessage), // Other async operations ]); ``` 3. **Cache embeddings for common queries:** ```typescript theme={null} const embeddingCache = new Map(); async function getCachedContext(query: string) { const cacheKey = query.toLowerCase().trim(); if (embeddingCache.has(cacheKey)) { return embeddingCache.get(cacheKey); } const context = await getContext(config, query); embeddingCache.set(cacheKey, context); // Clear cache after 5 minutes setTimeout(() => embeddingCache.delete(cacheKey), 5 * 60 * 1000); return context; } ``` 4. **Use streaming:** ```typescript theme={null} // Stream responses for better perceived performance const result = await streamText({ model: openai('gpt-4o'), messages, tools, }); return result.toDataStreamResponse(); ``` **Symptoms:** * Type errors in IDE * Build fails with type errors **Solutions:** 1. **Install type definitions:** ```bash theme={null} npm install --save-dev @types/node ``` 2. **Import types correctly:** ```typescript theme={null} import type { Memory, MemoryWithSimilarity } from '@usesatori/tools'; ``` 3. **Check tsconfig.json:** ```json theme={null} { "compilerOptions": { "strict": true, "esModuleInterop": true, "skipLibCheck": true, "moduleResolution": "bundler" } } ``` 4. **Rebuild packages:** ```bash theme={null} cd packages/js-tools npm run build ``` **Symptoms:** * `CORS policy` error in browser console * Requests fail from frontend **Solutions:** 1. **Never call Satori API from frontend:** ```typescript theme={null} // ❌ Don't do this in client components 'use client'; const client = new MemoryClient({ apiKey: '...' }); // API key exposed! // ✅ Do this instead - use API routes export async function POST(req: Request) { // Server-side only const client = new MemoryClient({ apiKey: process.env.SATORI_API_KEY!, }); } ``` 2. **Use server-side API routes:** ```typescript theme={null} // app/api/memories/route.ts export async function GET() { const client = new MemoryClient({ apiKey: process.env.SATORI_API_KEY!, baseUrl: process.env.SATORI_URL!, userId: 'user-123', }); const memories = await client.getAllMemories(); return Response.json({ memories }); } ``` **Symptoms:** * `Memory not found` when deleting * 404 errors **Solutions:** 1. **Verify memory ID:** ```typescript theme={null} // Make sure you have the correct UUID console.log('Deleting memory:', memoryId); await client.deleteMemory(memoryId); ``` 2. **Check memory belongs to user:** ```typescript theme={null} // Memory IDs are scoped per user const memories = await client.getAllMemories(); const exists = memories.some(m => m.id === memoryId); if (!exists) { console.error('Memory not found for this user'); } ``` 3. **Handle errors gracefully:** ```typescript theme={null} try { await client.deleteMemory(memoryId); } catch (error) { if (error.message.includes('Not Found')) { console.log('Memory already deleted or does not exist'); } else { throw error; } } ``` **Symptoms:** * Same information saved multiple times * Too many similar memories **Solutions:** 1. **Check before saving:** ```typescript theme={null} // Search for similar memories first const existing = await client.searchMemories(content, { threshold: 0.9, // High threshold for near-duplicates limit: 1, }); if (existing.length === 0) { await client.addMemory(content); } else { console.log('Similar memory already exists'); } ``` 2. **Update system prompt:** ```typescript theme={null} system: `Before saving a memory, consider if similar information already exists. Only save truly new or updated information.` ``` 3. **Periodic cleanup:** ```typescript theme={null} // Find and merge duplicate memories const memories = await client.getAllMemories(); for (let i = 0; i < memories.length; i++) { for (let j = i + 1; j < memories.length; j++) { const similarity = await calculateSimilarity( memories[i].content, memories[j].content ); if (similarity > 0.95) { // Keep the newer one, delete the older await client.deleteMemory(memories[i].id); break; } } } ``` ## Debugging Tips ### Enable Verbose Logging ```typescript theme={null} // Add detailed logging console.log('=== Memory Debug Info ==='); console.log('User ID:', userId); console.log('API Key:', process.env.SATORI_API_KEY?.substring(0, 15) + '...'); console.log('Base URL:', process.env.SATORI_URL); const context = await getContext(config, userMessage); console.log('Context fetched:', context); console.log('Context length:', context.length); const result = await streamText({ model: openai('gpt-4o'), system: `...${context}`, messages, tools, onFinish: (result) => { console.log('Tool calls:', result.toolCalls); console.log('Finish reason:', result.finishReason); }, }); ``` ### Test Memory Operations Directly ```typescript theme={null} // Test script to verify memory operations import { MemoryClient } from '@usesatori/tools'; async function testMemory() { const client = new MemoryClient({ apiKey: process.env.SATORI_API_KEY!, baseUrl: process.env.SATORI_URL!, userId: 'test-user', }); console.log('1. Adding memory...'); const memory = await client.addMemory('Test memory content'); console.log('✓ Memory added:', memory.id); console.log('2. Searching memories...'); const results = await client.searchMemories('test'); console.log('✓ Found', results.length, 'memories'); console.log('3. Getting all memories...'); const all = await client.getAllMemories(); console.log('✓ Total memories:', all.length); console.log('4. Deleting memory...'); await client.deleteMemory(memory.id); console.log('✓ Memory deleted'); console.log('All tests passed!'); } testMemory().catch(console.error); ``` ### Check Network Requests ```typescript theme={null} // Log all fetch requests const originalFetch = global.fetch; global.fetch = async (...args) => { console.log('Fetch:', args[0]); const response = await originalFetch(...args); console.log('Status:', response.status); return response; }; ``` ## Getting Help If you're still experiencing issues: Review the quickstart guide Report bugs or request features Get help from the community Contact our support team ## Useful Resources * [API Reference](/api-reference/introduction) * [Integration Guides](/guides/vercel-ai-sdk) * [Core Concepts](/concepts/how-it-works) * [Examples](/examples/chat-with-memory)