skill: rewrite supermemory skill for the v5 API and 5.0 SDKs

Namespace in the path, client.add/search/list with namespace first, documents/memories/namespaces/profiles/connectors groups, v5 error types. No v3/v4 names or routes remain in the skill.
This commit is contained in:
Mahesh Sanikommu 2026-10-06 11:39:58 -07:00
parent 58ef43ba5c
commit 4caf812253
7 changed files with 666 additions and 782 deletions

View file

@ -115,17 +115,17 @@ Ready-to-use code snippets for TypeScript and Python showing the basic workflow:
### 2. Complete SDK Documentation
Full reference for all SDK methods:
- `add()` - Store memories
- `profile()` - Retrieve user context
- `search.memories()` - Semantic search
- `documents.list()` - List documents
- `documents.delete()` - Delete documents
- `add(namespace, …)` - Store memories
- `profile(namespace)` - Retrieve user context
- `search(namespace, …)` - Semantic search
- `list(namespace, "documents", …)` - List documents
- `documents.delete(namespace, { ids })` - Delete documents
### 3. REST API Reference
Complete endpoint documentation with cURL examples:
- `POST /v3/documents` - Add documents
- `POST /v3/search` - Search memories
- `POST /v4/memories` - Create direct memories
- `POST /ns/{namespace}/document` - Add documents
- `POST /ns/{namespace}/search` - Search memories
- `POST /ns/{namespace}/profile` - Get a user profile
### 4. Architecture Deep Dive
Understand how Supermemory works:
@ -149,10 +149,10 @@ Understand how Supermemory works:
The skill emphasizes:
- **Container Tags**: Proper isolation and organization
- **Namespaces**: Proper isolation and organization
- **Metadata**: Rich metadata for advanced filtering
- **Thresholds**: Balancing precision and recall
- **Static vs Dynamic Memories**: When to mark memories as permanent
- **Static vs Dynamic Memories**: How profiles split long-lived facts from recent context
- **Error Handling**: Graceful handling of API errors
- **Integration Patterns**: Works with Vercel AI SDK, LangChain, CrewAI, etc.

View file

@ -46,25 +46,23 @@ const client = new Supermemory({
apiKey: process.env.SUPERMEMORY_API_KEY
});
// 1. Retrieve personalized context
const context = await client.profile({
containerTag: "user_123",
q: "What are my preferences?"
});
// 1. Retrieve personalized context (profile takes no query; search separately)
const [{ profile }, { results }] = await Promise.all([
client.profile("user_123"),
client.search("user_123", { query: "What are my preferences?", searchMode: "memories" }),
]);
// 2. Enrich your prompt with context
const profileText = [
...context.profile.static,
...context.profile.dynamic,
].join('\n');
const relevantMemories = JSON.stringify(context.searchResults?.results ?? []);
const profileText = [...profile.static, ...profile.dynamic]
.map((m) => m.memory)
.join('\n');
const relevantMemories = results.map((r) => r.memory ?? r.chunk).join('\n');
const systemMessage = `User Profile:\n${profileText}\n\nRelevant Memories:\n${relevantMemories}`;
// 3. Store new memories after conversation
const conversationText = "User: I prefer dark mode.\nAssistant: I'll remember that.";
await client.add({
await client.add("user_123", {
content: conversationText,
containerTag: "user_123",
metadata: { type: "conversation" }
});
```
@ -77,26 +75,20 @@ from supermemory import Supermemory
client = Supermemory(api_key=os.environ["SUPERMEMORY_API_KEY"])
# Retrieve context
context = client.profile(
container_tag="user_123",
q="What are my preferences?"
)
# Retrieve context: the namespace comes first, then keyword arguments
context = client.profile("user_123")
search = client.search("user_123", query="What are my preferences?", search_mode="memories")
profile_text = "\n".join(
(context.profile.static or []) + (context.profile.dynamic or [])
)
relevant_memories = "\n".join(
result.memory
for result in (context.search_results.results if context.search_results else [])
if result.memory
m.memory for m in context.profile.static + context.profile.dynamic
)
relevant_memories = "\n".join(r.memory for r in search.results if r.memory)
# Add memories
conversation_text = "User: I prefer dark mode.\nAssistant: I'll remember that."
client.add(
"user_123",
content=conversation_text,
container_tag="user_123",
metadata={"type": "conversation"}
)
```
@ -125,7 +117,6 @@ Supermemory builds a **living knowledge graph** rather than static document stor
- **TypeScript/JavaScript**: `npm install supermemory` ([npm](https://www.npmjs.com/package/supermemory))
- **Python**: `pip install supermemory` ([PyPI](https://pypi.org/project/supermemory/))
- **TypeScript agent tools/middleware**: `npm install @supermemory/tools`
- **Python OpenAI tools/middleware**: `pip install supermemory-openai-sdk`
Discover all available SDKs and community integrations at [supermemory.ai/docs](https://supermemory.ai/docs)
3. **Set Environment Variable**: `export SUPERMEMORY_API_KEY="your_key"`
@ -140,20 +131,22 @@ Supermemory supports two complementary integration styles:
| Path | When to use | Packages |
|------|-------------|----------|
| **Tools** | Model explicitly decides when to search, add, list, or forget | `@supermemory/tools/ai-sdk` (TypeScript), `supermemory-openai-sdk` (Python) |
| **Middleware** | Auto-inject profile context before each request and save conversations after | `@supermemory/tools/ai-sdk` (Vercel AI SDK), `@supermemory/tools/openai` (OpenAI), `supermemory-openai-sdk` (Python) |
| **Tools** | Model explicitly decides when to search, add, list, or forget | `@supermemory/tools/ai-sdk` (Vercel AI SDK), `@supermemory/tools/openai` (OpenAI) |
| **Middleware** | Auto-inject profile context before each request and save conversations after | `@supermemory/tools/ai-sdk` (Vercel AI SDK), `@supermemory/tools/openai` (OpenAI) |
Both read and write one `namespace` (usually one per end user). Middleware also takes an `id` that keeps one conversation in one document.
**Tools (7 canonical operations):**
| Tool | Use when |
|------|----------|
| `searchMemories` / `search_memories` | Proactive hybrid recall before answering when user-specific context could help — not only when explicitly asked. Hybrid returns both memory entries and source-document chunks. |
| `addMemory` / `add_memory` | Store a single generalizable fact the user stated |
| `getProfile` / `get_profile` | Load static + dynamic profile; pass `query` to scope search results to the current topic |
| `documentList` / `document_list` | Browse stored **source documents** (conversations, URLs, files); returns **document IDs** |
| `documentAdd` / `document_add` | Ingest raw content (text blob, conversation transcript, URL, notes) for **background processing** — memories are extracted automatically; use for substantial content, not single facts (`addMemory`) |
| `documentDelete` / `document_delete` | Permanently delete a source document and soft-forget memories extracted from it |
| `memoryForget` / `memory_forget` | **Soft delete** one learned profile fact by memory ID or exact content match |
| `searchMemories` | Proactive hybrid recall before answering when user-specific context could help — not only when explicitly asked. Hybrid returns both memory entries and source-document chunks. |
| `addMemory` | Store a single generalizable fact the user stated |
| `getProfile` | Load static + dynamic profile; pass `query` to also get `searchResults` for the current topic |
| `documentList` | Browse stored **source documents** (conversations, URLs, files); returns **document IDs** |
| `documentAdd` | Ingest raw content (text blob, conversation transcript, URL, notes) for **background processing** — memories are extracted automatically; use for substantial content, not single facts (`addMemory`) |
| `documentDelete` | Permanently delete a source document and soft-forget memories extracted from it |
| `memoryForget` | **Soft delete** one learned profile fact by memory ID or exact content match |
### Removing information — three different mechanisms
@ -161,11 +154,11 @@ Agents must pick the right removal path:
| User intent | Tool | What it removes | ID source |
|-------------|------|-----------------|-----------|
| "Forget that I like tea" / correct a wrong fact | `memoryForget` | One extracted profile memory (soft delete) | Memory ID from a search result that contains `memory`, or query-backed `getProfile.searchResults` / `get_profile.search_results` |
| "Forget that I like tea" / correct a wrong fact | `memoryForget` | One extracted profile memory (soft delete) | Memory ID from a `getProfile` entry, or from a search result that contains `memory` |
| "Delete that conversation" / remove a whole file or URL | `documentDelete` | Source document permanently; extracted memories are soft-forgotten | `documentId` from `documentList` |
| User is vague ("forget what you know about my job") | `searchMemories` first → then `memoryForget` | Same as memoryForget | Search first, then use `memoryId` |
**Do not confuse IDs:** `memoryId` ≠ `documentId`. In hybrid results, only an item containing `memory` has a forgettable memory ID; an item containing `chunk` has a chunk ID. Static/dynamic profile entries are plain text, so use query-backed profile search results when you need an ID.
**Do not confuse IDs:** `memoryId` ≠ `documentId`. In hybrid results, only an item containing `memory` has a forgettable memory ID; an item containing `chunk` has a chunk ID. Static/dynamic profile entries are `{ id, memory }`, so their `id` is a forgettable memory ID.
**Soft vs hard delete:** `memoryForget` hides a fact from profile/search but leaves source documents. `documentDelete` permanently removes the underlying source and soft-forgets its extracted memories.
@ -174,35 +167,22 @@ Agents must pick the right removal path:
- **`search()` / `searchMemories`**: Targeted recall — use proactively before answering when memory could improve the response, not only when the user says "search" or "what do you remember". Hybrid mode returns both extracted memories and source-document chunks.
- **`documents.*`**: Source management — list, add, or delete documents throughout their lifecycle.
With multiple configured container tags, `searchMemories`, `getProfile`, and `memoryForget` use the first tag because v4 memory operations are single-space. Add, list, and delete operations use the broader configured scope where supported.
Every tool reads and writes only the one configured `namespace`. A v5 document lives in exactly one namespace, so there is no multi-namespace tool config; create one tool set per namespace if an agent needs several.
**TypeScript (Vercel AI SDK):**
```typescript
import { supermemoryTools } from "@supermemory/tools/ai-sdk"
const tools = supermemoryTools(process.env.SUPERMEMORY_API_KEY!, {
containerTags: ["user_123"],
namespace: "user_123",
})
```
The aggregate includes destructive tools. Select only the operations the agent needs, and expose `documentDelete` or `memoryForget` only when the agent is authorized to remove data.
**Python (OpenAI function calling):**
```python
import os
**Python:** there is no v5 Python tools package yet. Call the Python SDK (`pip install supermemory`) from your own function-calling handlers.
from supermemory_openai import SupermemoryTools
tools = SupermemoryTools(
os.environ["SUPERMEMORY_API_KEY"],
{"container_tags": ["user_123"]},
)
definitions = tools.get_tool_definitions() # all 7 tools
```
Filter `definitions` before passing them to a model if it should not be able to call `document_delete` or `memory_forget`.
**For Chatbots**: Use middleware (`withSupermemory` / `with_supermemory`) for automatic context injection, or pass tools to the model for explicit memory control
**For Chatbots**: Use middleware (`withSupermemory`) for automatic context injection, or pass tools to the model for explicit memory control
**For Knowledge Bases (RAG)**: Use `add()` / `documentAdd` for text or URL ingestion, the SDK file-upload method for local files, then `searchMemories` with hybrid mode for retrieval
@ -220,9 +200,9 @@ Filter `definitions` before passing them to a model if it should not be able to
## Best Practices
1. **Container Tags**: Use consistent user/project IDs as containerTags for proper isolation
1. **Namespaces**: Use one consistent user/project ID as the namespace for proper isolation; each document belongs to exactly one namespace
2. **Metadata**: Add custom metadata for advanced filtering (source, type, timestamp)
3. **Thresholds**: The v4 search default is `0.6`; tune it only after checking retrieval quality
3. **Search defaults**: v5 search defaults to `searchMode: "hybrid"` and `threshold: 0.3`; set both explicitly when you need memories-only or stricter results
4. **Batch Operations**: Use bulk endpoints for multiple documents
## Integration Ecosystem

View file

@ -8,6 +8,8 @@ Complete REST API documentation for Supermemory.
https://api.supermemory.ai
```
Application routes are unversioned: there is no `/v5` path prefix. [`/v5/reference`](https://api.supermemory.ai/v5/reference) is the interactive v5 reference, not an API path.
## Authentication
All requests require authentication via Bearer token in the Authorization header:
@ -18,15 +20,25 @@ Authorization: Bearer YOUR_API_KEY
Get your API key at [console.supermemory.ai](https://console.supermemory.ai).
## Namespaces and Parameter Placement
Every content route is scoped to one namespace in the path: `/ns/{namespace}/...`. A document belongs to exactly one namespace; never send several namespaces in one request.
- `GET` options go in the query string.
- `POST`, `PATCH`, and `PUT` options go in the JSON body (or as form fields on file uploads). List pagination (`page`, `limit`, `sort`, `order`) is the exception and stays in the query string.
- `DELETE` options such as `moveTo` go in the query string; bulk deletes send `ids` in the JSON body.
Options sent in the wrong place return `400`.
## Endpoints
### POST /v3/documents
### POST /ns/{namespace}/document
Add a document for processing and memory extraction.
**Endpoint:**
```
POST https://api.supermemory.ai/v3/documents
POST https://api.supermemory.ai/ns/{namespace}/document
```
**Headers:**
@ -39,22 +51,26 @@ Content-Type: application/json
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `content` | string | Yes | The content to process. Can be a URL, text, PDF path, image, or video |
| `containerTag` | string | No | Identifier for organizing documents (max 100 chars, alphanumeric with hyphens/underscores) |
| `entityContext` | string | No | Context guidance for memory extraction (max 1500 chars) |
| `customId` | string | No | Your custom identifier (max 100 chars, alphanumeric with hyphens/underscores) |
| `content` | string | Yes | The content to process: text or a URL. Upload files with `POST /ns/{namespace}/document/file` |
| `id` | string | No | Your stable document ID. Repeating it appends or diffs into the same document |
| `supportingContext` | string | No | Context guidance for memory extraction |
| `metadata` | object | No | Custom key-value pairs (strings, numbers, booleans, or string arrays) |
| `group` | object | No | Grouping values for the document |
| `date` | string | No | Document date |
| `taskType` | string | No | `"memory"` extracts long-term memories; `"superrag"` indexes without memory generation |
| `dreaming` | string | No | `"dynamic"` (default) groups related documents; `"instant"` processes each document on its own and bills one extra operation |
Ingest routes take no query parameters.
**Example Request:**
```bash
curl -X POST https://api.supermemory.ai/v3/documents \
curl -X POST https://api.supermemory.ai/ns/user_123/document \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "https://example.com/article",
"containerTag": "user_123",
"entityContext": "Technical blog post about API design",
"supportingContext": "Technical blog post about API design",
"metadata": {
"source": "blog",
"category": "technical",
@ -72,6 +88,8 @@ curl -X POST https://api.supermemory.ai/v3/documents \
}
```
`status` is the document's processing state after the request: `queued` when new work was queued, otherwise the current state (for example `done` for an unchanged duplicate).
**Response (401 Unauthorized):**
```json
@ -81,32 +99,36 @@ curl -X POST https://api.supermemory.ai/v3/documents \
}
```
**Response (500 Internal Server Error):**
```json
{
"error": "Internal Server Error",
"details": "Failed to process document"
}
```
**Processing Statuses:**
- `unknown`: State not yet known
- `queued`: Document awaiting processing
- `extracting`: Content extraction in progress
- `chunking`: Breaking into semantic segments
- `embedding`: Generating vector embeddings
- `indexing`: Building relationships
- `done`: Processing complete, searchable
- `failed`: Processing failed
Related document routes:
| Route | Purpose |
|-------|---------|
| `POST /ns/{namespace}/document/batch` | Add 1–600 documents: `{"documents":[{"content":"...","id":"doc_1"}]}` |
| `POST /ns/{namespace}/document/file` | Upload a file as `multipart/form-data` (`metadata` and `group` are JSON-encoded strings) |
| `GET /ns/{namespace}/document/{id}?include=chunks,memories` | Read a document; lifecycle fields are under `system` |
| `PATCH /ns/{namespace}/document/{id}` | Replace content and/or update `supportingContext`, `metadata`, `group`, `date` |
| `DELETE /ns/{namespace}/document` | Delete 1–100 documents: `{"ids":["doc_1"]}` |
| `POST /ns/{namespace}/list/{type}` | List `documents`, `chunks`, or `memories` |
---
### POST /v4/search
### POST /ns/{namespace}/search
Search memories using semantic understanding with advanced filtering.
Search one namespace using semantic understanding with typed filtering. Search has no query-string parameters.
**Endpoint:**
```
POST https://api.supermemory.ai/v4/search
POST https://api.supermemory.ai/ns/{namespace}/search
```
**Headers:**
@ -120,62 +142,45 @@ Content-Type: application/json
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `query` | string | Yes | The search query |
| `containerTags` | string[] | No | Filter by container tags |
| `chunkThreshold` | number | No | Threshold for chunk selection (0-1). 0 = least sensitive (more results), 1 = most sensitive (fewer, accurate results). Default: 0 |
| `searchMode` | string | No | Search mode: "semantic" (default) or "hybrid" (semantic + keyword). Use "hybrid" for RAG applications for better accuracy |
| `docId` | string | No | Search within specific document (max 255 chars) |
| `filters` | object | No | Advanced filtering with AND/OR logic (up to 5 nesting levels) |
| `searchMode` | string | No | `"hybrid"` (default, memories + chunks), `"memories"`, or `"chunks"` |
| `limit` | number | No | Maximum results |
| `threshold` | number | No | Similarity threshold (0-1). 0 = more results, 1 = fewer, more accurate results. Default: `0.3` |
| `filter` | object | No | Typed filter with `and`/`or` groups (up to 5 nesting levels) |
| `include` | object | No | Booleans: `documents`, `related`, `forgotten` (each default `false`) |
| `rerank` | string | No | `"none"` (default), `"order"`, or `"aggregate"` |
| `rewriteQuery` | boolean | No | Retrieval-oriented query rewriting. Default: `false` |
**Filter Types:**
**Filter Shape:**
```typescript
{
"filters": {
// Metadata filtering
"metadata": {
"key": "value"
},
// Numeric comparisons
"numeric": {
"field": { "$gte": 4.0 } // >, <, >=, <=, =
},
// Array contains
"array_contains": {
"tags": "value"
},
// String contains
"string_contains": {
"content": "substring"
},
// Logical operators
"$and": [{ /* filters */ }],
"$or": [{ /* filters */ }]
}
}
type Filter =
| { field: string; operator: "eq" | "neq"; value: string; caseSensitive?: boolean }
| { field: string; operator: "eq" | "neq"; value: number | boolean }
| { field: string; operator: "gt" | "gte" | "lt" | "lte"; value: number }
| { field: string; operator: "contains" | "notContains"; value: string; caseSensitive?: boolean }
| { field: string; operator: "arrayContains" | "arrayNotContains"; value: string }
| { operator: "and" | "or"; operands: Filter[] };
```
Fields may contain letters, numbers, `_`, `.`, and `-`. Groups allow up to 200 operands. The same `filter` works on profile and list routes.
**Example Request:**
```bash
curl -X POST https://api.supermemory.ai/v4/search \
curl -X POST https://api.supermemory.ai/ns/documentation/search \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "How do I authenticate users?",
"searchMode": "hybrid",
"chunkThreshold": 0.5,
"filters": {
"metadata": {
"type": "documentation",
"category": "security"
},
"numeric": {
"rating": { "$gte": 4.0 }
}
"threshold": 0.5,
"filter": {
"operator": "and",
"operands": [
{ "field": "type", "operator": "eq", "value": "documentation" },
{ "field": "category", "operator": "eq", "value": "security" },
{ "field": "rating", "operator": "gte", "value": 4.0 }
]
}
}'
```
@ -186,32 +191,30 @@ curl -X POST https://api.supermemory.ai/v4/search \
{
"results": [
{
"content": "Authentication can be done using JWT tokens...",
"score": 0.89,
"docId": "doc_123",
"id": "chunk_456",
"chunk": "Authentication can be done using JWT tokens...",
"metadata": {
"type": "documentation",
"category": "security",
"rating": 4.5
},
"chunkId": "chunk_456"
"system": { "createdAt": "...", "updatedAt": "..." }
},
{
"content": "OAuth 2.0 is a standard protocol for authorization...",
"score": 0.82,
"docId": "doc_789",
"metadata": {
"type": "documentation",
"category": "security",
"rating": 5.0
},
"chunkId": "chunk_789"
"id": "mem_789",
"memory": "The API supports OAuth 2.0 for authorization",
"metadata": {},
"isLatest": true,
"isInference": false,
"system": { "createdAt": "...", "updatedAt": "..." }
}
],
"total": 2
"searchTime": 42
}
```
Each result carries `memory`, `chunk`, or both; branch on field presence. With `include.documents`, the source is under `result.included.document`; with `include.related`, related memories are under `result.included.related.{parents,children,siblings}`.
**Response (401 Unauthorized):**
```json
@ -223,99 +226,80 @@ curl -X POST https://api.supermemory.ai/v4/search \
---
### POST /v4/memories
### POST /ns/{namespace}/profile
Create memories directly, bypassing document ingestion. Generates embeddings and makes them immediately searchable.
Get the maintained profile for a namespace. Profile takes no query; call search separately for query-ranked results.
**Endpoint:**
```
POST https://api.supermemory.ai/v4/memories
```
**Headers:**
```http
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
POST https://api.supermemory.ai/ns/{namespace}/profile
```
**Request Body:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `memories` | array | Yes | Array of 1-100 memory objects |
| `memories[].content` | string | Yes | Memory text (1-10,000 chars). Preferably entity-centric (e.g., "John prefers dark mode") |
| `memories[].isStatic` | boolean | No | Marks permanent traits like name or profession. Default: false |
| `memories[].metadata` | object | No | Custom key-value pairs (strings, numbers, booleans, or string arrays) |
| `containerTag` | string | Yes | Identifier for the space/container these memories belong to |
| `filter` | object | No | Typed filter |
| `buckets` | string[] | No | Narrow the custom bucket section (up to 50 names). Omit to return every bucket |
**Example Request:**
```bash
curl -X POST https://api.supermemory.ai/v4/memories \
curl -X POST https://api.supermemory.ai/ns/user_123/profile \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"containerTag": "user_123",
"memories": [
{
"content": "User prefers dark mode",
"isStatic": true,
"metadata": {
"category": "preferences",
"source": "settings"
}
},
{
"content": "User mentioned working on a React project yesterday",
"isStatic": false,
"metadata": {
"category": "activity",
"timestamp": "2026-02-20T15:30:00Z"
}
}
]
}'
-d '{}'
```
**Response (201 Created):**
**Response (200 OK):**
```json
{
"documentId": "doc_abc123",
"memories": [
{
"id": "mem_xyz789",
"memory": "User prefers dark mode",
"isStatic": true,
"createdAt": "2026-02-21T10:00:00Z"
},
{
"id": "mem_def456",
"memory": "User mentioned working on a React project yesterday",
"isStatic": false,
"createdAt": "2026-02-21T10:00:00Z"
}
]
"profile": {
"static": [{ "id": "mem_1", "memory": "The user works in design" }],
"dynamic": [{ "id": "mem_2", "memory": "The user is preparing a launch" }],
"buckets": { "work": [{ "id": "mem_3", "memory": "Prefers concise project updates" }] }
}
}
```
**Response (400 Bad Request):**
`static`, `dynamic`, and `buckets` are always returned. Bucket definitions live at `GET/PUT/DELETE /ns/{namespace}/profile/buckets`.
---
### DELETE /ns/{namespace}/memories
Forget memories by exact ID (1–500 IDs).
```bash
curl -X DELETE https://api.supermemory.ai/ns/user_123/memories \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"ids":["mem_1","mem_2"]}'
```
To find memories by meaning, call `DELETE /ns/{namespace}/memories/semantic` with `{"query":"...","dryRun":true}`, review the matched IDs, then submit them to the exact-ID route. Both return:
```json
{
"error": "Bad Request",
"details": "Invalid request parameters: memories array must contain 1-100 items"
"count": 1,
"matches": [{ "id": "mem_1", "memory": "Old address" }],
"errors": [{ "id": "mem_2", "error": "Memory not found" }]
}
```
**Response (404 Not Found):**
---
```json
{
"error": "Not Found",
"details": "Space not found for given containerTag"
}
```
### Namespaces and Organization
| Route | Purpose |
|-------|---------|
| `GET /namespaces` | List namespaces with `documentCount` and `memoryCount` |
| `GET /ns/{namespace}` | Read a namespace's `supportingContext` |
| `PATCH /ns/{namespace}` | Set `supportingContext` (send `null` to clear) |
| `DELETE /ns/{namespace}` | Permanently delete a namespace and its content |
| `DELETE /ns/{namespace}?moveTo={target}` | Move content to another namespace, then remove the source (`202`, returns `operationId`) |
| `GET/PATCH /organization` | Read or set `organizationalContext` (PATCH needs an org admin) |
---
@ -326,10 +310,12 @@ curl -X POST https://api.supermemory.ai/v4/memories \
| Code | Meaning | Description |
|------|---------|-------------|
| 200 | OK | Request successful |
| 201 | Created | Resource created successfully |
| 400 | Bad Request | Invalid request parameters |
| 202 | Accepted | Async operation queued (for example a namespace move) |
| 400 | Bad Request | Invalid request parameters, or an option in the wrong place |
| 401 | Unauthorized | Missing or invalid API key |
| 404 | Not Found | Resource not found |
| 403 | Forbidden | Caller lacks permission (for example a non-admin updating `/organization`) |
| 404 | Not Found | Resource not found in this namespace |
| 409 | Conflict | Document still processing, or a sync already running |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Internal Server Error | Server error occurred |
@ -383,18 +369,17 @@ Check your plan details in the [console](https://console.supermemory.ai) for spe
## Best Practices
### 1. Use Idempotent IDs
### 1. Use Stable IDs
Use `customId` for idempotency to prevent duplicate processing:
Send your own `id` so repeated writes land in one document instead of creating duplicates:
```bash
curl -X POST https://api.supermemory.ai/v3/documents \
curl -X POST https://api.supermemory.ai/ns/user_123/document \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "Important document",
"customId": "doc_2026_02_21_001",
"containerTag": "user_123"
"id": "doc_2026_02_21_001"
}'
```
@ -403,13 +388,13 @@ curl -X POST https://api.supermemory.ai/v3/documents \
Always check status codes and handle errors gracefully:
```javascript
const response = await fetch('https://api.supermemory.ai/v3/documents', {
const response = await fetch('https://api.supermemory.ai/ns/user_123/document', {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ content: "...", containerTag: "user_123" })
body: JSON.stringify({ content: "..." })
});
if (!response.ok) {
@ -421,18 +406,18 @@ if (!response.ok) {
const data = await response.json();
```
### 3. Use Container Tags Consistently
### 3. Use Namespaces Consistently
Maintain consistent naming for container tags:
Maintain consistent naming for namespaces:
```bash
# Good
containerTag: "user_123"
containerTag: "user_456"
/ns/user_123/...
/ns/user_456/...
# Avoid inconsistency
containerTag: "user_123"
containerTag: "123" # Different format
/ns/user_123/...
/ns/123/... # Different format
```
### 4. Rich Metadata
@ -442,7 +427,6 @@ Add comprehensive metadata for better filtering:
```json
{
"content": "Product review",
"containerTag": "reviews",
"metadata": {
"product": "iPhone 15",
"rating": 4.5,
@ -453,14 +437,15 @@ Add comprehensive metadata for better filtering:
}
```
### 5. Optimize Search Thresholds
### 5. Set Search Defaults Explicitly
Start with default (0) and adjust based on results:
v5 defaults to `searchMode: "hybrid"` and `threshold: 0.3`. Set them explicitly and adjust based on results:
```json
{
"query": "authentication methods",
"chunkThreshold": 0.5 // Balanced precision/recall
"searchMode": "memories",
"threshold": 0.5
}
```
@ -470,14 +455,15 @@ For large documents, check processing status:
```bash
# Add document
curl -X POST https://api.supermemory.ai/v3/documents \
curl -X POST https://api.supermemory.ai/ns/docs/document \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{ "content": "large-document.pdf", "containerTag": "docs" }'
-H "Content-Type: application/json" \
-d '{ "content": "https://example.com/large-report.pdf", "id": "report_2026" }'
# Returns: { "id": "doc_123", "status": "queued" }
# Returns: { "id": "report_2026", "status": "queued" }
# Later, list documents to check status
curl -X GET https://api.supermemory.ai/v3/documents?containerTag=docs \
# Later, read the document and check system.status
curl https://api.supermemory.ai/ns/docs/document/report_2026 \
-H "Authorization: Bearer YOUR_API_KEY"
```
@ -500,12 +486,11 @@ curl -X GET https://api.supermemory.ai/v3/documents?containerTag=docs \
### Add Text Content
```bash
curl -X POST https://api.supermemory.ai/v3/documents \
curl -X POST https://api.supermemory.ai/ns/user_123/document \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "User mentioned they prefer TypeScript over JavaScript for type safety",
"containerTag": "user_123",
"metadata": {
"source": "chat",
"timestamp": "2026-02-21T10:00:00Z"
@ -516,13 +501,12 @@ curl -X POST https://api.supermemory.ai/v3/documents \
### Add URL
```bash
curl -X POST https://api.supermemory.ai/v3/documents \
curl -X POST https://api.supermemory.ai/ns/knowledge_base/document \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "https://blog.example.com/best-practices",
"containerTag": "knowledge_base",
"entityContext": "Software development best practices article",
"supportingContext": "Software development best practices article",
"metadata": {
"type": "article",
"category": "best-practices"
@ -533,50 +517,35 @@ curl -X POST https://api.supermemory.ai/v3/documents \
### Search with Filters (Hybrid Mode for RAG)
```bash
curl -X POST https://api.supermemory.ai/v4/search \
curl -X POST https://api.supermemory.ai/ns/knowledge_base/search \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "React performance optimization",
"searchMode": "hybrid",
"chunkThreshold": 0.6,
"filters": {
"$and": [
{
"metadata": {
"type": "tutorial"
}
},
{
"numeric": {
"rating": { "$gte": 4.0 }
}
}
"threshold": 0.6,
"filter": {
"operator": "and",
"operands": [
{ "field": "type", "operator": "eq", "value": "tutorial" },
{ "field": "rating", "operator": "gte", "value": 4.0 }
]
}
}'
```
### Create Direct Memories
### Add Facts With Fast Memory Formation
There is no direct memory write in v5; memories come from documents. Use `dreaming: "instant"` when facts must appear quickly:
```bash
curl -X POST https://api.supermemory.ai/v4/memories \
curl -X POST https://api.supermemory.ai/ns/user_789/document \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"containerTag": "user_789",
"memories": [
{
"content": "User name is Alice Johnson",
"isStatic": true,
"metadata": { "type": "profile" }
},
{
"content": "Alice completed the React tutorial today",
"isStatic": false,
"metadata": { "type": "activity", "date": "2026-02-21" }
}
]
"content": "User name is Alice Johnson. Alice completed the React tutorial today.",
"metadata": { "type": "profile" },
"dreaming": "instant"
}'
```
@ -588,4 +557,5 @@ Coming soon: Webhooks for document processing status updates.
- **API Issues**: Check [status.supermemory.ai](https://status.supermemory.ai)
- **Documentation**: [supermemory.ai/docs](https://supermemory.ai/docs)
- **API Reference**: [api.supermemory.ai/v5/reference](https://api.supermemory.ai/v5/reference)
- **Console**: [console.supermemory.ai](https://console.supermemory.ai)

View file

@ -43,7 +43,7 @@ Document enters the processing queue. The system validates the content type and
**What happens:**
- Content type detection (PDF, image, video, URL, text)
- Validation of metadata and container tags
- Validation of metadata and the namespace
- Assignment to processing queue
### 2. Extracting
@ -171,13 +171,13 @@ Processing complete. Content is now fully searchable and integrated into the kno
### Static vs. Dynamic Memories
**Static Memories** (`isStatic: true`):
**Static Memories** (`profile.static`):
- Permanent facts that don't change
- Examples: name, profession, birthday
- Not subject to temporal updates
- High priority in retrieval
**Dynamic Memories** (`isStatic: false`):
**Dynamic Memories** (`profile.dynamic`):
- Contextual, episodic information
- Examples: recent conversations, activities
- Can be updated or superseded
@ -195,10 +195,7 @@ Memory v2: "User prefers React" (isLatest: false)
Memory v3: "User prefers React with TypeScript" (isLatest: true)
```
When querying, you can choose:
- Latest version only (default)
- Full version history
- Specific version
Each search result reports `isLatest`. To see version history, read the memory with `GET /ns/{namespace}/memories/{id}?include=related`: `parents` are earlier versions and `children` are newer ones.
## Retrieval Mechanism
@ -223,7 +220,7 @@ Chunk 4: similarity = 0.12 // "Database schemas"
**3. Threshold Filtering**
```javascript
chunkThreshold: 0.5
threshold: 0.5
Results: [Chunk 1, Chunk 2] // Only >= 0.5
```
@ -258,9 +255,9 @@ Metadata:
Results: Semantically similar + metadata match
```
## Container Tag Isolation
## Namespace Isolation
Container tags create isolated "spaces" within Supermemory:
Namespaces create isolated spaces within Supermemory. Every request names one namespace in its path (`/ns/{namespace}/...`), and a document belongs to exactly one namespace:
```
┌─────────────────────────────────┐
@ -292,7 +289,7 @@ Container tags create isolated "spaces" within Supermemory:
User profiles are dynamically generated from memories:
**Static Profile** (from `isStatic: true` memories):
**Static Profile** (`profile.static`):
```
Name: John Doe
Role: Senior Software Engineer
@ -300,7 +297,7 @@ Preferences: Dark mode, TypeScript, Vim keybindings
Timezone: UTC-8 (PST)
```
**Dynamic Context** (from recent memories):
**Dynamic Context** (`profile.dynamic`):
```
Recent Activity:
- Working on React project (last 3 days)
@ -312,19 +309,17 @@ Recent Activity:
**Combined Profile:**
```javascript
{
"profile": "John Doe, Senior Software Engineer who prefers TypeScript and dark mode",
"memories": [
{
"content": "Currently working on React authentication",
"score": 0.95,
"timestamp": "2 hours ago"
},
{
"content": "Completed advanced TypeScript course",
"score": 0.87,
"timestamp": "yesterday"
}
]
"profile": {
"static": [
{ "id": "mem_1", "memory": "John Doe is a Senior Software Engineer" },
{ "id": "mem_2", "memory": "Prefers TypeScript and dark mode" }
],
"dynamic": [
{ "id": "mem_3", "memory": "Currently working on React authentication" },
{ "id": "mem_4", "memory": "Completed advanced TypeScript course" }
],
"buckets": {}
}
}
```
@ -463,8 +458,8 @@ messages = [
┌──────────────────────────────────┐
│ Supermemory API Layer │
│ ┌──────────┐ ┌──────────────┐ │
│ │ /documents│ │ /search │ │
│ │ /memories │ │ /profile │ │
│ │ /document │ │ /search │ │
│ │ /list │ │ /profile │ │
│ └──────────┘ └──────────────┘ │
└──────────┬───────────────────────┘
↓
@ -488,7 +483,7 @@ messages = [
```
[PDF Document]
↓
[API: POST /v3/documents]
[API: POST /ns/{namespace}/document]
↓
[Queue: Document ID returned, status: "queued"]
↓
@ -510,11 +505,11 @@ messages = [
```
[User Query: "authentication methods"]
↓
[API: POST /v4/search]
[API: POST /ns/{namespace}/search]
↓
[Embed: Query → Vector]
↓
[Search: Vector similarity in container]
[Search: Vector similarity in namespace]
↓
[Filter: Apply metadata filters]
↓
@ -538,7 +533,7 @@ Supermemory is designed for scale:
## Security & Privacy
- **Encryption**: AES-256 at rest, TLS 1.3 in transit
- **Isolation**: Container tags enforce strict boundaries
- **Isolation**: Namespaces enforce strict boundaries
- **Access Control**: API key-based authentication
- **Compliance**: SOC 2, GDPR compliant
- **Data Residency**: Regional storage options

View file

@ -23,8 +23,6 @@ npm install supermemory
### Python
```bash
pip install supermemory
# Or for async support with aiohttp
pip install 'supermemory[aiohttp]'
```
📦 View on PyPI: [https://pypi.org/project/supermemory/](https://pypi.org/project/supermemory/)
@ -58,33 +56,33 @@ const client = new Supermemory({
});
async function main() {
// 1. Retrieve context for personalization
const response = await client.profile({
containerTag: "user_123", // Unique user identifier
q: "What does the user prefer?"
});
// 1. Retrieve context for personalization ("user_123" is the namespace)
const { profile } = await client.profile("user_123");
console.log("Static Profile:", response.profile.static);
console.log("Dynamic Profile:", response.profile.dynamic);
if (response.searchResults) {
console.log("Search Results:", response.searchResults.results);
}
console.log("Static Profile:", profile.static);
console.log("Dynamic Profile:", profile.dynamic);
// Profile takes no query; search separately for query-ranked results
const { results } = await client.search("user_123", {
query: "What does the user prefer?",
searchMode: "memories"
});
console.log("Search Results:", results);
// 2. Enrich your LLM prompt
const systemMessage = `
Static Profile:
${response.profile.static.map(f => `- ${f}`).join('\n')}
${profile.static.map(m => `- ${m.memory}`).join('\n')}
Recent Context:
${response.profile.dynamic.map(f => `- ${f}`).join('\n')}
${profile.dynamic.map(m => `- ${m.memory}`).join('\n')}
`;
// Send systemMessage to your LLM...
// 3. Store new memories from the conversation
await client.add({
await client.add("user_123", {
content: "User mentioned they prefer dark mode and TypeScript",
containerTag: "user_123",
metadata: {
source: "chat",
timestamp: new Date().toISOString()
@ -99,6 +97,8 @@ main();
### Python Example
The Python SDK takes the namespace first, then keyword arguments.
```python
import os
from supermemory import Supermemory
@ -107,19 +107,14 @@ client = Supermemory(api_key=os.environ["SUPERMEMORY_API_KEY"])
def main():
# 1. Retrieve context
response = client.profile(
container_tag="user_123",
q="What does the user prefer?"
)
response = client.profile("user_123")
print("Static Profile:", response["profile"]["static"])
print("Dynamic Profile:", response["profile"]["dynamic"])
if "searchResults" in response:
print("Search Results:", response["searchResults"]["results"])
print("Static Profile:", response.profile.static)
print("Dynamic Profile:", response.profile.dynamic)
# 2. Enrich your LLM prompt
static_facts = "\n".join(f"- {fact}" for fact in response["profile"]["static"])
dynamic_facts = "\n".join(f"- {fact}" for fact in response["profile"]["dynamic"])
static_facts = "\n".join(f"- {m.memory}" for m in response.profile.static)
dynamic_facts = "\n".join(f"- {m.memory}" for m in response.profile.dynamic)
system_message = f"""
Static Profile:
@ -133,8 +128,8 @@ def main():
# 3. Store new memories
client.add(
"user_123",
content="User mentioned they prefer dark mode and TypeScript",
container_tag="user_123",
metadata={
"source": "chat",
"timestamp": "2026-02-21T10:00:00Z"
@ -147,37 +142,6 @@ if __name__ == "__main__":
main()
```
### Python Async Example
```python
import os
import asyncio
from supermemory import AsyncSupermemory
async def main():
client = AsyncSupermemory(api_key=os.environ["SUPERMEMORY_API_KEY"])
# 1. Retrieve context
response = await client.profile(
container_tag="user_123",
q="What does the user prefer?"
)
print("User facts:", response["profile"]["static"])
# 2. Store new memories
await client.add(
content="User mentioned they prefer dark mode and TypeScript",
container_tag="user_123",
metadata={"source": "chat"}
)
print("Memory stored successfully!")
if __name__ == "__main__":
asyncio.run(main())
```
## Core Workflow Pattern
The standard Supermemory workflow follows three steps:
@ -188,24 +152,23 @@ The standard Supermemory workflow follows three steps:
This pattern ensures your AI agent has perfect recall and becomes more personalized over time.
## Understanding Container Tags
## Understanding Namespaces
Container tags are identifiers that isolate memories:
A namespace is the identifier that isolates memories. It goes first in every SDK call and in the URL (`/ns/{namespace}/...`):
- Use **user IDs** for per-user personalization: `"user_123"`
- Use **project IDs** for project-specific context: `"project_abc"`
- Use **session IDs** for temporary context: `"session_xyz"`
- Use **organization IDs** for shared knowledge: `"org_acme"`
Memories with the same containerTag are grouped together and can be searched independently.
A document belongs to exactly one namespace, and each request reads or writes one namespace. To read across several, run one call per namespace and merge the results.
## Advanced: Threshold Filtering
Control relevance strictness with the `threshold` parameter:
Control relevance strictness with the `threshold` search option (v5 default `0.3`):
```typescript
const context = await client.profile({
containerTag: "user_123",
const { results } = await client.search("user_123", {
query: "user preferences",
threshold: 0.7 // 0-1: higher = stricter matching
});
@ -227,31 +190,29 @@ const context = await client.profile({
### Chatbot with Memory
```typescript
// Before generating response
const context = await client.profile({
containerTag: userId,
query: userMessage
});
const [{ profile }, { results }] = await Promise.all([
client.profile(userId),
client.search(userId, { query: userMessage, searchMode: "memories" })
]);
// After receiving LLM response
await client.add({
// After receiving LLM response; a stable id keeps one conversation in one document
await client.add(userId, {
content: `User: ${userMessage}\nAssistant: ${llmResponse}`,
containerTag: userId
id: conversationId
});
```
### Document Knowledge Base
```typescript
// Add documents
await client.add({
await client.add("knowledge_base", {
content: "https://example.com/documentation",
containerTag: "knowledge_base",
metadata: { type: "documentation" }
});
// Search documents (use hybrid mode for RAG)
const response = await client.search({
q: "How do I authenticate?",
containerTag: "knowledge_base",
// Search documents (hybrid is the v5 default: memories + source chunks)
const response = await client.search("knowledge_base", {
query: "How do I authenticate?",
searchMode: "hybrid",
limit: 10
});
@ -260,10 +221,7 @@ const response = await client.search({
### Personalized Recommendations
```typescript
// Get user profile
const profile = await client.profile({
containerTag: userId,
query: "user interests and preferences"
});
const { profile } = await client.profile(userId);
// Use profile to personalize recommendations
const recommendations = generateRecommendations(profile);
@ -277,14 +235,14 @@ const recommendations = generateRecommendations(profile);
- Verify you're using the correct key (not accidentally using a test key)
**No Results from Search**
- Try lowering the `threshold` parameter
- Ensure the containerTag matches what you used during `add()`
- Wait 1-2 minutes after adding content for processing to complete
- Try lowering the `threshold` option
- Ensure the namespace matches what you used during `add()`
- Wait for processing to finish. With the default `dreaming: "dynamic"`, a fresh namespace can show zero memories and an empty profile for several minutes; pass `dreaming: "instant"` to `add()` for quickstarts and tests
**Slow Processing**
- Large PDFs (100 pages) take 1-2 minutes
- Videos take 5-10 minutes
- Check document status with `documents.list()`
- Check document status with `client.list(namespace, "documents")` and read `system.status` on each item
## Support

View file

@ -14,7 +14,7 @@ yarn add supermemory
# or
pnpm add supermemory
# Agent tools and Vercel AI SDK middleware
# Agent tools and Vercel AI SDK / OpenAI middleware
npm install @supermemory/tools
```
@ -23,11 +23,6 @@ npm install @supermemory/tools
### Python
```bash
pip install supermemory
# Or for async support with aiohttp
pip install 'supermemory[aiohttp]'
# OpenAI function tools and middleware
pip install supermemory-openai-sdk
```
📦 View on PyPI: [https://pypi.org/project/supermemory/](https://pypi.org/project/supermemory/)
@ -44,30 +39,30 @@ import { Supermemory } from 'supermemory';
const client = new Supermemory({
apiKey: process.env.SUPERMEMORY_API_KEY, // Optional if env var is set
baseURL: 'https://api.supermemory.ai' // Optional, defaults to this
baseUrl: 'https://api.supermemory.ai' // Optional, defaults to this
});
```
Other client options: `timeoutInSeconds`, `headers`, and `maxRetries` (default 2).
### Python
```python
import os
from supermemory import Supermemory
# Synchronous client
client = Supermemory(
api_key=os.environ["SUPERMEMORY_API_KEY"], # Optional if env var is set
base_url="https://api.supermemory.ai" # Optional, defaults to this
)
# Asynchronous client
from supermemory import AsyncSupermemory
async_client = AsyncSupermemory(
api_key=os.environ["SUPERMEMORY_API_KEY"]
)
```
## Namespaces
Every content call is scoped to one **namespace**, passed first. A namespace is the isolation boundary (usually one per end user or project). A document belongs to exactly one namespace; to read across several, call once per namespace and merge the results.
In TypeScript, URL values (`namespace`, then `id` where there is one) are positional and everything else goes in one object. In Python, the namespace comes first and everything else is a keyword argument.
## Core Methods
### `add()` - Store Memories
@ -76,35 +71,40 @@ Add content to Supermemory for processing and memory extraction.
#### TypeScript
```typescript
await client.add({
content: string, // Required: plaintext or a URL string
containerTag?: string, // Optional: isolation identifier
entityContext?: string, // Optional: context for memory extraction
customId?: string, // Optional: your custom identifier
metadata?: Record<string, any> // Optional: custom key-value pairs
await client.add(namespace, {
content: string, // Required: plaintext or a URL string
id?: string, // Optional: your stable document ID
supportingContext?: string, // Optional: context for memory extraction
metadata?: Record<string, any>, // Optional: custom key-value pairs
date?: string, // Optional: document date
taskType?: "memory" | "superrag", // Optional: "superrag" indexes without memory generation
dreaming?: "dynamic" | "instant" // Optional: "dynamic" (default) or "instant"
});
// Returns: { id: string, status: "queued" | "done" | ... }
```
#### Python
```python
client.add(
namespace, # Required: positional
content=str, # Required: plaintext or a URL string
container_tag=str, # Optional: isolation identifier
entity_context=str, # Optional: context for memory extraction
custom_id=str, # Optional: your custom identifier
metadata=dict # Optional: custom key-value pairs
)
```
`add()` does not read a local file path. Upload local files with `client.documents.uploadFile({ file })` in TypeScript or `client.documents.upload_file(file=...)` in Python; `filepath` is metadata, not a file upload.
Repeating `add()` with the same `id` appends or diffs the new content into that document; it does not replace it. Use `documents.update()` to replace content.
`dreaming: "instant"` makes memories appear quickly and bills one extra operation per document. Under the default `"dynamic"`, a fresh namespace can show zero memories and an empty profile for several minutes, so use `"instant"` for quickstarts and tests.
`add()` does not read a local file path. Upload local files with `client.documents.uploadFile(namespace, { file })`; on that multipart call, `metadata` is a JSON string.
#### Examples
**Add text content:**
```typescript
await client.add({
await client.add("user_123", {
content: "User prefers dark mode and TypeScript over JavaScript",
containerTag: "user_123",
metadata: {
source: "preferences",
timestamp: new Date().toISOString()
@ -114,269 +114,284 @@ await client.add({
**Add URL for processing:**
```typescript
await client.add({
await client.add("knowledge_base", {
content: "https://example.com/blog/article",
containerTag: "knowledge_base",
entityContext: "technical documentation",
supportingContext: "technical documentation",
metadata: { type: "documentation", category: "api" }
});
```
**Add with custom ID:**
**Add with your own ID:**
```typescript
await client.add({
await client.add("project_abc", {
content: "Project requirements document...",
containerTag: "project_abc",
customId: "requirements_v1",
id: "requirements_v1",
metadata: { version: "1.0", author: "john@example.com" }
});
```
**Add several documents at once:**
```typescript
await client.documents.batchAdd("project_abc", {
documents: [
{ content: "first", id: "doc_1" },
{ content: "second", id: "doc_2" },
],
});
```
**Upload a file:**
```typescript
await client.documents.uploadFile("project_abc", {
file,
metadata: JSON.stringify({ source: "upload" }),
});
```
### `profile()` - Retrieve User Context
Get personalized context including static profile data and relevant dynamic memories.
Get the maintained profile for a namespace: static (long-lived) and dynamic (recent) memories. Profile takes no query; call `search()` separately when you need query-ranked results.
#### TypeScript
```typescript
const response = await client.profile({
containerTag: string, // Required: user/project identifier
q?: string, // Optional: search query to include search results
threshold?: number // Optional: relevance threshold (0-1, default 0.5)
const response = await client.profile(namespace, {
buckets?: string[], // Optional: narrow the custom bucket section (max 50 names)
filter?: Filter // Optional: typed filter (see Metadata Filtering)
});
// Returns:
// {
// profile: {
// static: string[], // Array of long-lived profile facts
// dynamic: string[] // Array of dynamic memories (recent context)
// },
// searchResults?: { // Only included if q parameter was provided
// results: Array<{ // Search results
// id: string,
// memory?: string,
// similarity: number,
// metadata: object | null
// }>,
// total: number,
// timing: number
// static: Array<{ id: string, memory: string }>, // Long-lived profile facts
// dynamic: Array<{ id: string, memory: string }>, // Recent context
// buckets: Record<string, Array<{ id: string, memory: string }>>
// }
// }
```
#### Python
```python
response = client.profile(
container_tag=str, # Required: user/project identifier
q=str, # Optional: search query to include search results
threshold=float # Optional: relevance threshold (0-1, default 0.5)
)
response = client.profile(namespace)
# Returns a ProfileResponse model:
# response.profile.static / response.profile.dynamic
# response.search_results.results # only when q was provided
# response.profile.static / response.profile.dynamic: lists of entries with .id and .memory
```
#### Examples
**Get user profile:**
```typescript
const response = await client.profile({
containerTag: "user_123",
q: "What are the user's preferences and settings?"
});
const { profile } = await client.profile("user_123");
console.log(response.profile.static); // ["User John Doe", "Prefers dark mode", ...]
console.log(response.profile.dynamic); // ["Recently mentioned...", "Last conversation..."]
console.log(response.searchResults); // Search results for the query (if provided)
console.log(profile.static.map(m => m.memory)); // ["User John Doe", "Prefers dark mode", ...]
console.log(profile.dynamic.map(m => m.memory)); // ["Recently mentioned...", "Last conversation..."]
```
**Profile without search (just get stored memories):**
**Profile plus query-ranked memories:**
```typescript
const response = await client.profile({
containerTag: "user_456"
// No q parameter = only returns profile.static and profile.dynamic
});
console.log(response.profile.static); // All static facts
console.log(response.profile.dynamic); // Recent dynamic memories
// response.searchResults will be undefined
const [{ profile }, { results }] = await Promise.all([
client.profile("user_456"),
client.search("user_456", {
query: "What are the user's preferences and settings?",
searchMode: "memories"
})
]);
```
Every profile entry carries an `id`, so it can be passed to `memories.forget()`.
### `search()` - Semantic Search
Search across memories using semantic retrieval. `client.search()` is the current TypeScript v4 call. Python uses `client.search.memories()` for the same v4 endpoint; the TypeScript `client.search.documents()` method is the legacy v3 document response.
Search one namespace. Every option goes in one object; omitting `searchMode` gives `hybrid`.
#### TypeScript
```typescript
const response = await client.search({
q: string, // Required: search query
containerTag?: string, // Optional: filter by container tag
limit?: number, // Optional: max results (default 10, max 100)
threshold?: number, // Optional: similarity threshold (0-1, default 0.6)
searchMode?: "memories" | "hybrid" | "documents", // Optional: "memories" (default), "hybrid" (memories + document chunks), or "documents" (chunks only)
filters?: FilterObject // Optional: advanced filtering
const response = await client.search(namespace, {
query: string, // Required: search query
searchMode?: "hybrid" | "memories" | "chunks", // Optional: "hybrid" (default), "memories", or "chunks"
limit?: number, // Optional: max results
threshold?: number, // Optional: similarity threshold (0-1, default 0.3)
filter?: Filter, // Optional: typed filter
include?: { documents?: boolean, related?: boolean, forgotten?: boolean },
rerank?: "none" | "order" | "aggregate", // Optional: default "none"
rewriteQuery?: boolean // Optional: default false
});
// Returns:
// {
// results: Array<{
// id: string,
// memory?: string, // Memory content (for memory results)
// chunk?: string, // Chunk content (for chunk results in hybrid mode)
// metadata: object | null,
// updatedAt: string,
// similarity: number,
// version?: number | null
// memory?: string, // Present on memory results
// chunk?: string, // Present on chunk results
// metadata: object,
// isLatest?: boolean,
// isInference?: boolean,
// system: { ... }, // Lifecycle fields
// included?: { document?, related?: { parents, children, siblings } }
// }>,
// total: number,
// timing: number // Search time in milliseconds
// searchTime: number
// }
```
Branch on whether a result has `memory` or `chunk` instead of assuming one shape.
#### Python
```python
response = client.search.memories(
q=str, # Required: search query
container_tag=str, # Optional: filter by container tag
threshold=float, # Optional: similarity threshold (0-1, default 0.6)
limit=int, # Optional: max results (default 10, max 100)
search_mode=str, # Optional: "memories" (default), "hybrid", or "documents"
filters=dict # Optional: advanced filtering
response = client.search(
namespace,
query=str, # Required: search query
)
# Returns a SearchMemoriesResponse model:
# response.results, response.total, response.timing
# response.results
```
#### Examples
**Basic semantic search:**
```typescript
const response = await client.search({
q: "How do I authenticate users?",
containerTag: "documentation",
const { results } = await client.search("documentation", {
query: "How do I authenticate users?",
limit: 10
});
response.results.forEach(result => {
console.log(`Similarity: ${result.similarity}`);
results.forEach(result => {
console.log(`Content: ${result.memory ?? result.chunk}`);
});
```
**Hybrid search for RAG (memories + source chunks):**
**Memories only, with v4-style strictness:**
```typescript
const response = await client.search({
q: "authentication methods",
containerTag: "docs",
searchMode: "hybrid", // Returns both extracted memories and document chunks
threshold: 0.3,
const { results } = await client.search("user_123", {
query: "What did the user decide?",
searchMode: "memories",
threshold: 0.6,
limit: 10
});
```
**Source chunks only (document search):**
```typescript
const { results } = await client.search("docs", {
query: "authentication methods",
searchMode: "chunks"
});
```
**Search with metadata filters:**
```typescript
const response = await client.search({
q: "authentication methods",
containerTag: "docs",
threshold: 0.3,
filters: {
AND: [
{ key: "type", value: "tutorial" },
{ key: "category", value: "security" }
const { results } = await client.search("docs", {
query: "authentication methods",
filter: {
operator: "and",
operands: [
{ field: "type", operator: "eq", value: "tutorial" },
{ field: "category", operator: "eq", value: "security" }
]
}
});
```
**Search within a filepath:**
```typescript
const response = await client.search({
q: "rate limiting configuration",
containerTag: "specific_project",
filepath: "/docs/api.md"
});
```
### `list()` - List Documents, Chunks, or Memories
### `documents.list()` - List Documents
Retrieve stored documents with optional filtering and pagination.
Retrieve stored items in one namespace with optional filtering and pagination.
#### TypeScript
```typescript
const docs = await client.documents.list({
containerTags?: string[], // Optional: filter by one or more containers
limit?: number, // Optional: items per page (default 10)
page?: number, // Optional: 1-based page number (default 1)
includeContent?: boolean, // Optional: include source content (default false)
sort?: "createdAt" | "updatedAt",
order?: "asc" | "desc"
const response = await client.list(namespace, "documents" | "chunks" | "memories", {
page?: number, // Optional: 1-based page number (default 1)
limit?: number, // Optional: items per page (default 10, max 100)
sort?: "createdAt" | "updatedAt", // Optional: default "createdAt"
order?: "asc" | "desc", // Optional: default "desc"
filter?: Filter // Optional: typed filter
});
// Returns:
// {
// memories: Array<{
// id: string,
// status: string,
// metadata: object,
// createdAt: string,
// content?: string // only when includeContent=true
// }>,
// pagination: { currentPage, totalItems, totalPages, limit? }
// documents: Array<{ id, metadata, system: { status, createdAt, updatedAt }, ... }>,
// chunks: [...],
// memories: [...],
// pagination: { currentPage, totalPages, ... }
// }
```
#### Python
```python
docs = client.documents.list(
container_tags=[str], # Optional: filter by one or more containers
limit=int, # Optional: items per page (default 10)
page=int, # Optional: 1-based page number (default 1)
include_content=bool # Optional: include source content (default False)
)
// Only the array for the requested type is populated.
```
#### Examples
**List all documents for a user:**
```typescript
const docs = await client.documents.list({
containerTags: ["user_123"],
limit: 50
});
const { documents } = await client.list("user_123", "documents", { limit: 50 });
docs.memories.forEach(doc => {
console.log(`${doc.id}: ${doc.status}`);
documents.forEach(doc => {
console.log(`${doc.id}: ${doc.system.status}`);
});
```
There is no separate processing list: read `system.status` on each document (`queued`, `extracting`, `chunking`, `embedding`, `indexing`, `done`, `failed`).
**Paginated listing:**
```typescript
const page1 = await client.documents.list({ limit: 20, page: 1 });
const page2 = await client.documents.list({ limit: 20, page: 2 });
const page1 = await client.list("user_123", "documents", { limit: 20, page: 1 });
const page2 = await client.list("user_123", "documents", { limit: 20, page: 2 });
```
### `documents.delete()` - Delete Document
**List memories:**
```typescript
const { memories } = await client.list("user_123", "memories");
```
Permanently remove a source document. Memories extracted from that source are soft-forgotten so they no longer appear in profile or search.
### `documents.get()` / `documents.update()`
```typescript
const doc = await client.documents.get("user_123", "doc_1", {
include: ["chunks", "memories"]
});
// Replaces the canonical content and reprocesses it
await client.documents.update("user_123", "doc_1", {
content: "corrected source",
metadata: { revision: 2 }
});
```
The document ID may be the Supermemory ID or your own `id`; it is resolved only inside that namespace.
### `documents.delete()` - Delete Documents
Permanently remove source documents. Memories extracted from them are soft-forgotten so they no longer appear in profile or search.
#### TypeScript
```typescript
await client.documents.delete(documentId);
```
#### Python
```python
client.documents.delete(document_id)
const { count, errors } = await client.documents.delete(namespace, {
ids: string[] // 1-100 Supermemory or caller-defined IDs
});
```
#### Example
```typescript
await client.documents.delete("doc_abc123");
const { count, errors } = await client.documents.delete("user_123", {
ids: ["doc_abc123"]
});
```
HTTP success can include partial failures; check `errors`.
### `memories.forget()` / `memories.forgetMatching()` - Forget Memories
```typescript
// Preview memories that match by meaning (dryRun is required)
const preview = await client.memories.forgetMatching("user_123", {
query: "outdated home address",
dryRun: true
});
// Forget the reviewed IDs
await client.memories.forget("user_123", {
ids: preview.matches.map((m) => m.id)
});
```
Both return `{ count, matches, errors }`.
## Advanced Features
### Metadata Filtering
@ -384,9 +399,8 @@ await client.documents.delete("doc_abc123");
Add rich metadata to enable advanced filtering:
```typescript
await client.add({
await client.add("reviews", {
content: "Product review of iPhone 15",
containerTag: "reviews",
metadata: {
product: "iPhone 15",
rating: 4.5,
@ -395,65 +409,77 @@ await client.add({
}
});
// Search with metadata filters
const results = await client.search({
q: "phone reviews",
containerTag: "reviews",
filters: {
AND: [
{ key: "rating", value: "4.0", filterType: "numeric", numericOperator: ">=" },
{ key: "verified", value: "true" },
{ key: "tags", value: "apple", filterType: "array_contains" }
// Search with a typed filter
const { results } = await client.search("reviews", {
query: "phone reviews",
filter: {
operator: "and",
operands: [
{ field: "rating", operator: "gte", value: 4.0 },
{ field: "verified", operator: "eq", value: true },
{ field: "tags", operator: "arrayContains", value: "apple" }
]
}
});
```
### Entity Context for Better Extraction
Filter operators: `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `contains`, `notContains`, `arrayContains`, `arrayNotContains`, and `and` / `or` groups with `operands`. String comparisons accept `caseSensitive`. `filter` is a typed object, not a JSON string, and the same shape works in `search`, `list`, and `profile`. Keys are literal: `customer.plan` is one field name, not a nested path.
### Supporting Context for Better Extraction
Provide context to guide memory extraction:
```typescript
await client.add({
await client.add("user_123", {
content: "User mentioned preferring React over Vue",
containerTag: "user_123",
entityContext: "This is a conversation about frontend framework preferences"
supportingContext: "This is a conversation about frontend framework preferences"
});
```
The `entityContext` helps Supermemory understand what type of information to extract and prioritize.
The `supportingContext` helps Supermemory understand what type of information to extract and prioritize. A namespace can also carry its own context:
### Container Tag Patterns
```typescript
await client.namespaces.update("user_123", {
supportingContext: "Personal assistant memory for one user"
});
```
### Namespace Patterns
**Per-User Isolation:**
```typescript
const userId = "user_123";
await client.add({ content: "...", containerTag: userId });
const context = await client.profile({ containerTag: userId, q: "..." });
await client.add(userId, { content: "..." });
const { profile } = await client.profile(userId);
```
**Multi-Tenant Applications:**
```typescript
const orgTag = `org_${organizationId}`;
const userTag = `org_${organizationId}_user_${userId}`;
const orgNamespace = `org_${organizationId}`;
const userNamespace = `org_${organizationId}_user_${userId}`;
// Org-wide knowledge
await client.add({ content: "...", containerTag: orgTag });
await client.add(orgNamespace, { content: "..." });
// User-specific within org
await client.add({ content: "...", containerTag: userTag });
await client.add(userNamespace, { content: "..." });
```
**Project-Based Organization:**
```typescript
const projectTag = `project_${projectId}`;
await client.add({
const projectNamespace = `project_${projectId}`;
await client.add(projectNamespace, {
content: "Project requirements...",
containerTag: projectTag,
metadata: { type: "requirements", version: "1.0" }
});
```
**Manage namespaces:**
```typescript
const { namespaces } = await client.namespaces.list();
await client.namespaces.delete("project_alpha", { moveTo: "project_archive" });
```
## Integration with AI Frameworks
### Vercel AI SDK
@ -468,7 +494,7 @@ import { openai } from "@ai-sdk/openai"
import { supermemoryTools } from "@supermemory/tools/ai-sdk"
const allTools = supermemoryTools(process.env.SUPERMEMORY_API_KEY!, {
containerTags: ["user_123"],
namespace: "user_123",
})
// Select the operations this agent is allowed to call.
@ -492,7 +518,7 @@ Tools: `searchMemories`, `addMemory`, `getProfile`, `documentList`, `documentAdd
Use `searchMemories` for targeted hybrid recall; `getProfile` for broad static/dynamic user context; `documentList`, `documentAdd`, and `documentDelete` for source management. Hybrid search returns both extracted memories and source-document chunks.
If you configure multiple container tags, `searchMemories`, `getProfile`, and `memoryForget` use the first tag because v4 memory operations are single-space. Add, list, and delete operations use the broader configured scope where supported.
Every tool reads and writes only the configured `namespace`. Create one tool set per namespace if an agent needs more than one.
`supermemoryTools()` includes destructive operations. Expose `documentDelete` and `memoryForget` only when the agent is authorized to remove data, and require user confirmation when appropriate. `stopWhen` allows the model to consume tool results and produce a final answer instead of stopping immediately after the first tool call.
@ -505,11 +531,13 @@ import { withSupermemory } from "@supermemory/tools/ai-sdk"
import { openai } from "@ai-sdk/openai"
const modelWithMemory = withSupermemory(openai("gpt-4o"), {
containerTag: "user_123",
customId: "conversation_456",
namespace: "user_123",
id: "conversation_456",
})
```
`id` groups a conversation's messages into one document. For the OpenAI SDK, import `withSupermemory` from `@supermemory/tools/openai` and pass the same options.
#### Manual SDK integration
```typescript
@ -519,17 +547,16 @@ import { generateText } from 'ai';
const memory = new Supermemory();
async function chat(userId: string, message: string) {
async function chat(userId: string, conversationId: string, message: string) {
// 1. Get context
const context = await memory.profile({
containerTag: userId,
q: message
});
const profileText = [
...context.profile.static,
...context.profile.dynamic,
].join('\n');
const searchText = JSON.stringify(context.searchResults?.results ?? []);
const [{ profile }, { results }] = await Promise.all([
memory.profile(userId),
memory.search(userId, { query: message, searchMode: "memories" })
]);
const profileText = [...profile.static, ...profile.dynamic]
.map(m => m.memory)
.join('\n');
const searchText = results.map(r => r.memory ?? r.chunk).join('\n');
// 2. Generate response with context
const { text } = await generateText({
@ -538,10 +565,10 @@ async function chat(userId: string, message: string) {
prompt: message
});
// 3. Store conversation
await memory.add({
// 3. Store conversation; the same id keeps it in one document
await memory.add(userId, {
content: `User: ${message}\nAssistant: ${text}`,
containerTag: userId
id: conversationId
});
return text;
@ -560,23 +587,22 @@ const llm = new ChatOpenAI({ model: 'gpt-4' });
async function chatWithMemory(userId: string, userMessage: string) {
// Retrieve context
const context = await memory.profile({
containerTag: userId,
q: userMessage
});
const [{ profile }, { results }] = await Promise.all([
memory.profile(userId),
memory.search(userId, { query: userMessage, searchMode: "memories" })
]);
// Create messages with context
const messages = [
new SystemMessage(`Context: ${JSON.stringify(context)}`),
new SystemMessage(`Context: ${JSON.stringify({ profile, results })}`),
new HumanMessage(userMessage)
];
const response = await llm.invoke(messages);
// Store interaction
await memory.add({
content: `${userMessage}\n${response.content}`,
containerTag: userId
await memory.add(userId, {
content: `${userMessage}\n${response.content}`
});
return response.content;
@ -593,19 +619,13 @@ memory = Supermemory()
def create_memory_enhanced_agent(user_id: str):
# Get user context
context = memory.profile(
container_tag=user_id,
q="user preferences and history"
)
context = memory.profile(user_id)
search = memory.search(user_id, query="user preferences and history")
profile_text = "\n".join(
(context.profile.static or []) + (context.profile.dynamic or [])
)
search_text = "\n".join(
result.memory
for result in (context.search_results.results if context.search_results else [])
if result.memory
m.memory for m in context.profile.static + context.profile.dynamic
)
search_text = "\n".join(r.memory for r in search.results if r.memory)
agent = Agent(
role="Personal Assistant",
@ -619,11 +639,11 @@ def create_memory_enhanced_agent(user_id: str):
## Best Practices
### 1. Consistent Container Tags
Always use the same format for container tags:
### 1. Consistent Namespaces
Always use the same format for namespaces:
```typescript
// Good
const tag = `user_${userId}`;
const namespace = `user_${userId}`;
// Avoid inconsistency
// Sometimes: "user_123"
@ -633,9 +653,8 @@ const tag = `user_${userId}`;
### 2. Rich Metadata
Add metadata for better filtering and organization:
```typescript
await client.add({
await client.add("user_123", {
content: "...",
containerTag: "user_123",
metadata: {
source: "chat",
timestamp: new Date().toISOString(),
@ -645,58 +664,61 @@ await client.add({
});
```
### 3. Meaningful Custom IDs
Use custom IDs for idempotency and reference:
### 3. Meaningful IDs
Use your own `id` for idempotency and reference. Repeating an `id` appends to that document instead of creating a new one:
```typescript
await client.add({
await client.add("feedback", {
content: "...",
customId: `feedback_${userId}_${Date.now()}`,
containerTag: "feedback"
id: `feedback_${userId}_${Date.now()}`
});
```
### 4. Appropriate Thresholds
Start with the v4 search default (`0.6`) and adjust based on results:
v5 search defaults to `threshold: 0.3` and `searchMode: "hybrid"`. Set them explicitly and adjust based on results:
- **0.3-0.5**: Broader recall, good for discovery
- **0.5-0.7**: Balanced precision and recall; `0.6` is the default
- **0.5-0.7**: Balanced precision and recall
- **0.7-1.0**: High precision, fewer but more relevant results
### 5. Error Handling
Always handle errors gracefully:
```typescript
import { SupermemoryError } from 'supermemory';
try {
await client.add({ content: "...", containerTag: "user_123" });
await client.add("user_123", { content: "..." });
} catch (error) {
if (error.status === 401) {
if (error instanceof SupermemoryError && error.statusCode === 401) {
console.error("Invalid API key");
} else if (error.status === 429) {
} else if (error instanceof SupermemoryError && error.statusCode === 429) {
console.error("Rate limit exceeded");
} else {
console.error("Failed to add memory:", error.message);
console.error("Failed to add memory:", error);
}
}
```
`NotFoundError`, `UnauthorizedError`, and `ConflictError` cover common statuses; `SupermemoryTimeoutError` covers timeouts.
## Naming Conventions
### TypeScript (camelCase)
- `containerTag`
- `entityContext`
- `customId`
- `namespace` (first positional argument)
- `id`
- `supportingContext`
- `group`
- `filter`
- `query`
- `threshold`
- `q`
### Python (snake_case)
- `container_tag`
- `entity_context`
- `custom_id`
- `threshold`
- `namespace` (first positional argument)
- `content`, `metadata`, `query` as keyword arguments
## Performance Tips
1. **Batch Operations**: Add multiple documents in quick succession if needed
1. **Batch Operations**: Use `documents.batchAdd()` for up to 600 documents per call
2. **Async/Await**: Always use async operations to avoid blocking
3. **Pagination**: Use `limit` and 1-based `page` for large document lists
3. **Pagination**: Use `limit` (max 100) and 1-based `page` for large lists
4. **Caching**: Cache profile() results for short periods if making multiple calls
## Support

View file

@ -28,17 +28,13 @@ import { generateText } from 'ai';
const memory = new Supermemory();
async function chat(userId: string, message: string) {
async function chat(userId: string, conversationId: string, message: string) {
// 1. Retrieve user context
const response = await memory.profile({
containerTag: userId,
q: message,
threshold: 0.6
});
const { profile } = await memory.profile(userId);
// 2. Build system prompt with personalization
const staticFacts = response.profile.static.map(f => `- ${f}`).join('\n');
const dynamicFacts = response.profile.dynamic.map(f => `- ${f}`).join('\n');
const staticFacts = profile.static.map(m => `- ${m.memory}`).join('\n');
const dynamicFacts = profile.dynamic.map(m => `- ${m.memory}`).join('\n');
const systemPrompt = `
You are a helpful assistant with perfect memory.
@ -59,13 +55,12 @@ Use this context to provide personalized, contextually aware responses.
prompt: message
});
// 4. Store the interaction
await memory.add({
// 4. Store the interaction; the same id keeps the conversation in one document
await memory.add(userId, {
content: `User: ${message}\nAssistant: ${text}`,
containerTag: userId,
id: conversationId,
metadata: {
timestamp: new Date().toISOString(),
messageId: crypto.randomUUID(),
type: 'conversation'
}
});
@ -74,7 +69,7 @@ Use this context to provide personalized, contextually aware responses.
}
// Usage
const response = await chat('user_123', 'What did I tell you about my preferences?');
const response = await chat('user_123', 'conv_1', 'What did I tell you about my preferences?');
console.log(response); // Uses stored context to answer accurately
```
@ -90,15 +85,11 @@ openai_client = OpenAI()
def chat(user_id: str, message: str) -> str:
# 1. Retrieve context
response = memory.profile(
container_tag=user_id,
q=message,
threshold=0.6
)
profile = memory.profile(user_id).profile
# 2. Build system prompt
static_facts = "\n".join(f"- {fact}" for fact in response['profile']['static'])
dynamic_facts = "\n".join(f"- {fact}" for fact in response['profile']['dynamic'])
static_facts = "\n".join(f"- {m.memory}" for m in profile.static)
dynamic_facts = "\n".join(f"- {m.memory}" for m in profile.dynamic)
system_prompt = f"""
You are a helpful assistant with perfect memory.
@ -124,8 +115,8 @@ Use this context to provide personalized responses.
# 4. Store interaction
memory.add(
user_id,
content=f"User: {message}\nAssistant: {text}",
container_tag=user_id,
metadata={
"timestamp": datetime.datetime.now().isoformat(),
"type": "conversation"
@ -165,14 +156,10 @@ interface Task {
async function taskAssistant(userId: string, query: string) {
// Get task-related context
const response = await memory.profile({
containerTag: `${userId}_tasks`,
q: query,
threshold: 0.5
});
const { profile } = await memory.profile(`${userId}_tasks`);
// Build context from profile
const tasks = response.profile.dynamic.map(f => `- ${f}`).join('\n');
const tasks = profile.dynamic.map(m => `- ${m.memory}`).join('\n');
// Generate intelligent response
const { text } = await generateText({
@ -192,10 +179,9 @@ Help the user track, prioritize, and complete their tasks.
}
async function addTask(userId: string, task: Task) {
await memory.add({
await memory.add(`${userId}_tasks`, {
content: `Task: ${task.title} (Status: ${task.status}, Priority: ${task.priority})`,
containerTag: `${userId}_tasks`,
customId: task.id,
id: task.id,
metadata: {
status: task.status,
priority: task.priority,
@ -205,15 +191,13 @@ async function addTask(userId: string, task: Task) {
}
async function updateTask(userId: string, taskId: string, status: Task['status']) {
// Add update (Supermemory will create relationship)
await memory.add({
// Same id appends the update to the task's document (Supermemory will create relationship)
await memory.add(`${userId}_tasks`, {
content: `Task ${taskId} updated to status: ${status}`,
containerTag: `${userId}_tasks`,
id: taskId,
metadata: {
taskId,
status,
updatedAt: new Date().toISOString(),
type: 'update'
updatedAt: new Date().toISOString()
}
});
}
@ -262,9 +246,8 @@ async function indexDocumentation() {
];
for (const doc of docs) {
await memory.add({
await memory.add('documentation', {
content: doc.url,
containerTag: 'documentation',
metadata: {
category: doc.category,
type: 'documentation',
@ -276,22 +259,18 @@ async function indexDocumentation() {
// 2. Search documentation
async function searchDocs(query: string, category?: string) {
const filters = category ? {
metadata: { category }
} : undefined;
const results = await memory.search.memories({
q: query,
containerTag: 'documentation',
const { results } = await memory.search('documentation', {
query,
searchMode: 'hybrid', // Use hybrid search for better RAG accuracy
threshold: 0.3,
limit: 10,
filters
filter: category
? { field: 'category', operator: 'eq', value: category }
: undefined
});
return results.map(r => ({
content: r.content,
relevance: r.score,
content: r.memory ?? r.chunk,
metadata: r.metadata
}));
}
@ -368,9 +347,8 @@ interface Ticket {
// 1. Store customer profile
async function createCustomer(customer: Customer) {
await memory.add({
await memory.add(customer.id, {
content: `Customer: ${customer.name} (${customer.email}), Plan: ${customer.plan}`,
containerTag: customer.id,
metadata: {
type: 'profile',
plan: customer.plan,
@ -381,10 +359,9 @@ async function createCustomer(customer: Customer) {
// 2. Log support ticket
async function createTicket(ticket: Ticket) {
await memory.add({
await memory.add(ticket.customerId, {
content: `Ticket ${ticket.id}: ${ticket.subject}\n${ticket.description}`,
containerTag: ticket.customerId,
customId: ticket.id,
id: ticket.id,
metadata: {
type: 'ticket',
status: ticket.status,
@ -396,12 +373,13 @@ async function createTicket(ticket: Ticket) {
// 3. Resolve ticket
async function resolveTicket(customerId: string, ticketId: string, resolution: string) {
await memory.add({
// Same id appends the resolution to the ticket's document
await memory.add(customerId, {
content: `Ticket ${ticketId} resolved: ${resolution}`,
containerTag: customerId,
id: ticketId,
metadata: {
type: 'resolution',
ticketId,
type: 'ticket',
status: 'resolved',
resolvedAt: new Date().toISOString()
}
});
@ -409,14 +387,10 @@ async function resolveTicket(customerId: string, ticketId: string, resolution: s
// 4. Support agent assistant
async function supportAssistant(customerId: string, query: string) {
const response = await memory.profile({
containerTag: customerId,
q: query,
threshold: 0.5
});
const { profile } = await memory.profile(customerId);
const staticInfo = response.profile.static.map(f => `- ${f}`).join('\n');
const recentTickets = response.profile.dynamic.map(f => `- ${f}`).join('\n');
const staticInfo = profile.static.map(m => `- ${m.memory}`).join('\n');
const recentTickets = profile.dynamic.map(m => `- ${m.memory}`).join('\n');
const { text } = await generateText({
model: openai('gpt-4'),
@ -493,10 +467,10 @@ async function indexCodebase(projectId: string, directory: string) {
const content = fs.readFileSync(file, 'utf-8');
const relativePath = path.relative(directory, file);
await memory.add({
// Re-indexing a changed file: use documents.update() to replace its content
await memory.add(`${projectId}_codebase`, {
content: `File: ${relativePath}\n\n${content}`,
containerTag: `${projectId}_codebase`,
customId: relativePath,
id: relativePath.replace(/[^A-Za-z0-9_-]/g, '_'),
metadata: {
type: 'source_file',
language: path.extname(file).slice(1),
@ -509,10 +483,9 @@ async function indexCodebase(projectId: string, directory: string) {
// 2. Index pull requests and reviews
async function indexPR(projectId: string, prNumber: number, diff: string, comments: string[]) {
await memory.add({
await memory.add(`${projectId}_reviews`, {
content: `PR #${prNumber}\n\nDiff:\n${diff}\n\nComments:\n${comments.join('\n')}`,
containerTag: `${projectId}_reviews`,
customId: `pr_${prNumber}`,
id: `pr_${prNumber}`,
metadata: {
type: 'pull_request',
number: prNumber,
@ -524,17 +497,16 @@ async function indexPR(projectId: string, prNumber: number, diff: string, commen
// 3. Review code with context
async function reviewCode(projectId: string, code: string, fileName: string) {
// Search for similar code patterns
const similarCode = await memory.search.memories({
q: code,
containerTag: `${projectId}_codebase`,
const { results: similarCode } = await memory.search(`${projectId}_codebase`, {
query: code,
searchMode: 'chunks',
threshold: 0.3,
limit: 5
});
// Get past review comments
const pastReviews = await memory.search.memories({
q: `code review comments for ${fileName}`,
containerTag: `${projectId}_reviews`,
const { results: pastReviews } = await memory.search(`${projectId}_reviews`, {
query: `code review comments for ${fileName}`,
threshold: 0.3,
limit: 5
});
@ -545,10 +517,10 @@ async function reviewCode(projectId: string, code: string, fileName: string) {
You are a code review assistant familiar with this codebase.
Similar Code Patterns:
${similarCode.map(c => c.content).slice(0, 3).join('\n\n---\n\n')}
${similarCode.map(c => c.chunk).slice(0, 3).join('\n\n---\n\n')}
Past Review Patterns:
${pastReviews.map(p => p.content).slice(0, 3).join('\n\n---\n\n')}
${pastReviews.map(p => p.memory ?? p.chunk).slice(0, 3).join('\n\n---\n\n')}
Provide a thoughtful code review, considering existing patterns and past feedback.
`,
@ -608,14 +580,13 @@ interface LearningSession {
}
async function recordLearningSession(session: LearningSession) {
await memory.add({
await memory.add(session.studentId, {
content: `
Topic: ${session.topic}
Understanding: ${session.understanding}
Content covered: ${session.content}
Questions asked: ${session.questions.join(', ')}
`,
containerTag: session.studentId,
metadata: {
type: 'learning_session',
topic: session.topic,
@ -626,14 +597,10 @@ Questions asked: ${session.questions.join(', ')}
}
async function adaptiveTutor(studentId: string, question: string) {
const response = await memory.profile({
containerTag: studentId,
q: question,
threshold: 0.5
});
// Get learning history from search results (if available)
const searchResults = response.searchResults?.results || [];
const [{ profile }, { results: searchResults }] = await Promise.all([
memory.profile(studentId),
memory.search(studentId, { query: question, searchMode: 'chunks', threshold: 0.5 })
]);
// Analyze learning patterns from metadata
const weakTopics = searchResults
@ -644,8 +611,8 @@ async function adaptiveTutor(studentId: string, question: string) {
.filter(r => r.metadata?.understanding === 'high')
.map(r => r.metadata?.topic);
const staticInfo = response.profile.static.map(f => `- ${f}`).join('\n');
const recentLearning = response.profile.dynamic.slice(0, 5).map(f => f).join('\n\n');
const staticInfo = profile.static.map(m => `- ${m.memory}`).join('\n');
const recentLearning = profile.dynamic.slice(0, 5).map(m => m.memory).join('\n\n');
const { text } = await generateText({
model: openai('gpt-4'),
@ -724,8 +691,8 @@ interface User {
name: string;
}
// Container tag strategy
function getContainerTags(orgId: string, userId: string) {
// Namespace strategy: one namespace per isolation boundary
function getNamespaces(orgId: string, userId: string) {
return {
org: `org_${orgId}`,
user: `org_${orgId}_user_${userId}`,
@ -735,11 +702,10 @@ function getContainerTags(orgId: string, userId: string) {
// 1. Store organization-wide knowledge
async function addOrgKnowledge(orgId: string, content: string) {
const tags = getContainerTags(orgId, '');
const namespaces = getNamespaces(orgId, '');
await memory.add({
await memory.add(namespaces.shared, {
content,
containerTag: tags.shared,
metadata: {
type: 'org_knowledge',
visibility: 'organization'
@ -749,11 +715,10 @@ async function addOrgKnowledge(orgId: string, content: string) {
// 2. Store user-specific data
async function addUserData(orgId: string, userId: string, content: string) {
const tags = getContainerTags(orgId, userId);
const namespaces = getNamespaces(orgId, userId);
await memory.add({
await memory.add(namespaces.user, {
content,
containerTag: tags.user,
metadata: {
type: 'user_data',
visibility: 'private'
@ -763,20 +728,20 @@ async function addUserData(orgId: string, userId: string, content: string) {
// 3. Search with proper isolation
async function search(orgId: string, userId: string, query: string, includeShared: boolean = true) {
const tags = getContainerTags(orgId, userId);
const namespaces = getNamespaces(orgId, userId);
const containerTags = includeShared
? [tags.user, tags.shared] // User + org shared
: [tags.user]; // User only
const targets = includeShared
? [namespaces.user, namespaces.shared] // User + org shared
: [namespaces.user]; // User only
const results = await memory.search.memories({
q: query,
containerTag: containerTags[0], // Use first tag
threshold: 0.3,
limit: 10
});
// One search per namespace, then merge
const responses = await Promise.all(
targets.map(namespace =>
memory.search(namespace, { query, threshold: 0.3, limit: 10 })
)
);
return results;
return responses.flatMap(r => r.results);
}
// Usage
@ -833,7 +798,7 @@ interface Paper {
}
async function addPaper(userId: string, paper: Paper) {
await memory.add({
await memory.add(`${userId}_research`, {
content: `
Title: ${paper.title}
Authors: ${paper.authors.join(', ')}
@ -841,8 +806,7 @@ Year: ${paper.year}
Abstract: ${paper.abstract}
URL: ${paper.url}
`,
containerTag: `${userId}_research`,
customId: paper.url,
id: paper.url.replace(/[^A-Za-z0-9_-]/g, '_'),
metadata: {
type: 'paper',
year: paper.year,
@ -853,9 +817,8 @@ URL: ${paper.url}
}
async function addResearchNote(userId: string, note: string, relatedPapers: string[]) {
await memory.add({
await memory.add(`${userId}_research`, {
content: note,
containerTag: `${userId}_research`,
metadata: {
type: 'note',
relatedPapers,
@ -865,9 +828,9 @@ async function addResearchNote(userId: string, note: string, relatedPapers: stri
}
async function findRelatedResearch(userId: string, topic: string) {
const results = await memory.search.memories({
q: topic,
containerTag: `${userId}_research`,
const { results } = await memory.search(`${userId}_research`, {
query: topic,
searchMode: 'chunks',
threshold: 0.3,
limit: 20
});
@ -884,9 +847,9 @@ async function synthesizeInsights(userId: string, research_question: string) {
const context = [
'=== Related Papers ===',
...related.papers.map(p => p.content),
...related.papers.map(p => p.chunk),
'\n=== Your Notes ===',
...related.notes.map(n => n.content)
...related.notes.map(n => n.chunk)
].join('\n\n');
const { text } = await generateText({
@ -951,14 +914,11 @@ const synthesis = await synthesizeInsights(
```typescript
// Always retrieve context first
const response = await memory.profile({
containerTag: userId,
q: userMessage
});
const { profile } = await memory.profile(userId);
// Then use in generation
const staticFacts = response.profile.static.join('\n');
const dynamicFacts = response.profile.dynamic.join('\n');
const staticFacts = profile.static.map(m => m.memory).join('\n');
const dynamicFacts = profile.dynamic.map(m => m.memory).join('\n');
const llmResponse = await generateText({
system: `User Profile:\n${staticFacts}\n\nRecent Context:\n${dynamicFacts}`,
@ -970,9 +930,8 @@ const llmResponse = await generateText({
```typescript
// Always store the result
await memory.add({
await memory.add(userId, {
content: `Input: ${input}\nOutput: ${output}`,
containerTag: userId,
metadata: { timestamp: new Date().toISOString() }
});
```
@ -980,9 +939,8 @@ await memory.add({
### Pattern 3: Rich Metadata for Filtering
```typescript
await memory.add({
await memory.add(userId, {
content: data,
containerTag: userId,
metadata: {
type: 'conversation',
category: 'support',
@ -993,35 +951,36 @@ await memory.add({
});
// Later filter by metadata
const results = await memory.search.memories({
q: 'billing issues',
containerTag: 'user_123',
filters: {
metadata: { priority: 'high', type: 'conversation' }
const { results } = await memory.search('user_123', {
query: 'billing issues',
filter: {
operator: 'and',
operands: [
{ field: 'priority', operator: 'eq', value: 'high' },
{ field: 'type', operator: 'eq', value: 'conversation' }
]
}
});
```
### Pattern 4: Hierarchical Container Tags
### Pattern 4: Hierarchical Namespaces
```typescript
// Organization → Team → User hierarchy
const tags = {
// Organization → Team → User hierarchy; each level is its own namespace
const namespaces = {
org: `org_${orgId}`,
team: `org_${orgId}_team_${teamId}`,
user: `org_${orgId}_team_${teamId}_user_${userId}`
};
// Search at appropriate level
const orgWide = await memory.search.memories({
q: 'company policies',
containerTag: tags.org,
const orgWide = await memory.search(namespaces.org, {
query: 'company policies',
limit: 10
});
const teamSpecific = await memory.search.memories({
q: 'team resources',
containerTag: tags.team,
const teamSpecific = await memory.search(namespaces.team, {
query: 'team resources',
limit: 10
});
```