Skip to main content

memory.add

Saves a new memory with automatic embedding generation for semantic search.

Parameters

userId
string
required
User identifier for memory isolation. Must be a stable, unique ID for each user.
userId: 'user-123'
content
string
required
The text content to save. Should be a complete, self-contained statement.
content: 'User prefers TypeScript over JavaScript for type safety'
Write memories as complete sentences that make sense out of context.
metadata
object
Optional metadata for categorization and filtering.
metadata: {
  category: 'preference',
  tags: ['programming', 'languages'],
  importance: 'high',
  customField: 'value'
}

Response

id
string
required
Unique identifier (UUID) for the memory
content
string
required
The saved text content
userId
string
required
User identifier
clerkUserId
string
required
Tenant identifier (API key owner)
metadata
object
Custom metadata object
createdAt
string
required
ISO 8601 timestamp of creation
updatedAt
string
required
ISO 8601 timestamp of last update

Examples

import { createTRPCClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from '@satori/server';

const client = createTRPCClient<AppRouter>({
  links: [
    httpBatchLink({
      url: 'https://api.satori.dev/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);
{
  "userId": "user-123",
  "content": "User prefers TypeScript over JavaScript for type safety",
  "metadata": {
    "category": "preference",
    "tags": ["programming", "languages"],
    "importance": "high"
  }
}
{
  "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"
}

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

// ✅ 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',
});
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',
  },
});
// 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

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);
  }
}