Skip to main content

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

npm install @satori/tools

Basic Usage

Creating a Client

import { MemoryClient } from '@satori/tools';

const client = new MemoryClient({
  apiKey: process.env.SATORI_API_KEY!,
  baseUrl: process.env.SATORI_URL!,
  userId: 'user-123',
});

Adding Memories

const memory = await client.addMemory(
  'User prefers TypeScript over JavaScript'
);

console.log('Saved memory:', memory.id);
With metadata:
const memory = await client.addMemory(
  'User prefers dark mode in all applications',
  {
    metadata: {
      category: 'preference',
      tags: ['ui', 'theme'],
      importance: 'high',
    },
  }
);

Searching Memories

const memories = await client.searchMemories('programming languages');

memories.forEach((memory) => {
  console.log(`${memory.content} (similarity: ${memory.similarity})`);
});
With options:
const memories = await client.searchMemories('preferences', {
  limit: 5,
  threshold: 0.8, // Only very similar memories
});

Getting All Memories

const allMemories = await client.getAllMemories();

console.log(`Total memories: ${allMemories.length}`);
With limit:
const recentMemories = await client.getAllMemories({ limit: 10 });

Deleting Memories

await client.deleteMemory('memory-uuid');
console.log('Memory deleted');

Complete API Reference

Constructor

apiKey
string
required
Your Satori API key
baseUrl
string
required
Satori server URL (e.g., https://api.satori.dev)
userId
string
required
User identifier for memory isolation

Methods

addMemory(content, options?)

Saves a new memory.
content
string
required
The text content to save
options.metadata
object
Optional metadata for organization
{
  category: 'preference',
  tags: ['important'],
  customField: 'value'
}
Returns: Promise<Memory>
{
  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.
query
string
required
Natural language search query
options.limit
number
default:"10"
Maximum number of results (1-100)
options.threshold
number
default:"0.7"
Minimum similarity score (0-1)
Returns: Promise<MemoryWithSimilarity[]>
[
  {
    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.
options.limit
number
default:"100"
Maximum number of memories to return
Returns: Promise<Memory[]>

deleteMemory(id)

Deletes a specific memory.
id
string
required
UUID of the memory to delete
Returns: Promise<void>

Advanced Use Cases

Building an Admin Dashboard

import { MemoryClient } from '@satori/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

// Cron job to clean up old memories
import { MemoryClient } from '@satori/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

// 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

import { MemoryClient } from '@satori/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:
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:
import type { 
  Memory, 
  MemoryWithSimilarity,
  AddMemoryOptions,
  SearchOptions 
} from '@satori/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:
import { MemoryClient } from '@satori/tools';

// Mock the client
jest.mock('@satori/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:
// ✅ 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:
// ✅ 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:
await client.addMemory('User prefers TypeScript', {
  metadata: {
    category: 'preference',
    subcategory: 'programming',
    tags: ['language', 'typescript'],
    importance: 'high',
  },
});
Always wrap operations in try-catch blocks:
try {
  await client.addMemory(content);
} catch (error) {
  console.error('Failed to save memory:', error);
  // Fallback behavior
}

Next Steps