From 4caf81225320a7b863c91284b2aeee80bb696d4d Mon Sep 17 00:00:00 2001 From: Mahesh Sanikommu Date: Tue, 6 Oct 2026 11:39:58 -0700 Subject: [PATCH] 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. --- skills/supermemory/README.md | 20 +- skills/supermemory/SKILL.md | 90 ++- .../supermemory/references/api-reference.md | 366 ++++++------ skills/supermemory/references/architecture.md | 57 +- skills/supermemory/references/quickstart.md | 128 ++--- skills/supermemory/references/sdk-guide.md | 534 +++++++++--------- skills/supermemory/references/use-cases.md | 245 ++++---- 7 files changed, 662 insertions(+), 778 deletions(-) diff --git a/skills/supermemory/README.md b/skills/supermemory/README.md index 03cac7b4f..7c4f26207 100644 --- a/skills/supermemory/README.md +++ b/skills/supermemory/README.md @@ -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. diff --git a/skills/supermemory/SKILL.md b/skills/supermemory/SKILL.md index e9a4e142f..4129a28de 100644 --- a/skills/supermemory/SKILL.md +++ b/skills/supermemory/SKILL.md @@ -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 - -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`. +**Python:** there is no v5 Python tools package yet. Call the Python SDK (`pip install supermemory`) from your own function-calling handlers. -**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 diff --git a/skills/supermemory/references/api-reference.md b/skills/supermemory/references/api-reference.md index 791c303d7..39a31076d 100644 --- a/skills/supermemory/references/api-reference.md +++ b/skills/supermemory/references/api-reference.md @@ -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,102 +226,83 @@ 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`. -```json -{ - "error": "Bad Request", - "details": "Invalid request parameters: memories array must contain 1-100 items" -} +--- + +### 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"]}' ``` -**Response (404 Not Found):** +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": "Not Found", - "details": "Space not found for given containerTag" + "count": 1, + "matches": [{ "id": "mem_1", "memory": "Old address" }], + "errors": [{ "id": "mem_2", "error": "Memory not found" }] } ``` --- +### 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) | + +--- + ## Error Handling ### HTTP Status Codes @@ -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) diff --git a/skills/supermemory/references/architecture.md b/skills/supermemory/references/architecture.md index 61c35e6c7..bfc73306e 100644 --- a/skills/supermemory/references/architecture.md +++ b/skills/supermemory/references/architecture.md @@ -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 diff --git a/skills/supermemory/references/quickstart.md b/skills/supermemory/references/quickstart.md index 4be0a762e..b9991259b 100644 --- a/skills/supermemory/references/quickstart.md +++ b/skills/supermemory/references/quickstart.md @@ -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:", profile.static); + console.log("Dynamic Profile:", profile.dynamic); - console.log("Static Profile:", response.profile.static); - console.log("Dynamic Profile:", response.profile.dynamic); - if (response.searchResults) { - console.log("Search Results:", response.searchResults.results); - } + // 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 diff --git a/skills/supermemory/references/sdk-guide.md b/skills/supermemory/references/sdk-guide.md index e57e54213..3145d1cfd 100644 --- a/skills/supermemory/references/sdk-guide.md +++ b/skills/supermemory/references/sdk-guide.md @@ -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,29 +39,29 @@ 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 +## Namespaces -async_client = AsyncSupermemory( - api_key=os.environ["SUPERMEMORY_API_KEY"] -) -``` +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 @@ -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 // 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, // 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> // } // } ``` #### 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 }); ``` -**Search with metadata filters:** +**Source chunks only (document search):** ```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", + searchMode: "chunks" }); ``` -**Search within a filepath:** +**Search with metadata filters:** ```typescript -const response = await client.search({ - q: "rate limiting configuration", - containerTag: "specific_project", - filepath: "/docs/api.md" +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" } + ] + } }); ``` -### `documents.list()` - List Documents +### `list()` - List Documents, Chunks, or Memories -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 ```typescript -await client.documents.delete(documentId); +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 } +}); ``` -#### Python -```python -client.documents.delete(document_id) +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 +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 diff --git a/skills/supermemory/references/use-cases.md b/skills/supermemory/references/use-cases.md index db80e0d6a..bdc6de1df 100644 --- a/skills/supermemory/references/use-cases.md +++ b/skills/supermemory/references/use-cases.md @@ -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 }); ```