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
Satori server URL (e.g., https://api.satori.dev)
User identifier for memory isolation
Methods
addMemory(content, options?)
Saves a new memory.
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.
Natural language search query
Maximum number of results (1-100)
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.
Maximum number of memories to return
Returns: Promise<Memory[]>
deleteMemory(id)
Deletes a specific memory.
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\n User 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' );
Use descriptive memory content
Store complete, self-contained information: // ✅ Good
await client . addMemory ( 'User prefers dark mode in all applications' );
// ❌ Bad
await client . addMemory ( 'dark mode' ); // Too vague
Add metadata for organization
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