From 5c24e67d60c1946ce1494cd6d5ddf611ebf4d997 Mon Sep 17 00:00:00 2001
From: MaheshtheDev <38828053+MaheshtheDev@users.noreply.github.com>
Date: Tue, 6 Oct 2026 17:03:47 +0000
Subject: [PATCH] feat(tools)!: @supermemory/tools 3.0 on the v5 API, retire
@supermemory/ai-sdk (#1773)
Every integration (AI SDK, OpenAI, Mastra, VoltAgent, Claude memory) now calls v5 through `supermemory@5`. The config takes one `namespace` instead of `containerTags` or `projectId`, and `withSupermemory` takes `namespace` and `id` instead of `containerTag` and `customId`. No aliases. With no config the tools still use `sm_project_default`.
Conversations are stored as one document per conversation keyed by `id` instead of `/v4/conversations`. `memoryForget` drops `reason`; forgetting by text previews with `forgetMatching` and then forgets exact matches by id. Claude memory marks its files with `metadata.source` instead of a second tag. Search keeps the 2.x defaults so results do not shift.
`packages/ai-sdk` is removed: npm already deprecates it in favor of `@supermemory/tools/ai-sdk`, so its CI steps and playground wiring go too. `apps/sdk-playground` moves to the new tools API and v5 routes, including its Python server.
Tested against production: add, search, profile, list and delete. 112 unit tests pass; type errors drop from 148 to 141, none new. The `supermemory` dependency pins the rc until 5.0.0 is published.
---
.github/workflows/ci.yml | 20 +-
apps/sdk-playground/README.md | 8 +-
apps/sdk-playground/next.config.ts | 2 +-
apps/sdk-playground/package.json | 7 +-
apps/sdk-playground/python/pyproject.toml | 1 -
apps/sdk-playground/python/server.py | 296 ++++++-----
apps/sdk-playground/python/uv.lock | 10 +-
apps/sdk-playground/src/app/api/chat/route.ts | 9 +-
.../src/app/api/context/route.ts | 16 +-
apps/sdk-playground/src/app/page.tsx | 14 +-
.../src/components/ContextPanel.tsx | 45 +-
apps/sdk-playground/src/lib/chat-handlers.ts | 63 +--
apps/sdk-playground/src/lib/context-api.ts | 151 +++---
.../src/lib/request-validation.ts | 18 +-
apps/sdk-playground/src/lib/sdk-registry.ts | 9 -
apps/sdk-playground/src/lib/tools-catalog.ts | 23 +-
bun.lock | 115 +----
packages/ai-sdk/.npmignore | 5 -
packages/ai-sdk/README.md | 318 ------------
packages/ai-sdk/package.json | 47 --
packages/ai-sdk/src/index.ts | 1 -
packages/ai-sdk/src/tools.test.ts | 169 ------
packages/ai-sdk/src/tools.ts | 17 -
packages/ai-sdk/tsconfig.json | 12 -
packages/ai-sdk/tsdown.config.ts | 16 -
packages/tools/README.md | 240 ++++++---
packages/tools/package.json | 4 +-
packages/tools/src/ai-sdk.ts | 180 ++-----
.../tools/src/claude-memory-simple-example.ts | 2 +-
packages/tools/src/claude-memory.test.ts | 115 +++--
packages/tools/src/claude-memory.ts | 407 ++++-----------
packages/tools/src/conversations-client.ts | 127 +++--
packages/tools/src/index.ts | 2 +-
packages/tools/src/mastra/processor.ts | 62 +--
packages/tools/src/mastra/types.ts | 8 +-
packages/tools/src/mastra/wrapper.ts | 12 +-
packages/tools/src/openai/index.ts | 20 +-
packages/tools/src/openai/middleware.ts | 246 +++------
packages/tools/src/openai/tools.ts | 210 ++------
packages/tools/src/shared/cache.ts | 6 +-
packages/tools/src/shared/context.ts | 4 +-
packages/tools/src/shared/forget-memory.ts | 45 --
packages/tools/src/shared/index.ts | 2 +
.../tools/src/shared/memory-client.test.ts | 50 +-
packages/tools/src/shared/memory-client.ts | 101 ++--
packages/tools/src/shared/types.ts | 41 +-
packages/tools/src/tool-operations.test.ts | 484 ++++++++++++------
packages/tools/src/tools-descriptions.ts | 39 ++
packages/tools/src/tools-shared.test.ts | 30 +-
packages/tools/src/tools-shared.ts | 379 +++++---------
packages/tools/src/tools.test.ts | 50 +-
packages/tools/src/types.ts | 24 +-
packages/tools/src/vercel/index.ts | 34 +-
packages/tools/src/vercel/memory-prompt.ts | 4 +-
packages/tools/src/vercel/middleware.ts | 53 +-
packages/tools/src/vercel/util.ts | 2 +-
packages/tools/src/voltagent/hooks.ts | 8 +-
packages/tools/src/voltagent/index.ts | 43 +-
packages/tools/src/voltagent/middleware.ts | 97 ++--
packages/tools/src/voltagent/options.ts | 50 +-
packages/tools/src/voltagent/types.ts | 2 +-
packages/tools/test/ai-sdk-test.ts | 4 +-
packages/tools/test/anthropic-example.ts | 6 +-
.../tools/test/chatapp/app/api/chat/route.ts | 4 +-
.../test/chatapp/app/api/openai-chat/route.ts | 4 +-
.../test/chatapp/app/api/stream/route.ts | 4 +-
packages/tools/test/claude-memory-examples.ts | 49 +-
.../tools/test/claude-memory-real-example.ts | 18 +-
packages/tools/test/claude-memory.test.ts | 9 +-
.../tools/test/mastra/integration.test.ts | 94 ++--
packages/tools/test/mastra/unit.test.ts | 352 ++++++-------
.../tools/test/openai-middleware.unit.test.ts | 13 +-
packages/tools/test/openai-responses-test.ts | 4 +-
packages/tools/test/test-memory-tool.ts | 3 +-
packages/tools/test/v5-fetch.ts | 66 +++
packages/tools/test/voltagent.unit.test.ts | 11 +-
.../conversation-conversion.test.ts | 81 ++-
.../test/with-supermemory/integration.test.ts | 106 ++--
.../tools/test/with-supermemory/unit.test.ts | 190 +++----
skills/supermemory/SKILL.md | 3 +-
skills/supermemory/references/sdk-guide.md | 2 +-
81 files changed, 2181 insertions(+), 3417 deletions(-)
delete mode 100644 packages/ai-sdk/.npmignore
delete mode 100644 packages/ai-sdk/README.md
delete mode 100644 packages/ai-sdk/package.json
delete mode 100644 packages/ai-sdk/src/index.ts
delete mode 100644 packages/ai-sdk/src/tools.test.ts
delete mode 100644 packages/ai-sdk/src/tools.ts
delete mode 100644 packages/ai-sdk/tsconfig.json
delete mode 100644 packages/ai-sdk/tsdown.config.ts
delete mode 100644 packages/tools/src/shared/forget-memory.ts
create mode 100644 packages/tools/src/tools-descriptions.ts
create mode 100644 packages/tools/test/v5-fetch.ts
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 1a81e979..861dc706 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -41,12 +41,6 @@ jobs:
echo "tools=true" >> "$GITHUB_OUTPUT"
fi
- if git diff --quiet "${{ github.event.pull_request.base.sha }}" HEAD -- packages/ai-sdk; then
- echo "ai_sdk=false" >> "$GITHUB_OUTPUT"
- else
- echo "ai_sdk=true" >> "$GITHUB_OUTPUT"
- fi
-
if git diff --quiet "${{ github.event.pull_request.base.sha }}" HEAD -- apps/sdk-playground; then
echo "sdk_playground=false" >> "$GITHUB_OUTPUT"
else
@@ -81,21 +75,9 @@ jobs:
run: bun run --cwd packages/tools test:unit
- name: Build Tools package
- if: steps.sdk-changes.outputs.tools == 'true' || steps.sdk-changes.outputs.ai_sdk == 'true' || steps.sdk-changes.outputs.sdk_playground == 'true'
+ if: steps.sdk-changes.outputs.tools == 'true' || steps.sdk-changes.outputs.sdk_playground == 'true'
run: bun run --cwd packages/tools build
- - name: Run AI SDK type checking
- if: steps.sdk-changes.outputs.tools == 'true' || steps.sdk-changes.outputs.ai_sdk == 'true' || steps.sdk-changes.outputs.sdk_playground == 'true'
- run: bun run --cwd packages/ai-sdk check-types
-
- - name: Run AI SDK unit tests
- if: steps.sdk-changes.outputs.ai_sdk == 'true'
- run: bun run --cwd packages/ai-sdk test:unit
-
- - name: Build AI SDK package
- if: steps.sdk-changes.outputs.ai_sdk == 'true' || steps.sdk-changes.outputs.sdk_playground == 'true'
- run: bun run --cwd packages/ai-sdk build
-
- name: Run SDK Playground type checking
if: steps.sdk-changes.outputs.sdk_playground == 'true'
run: bun run --cwd apps/sdk-playground check-types:app
diff --git a/apps/sdk-playground/README.md b/apps/sdk-playground/README.md
index 91514f4f..fccd1358 100644
--- a/apps/sdk-playground/README.md
+++ b/apps/sdk-playground/README.md
@@ -6,7 +6,7 @@ Chat with a **real agent** and switch which Supermemory SDK integration powers i
> This is a local, single-user development tool. It makes real API calls, stores
> browser-entered keys only in memory unless you opt into tab-scoped
> `sessionStorage`, and exposes tools that can permanently delete documents. Use
-> disposable development credentials and a test container; do not deploy it or
+> disposable development credentials and a test namespace; do not deploy it or
> point it at production data.
## Integrations
@@ -17,7 +17,6 @@ Chat with a **real agent** and switch which Supermemory SDK integration powers i
| OpenAI + middleware | automatic | same, via OpenAI client wrapper |
| AI SDK + tools | explicit | model calls 7 memory tools via `generateText` |
| OpenAI + tools | explicit | OpenAI function-calling loop |
-| `@supermemory/ai-sdk` | explicit | re-export of tools/ai-sdk |
| Python OpenAI middleware | automatic | `with_supermemory` |
| Python OpenAI tools | explicit | `SupermemoryTools` loop |
| Python supermemory direct | manual | `profile()` + OpenAI + `add()` |
@@ -39,9 +38,8 @@ cp apps/sdk-playground/.env.example apps/sdk-playground/.env.local
# OPENAI_API_KEY=...
```
-The playground scripts build `@supermemory/tools` first and
-`@supermemory/ai-sdk` second before starting, type-checking, or building the
-Next.js app. Development mode also watches both workspace packages.
+The playground scripts build `@supermemory/tools` before starting, type-checking, or building the
+Next.js app. Development mode also watches the workspace package.
## Run
diff --git a/apps/sdk-playground/next.config.ts b/apps/sdk-playground/next.config.ts
index 94cfb40e..52f142cb 100644
--- a/apps/sdk-playground/next.config.ts
+++ b/apps/sdk-playground/next.config.ts
@@ -1,7 +1,7 @@
import type { NextConfig } from "next"
const nextConfig: NextConfig = {
- transpilePackages: ["@supermemory/tools", "@supermemory/ai-sdk"],
+ transpilePackages: ["@supermemory/tools"],
}
export default nextConfig
diff --git a/apps/sdk-playground/package.json b/apps/sdk-playground/package.json
index f1430084..e12c03a7 100644
--- a/apps/sdk-playground/package.json
+++ b/apps/sdk-playground/package.json
@@ -5,10 +5,10 @@
"portless": { "name": "sdk.dev.supermemory", "script": "dev:app" },
"scripts": {
"dev": "portless",
- "dev:app": "bun run build:dependencies && concurrently -k -n tools,ai-sdk,next,py -c yellow,magenta,blue,green \"bun run --cwd ../../packages/tools dev\" \"bun run --cwd ../../packages/ai-sdk dev\" \"next dev --hostname 127.0.0.1 --port ${PORT:-3005}\" \"bun run dev:python\"",
+ "dev:app": "bun run build:dependencies && concurrently -k -n tools,next,py -c yellow,blue,green \"bun run --cwd ../../packages/tools dev\" \"next dev --hostname 127.0.0.1 --port ${PORT:-3005}\" \"bun run dev:python\"",
"dev:next": "bun run build:dependencies && next dev --hostname 127.0.0.1 --port ${PORT:-3005}",
"dev:python": "cd python && uv run server.py",
- "build:dependencies": "bun run --cwd ../../packages/tools build && bun run --cwd ../../packages/ai-sdk build",
+ "build:dependencies": "bun run --cwd ../../packages/tools build",
"build:app": "next build",
"build": "bun run build:dependencies && bun run build:app",
"start": "concurrently -k -n next,py -c blue,green \"next start --hostname 127.0.0.1 --port ${PORT:-3005}\" \"bun run dev:python\"",
@@ -18,14 +18,13 @@
},
"dependencies": {
"@ai-sdk/openai": "^2.0.22",
- "@supermemory/ai-sdk": "workspace:*",
"@supermemory/tools": "workspace:*",
"ai": "^5.0.113",
"next": "^16.3.3",
"openai": "^4.104.0",
"react": "19.2.0",
"react-dom": "19.2.0",
- "supermemory": "^4.25.4"
+ "supermemory": "^5.0.0"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
diff --git a/apps/sdk-playground/python/pyproject.toml b/apps/sdk-playground/python/pyproject.toml
index 6b5aaf69..7ec60299 100644
--- a/apps/sdk-playground/python/pyproject.toml
+++ b/apps/sdk-playground/python/pyproject.toml
@@ -7,7 +7,6 @@ dependencies = [
"httpx>=0.28.0",
"uvicorn[standard]>=0.32.0",
"python-dotenv>=1.0.1",
- "supermemory>=3.50.0",
"openai>=1.102.0",
"supermemory-openai-sdk[async]",
]
diff --git a/apps/sdk-playground/python/server.py b/apps/sdk-playground/python/server.py
index 842e3d7e..322e62fa 100644
--- a/apps/sdk-playground/python/server.py
+++ b/apps/sdk-playground/python/server.py
@@ -8,7 +8,7 @@ import re
import time
from pathlib import Path
from typing import Annotated, Any, Literal, Optional
-from urllib.parse import urlparse
+from urllib.parse import quote, urlparse
from dotenv import load_dotenv
from fastapi import FastAPI, Header, Query
@@ -27,13 +27,16 @@ CHAT_TIMEOUT_SECONDS = 115.0
CONTEXT_DEBUG_TIMEOUT_SECONDS = 10.0
DIRECT_SAVE_TIMEOUT_SECONDS = 10.0
MAX_OUTPUT_TOKENS = 2_048
+DOCUMENT_LIST_LIMIT = 25
+# Matches the v4 profile search defaults the TS middlewares keep (memories mode, 0.6).
+PROFILE_SEARCH_THRESHOLD = 0.6
MAX_MESSAGE_LENGTH = 20_000
MAX_MESSAGES = 64
MAX_TOTAL_MESSAGE_LENGTH = 100_000
MAX_API_KEY_LENGTH = 1_024
-MAX_CONTAINER_TAG_LENGTH = 100
+MAX_NAMESPACE_LENGTH = 100
MAX_CONVERSATION_ID_LENGTH = 242
-CONTAINER_TAG_PATTERN = r"^[a-zA-Z0-9_:-]+$"
+NAMESPACE_PATTERN = r"^[a-zA-Z0-9_:-]+$"
TOOLS_SYSTEM_PROMPT = """You are a helpful assistant with Supermemory long-term memory.
@@ -82,11 +85,11 @@ class ChatRequest(BaseModel):
"py-supermemory-direct",
]
messages: list[ChatMessage] = Field(min_length=1, max_length=MAX_MESSAGES)
- containerTag: str = Field(
+ namespace: str = Field(
default="sdk-playground",
min_length=1,
- max_length=MAX_CONTAINER_TAG_LENGTH,
- pattern=CONTAINER_TAG_PATTERN,
+ max_length=MAX_NAMESPACE_LENGTH,
+ pattern=NAMESPACE_PATTERN,
)
conversationId: str = Field(
min_length=1,
@@ -114,11 +117,11 @@ class ChatRequest(BaseModel):
class ContextRequest(BaseModel):
- containerTag: str = Field(
+ namespace: str = Field(
default="sdk-playground",
min_length=1,
- max_length=MAX_CONTAINER_TAG_LENGTH,
- pattern=CONTAINER_TAG_PATTERN,
+ max_length=MAX_NAMESPACE_LENGTH,
+ pattern=NAMESPACE_PATTERN,
)
query: Optional[str] = Field(default=None, max_length=MAX_MESSAGE_LENGTH)
apiKeys: Optional[SupermemoryApiKeys] = None
@@ -172,7 +175,7 @@ def public_error(error: Exception, *secrets: str) -> str:
async def chat_openai_middleware(
messages: list[ChatMessage],
- container_tag: str,
+ namespace: str,
conversation_id: str,
memory_mode: str,
middleware_config: MiddlewareConfig,
@@ -189,7 +192,7 @@ async def chat_openai_middleware(
max_retries=1,
),
OpenAIMiddlewareOptions(
- container_tag=container_tag,
+ container_tag=namespace,
custom_id=conversation_id,
mode=memory_mode,
add_memory=middleware_config.addMemory,
@@ -221,7 +224,7 @@ async def chat_openai_middleware(
async def chat_openai_tools(
messages: list[ChatMessage],
- container_tag: str,
+ namespace: str,
sm_key: str,
oai_key: str,
) -> tuple[str, list[dict[str, Any]]]:
@@ -235,7 +238,7 @@ async def chat_openai_tools(
)
config: dict[str, Any] = {
"base_url": supermemory_base_url(),
- "container_tags": [container_tag],
+ "container_tags": [namespace],
}
tools = SupermemoryTools(sm_key, config)
@@ -296,24 +299,6 @@ def list_field(value: Any, name: str) -> list[Any]:
return result if isinstance(result, list) else []
-def extract_profile_context(profile_response: Any) -> dict[str, list[Any]]:
- profile = object_field(profile_response, "profile", {}) or {}
- search_results = object_field(profile_response, "search_results", None)
- if search_results is None and isinstance(profile_response, dict):
- search_results = profile_response.get("searchResults")
-
- if isinstance(search_results, list):
- search_list = search_results
- else:
- search_list = list_field(search_results, "results")
-
- return {
- "static": list_field(profile, "static"),
- "dynamic": list_field(profile, "dynamic"),
- "searchResults": search_list,
- }
-
-
def display_context_item(item: Any) -> str:
if hasattr(item, "model_dump"):
return json.dumps(item.model_dump(mode="json"), ensure_ascii=False)
@@ -322,7 +307,7 @@ def display_context_item(item: Any) -> str:
return str(item)
-def direct_conversation_custom_id(conversation_id: str) -> str:
+def direct_conversation_id(conversation_id: str) -> str:
readable = re.sub(r"[^A-Za-z0-9._-]+", "-", conversation_id).strip("-._")
readable = readable[:40] or "session"
digest = hashlib.sha256(conversation_id.encode("utf-8")).hexdigest()[:12]
@@ -339,45 +324,65 @@ def conversation_transcript(messages: list[ChatMessage], assistant_text: str) ->
return "\n\n".join(transcript)
+def supermemory_http(sm_key: str, timeout: float = HTTP_TIMEOUT_SECONDS):
+ import httpx
+
+ return httpx.AsyncClient(
+ base_url=supermemory_base_url(),
+ headers={"Authorization": f"Bearer {sm_key}"},
+ timeout=timeout,
+ follow_redirects=False,
+ )
+
+
+def namespace_path(namespace: str, suffix: str) -> str:
+ return f"/ns/{quote(namespace, safe='')}{suffix}"
+
+
async def fetch_profile_context(
- container_tag: str,
+ namespace: str,
sm_key: str,
query: Optional[str] = None,
- *,
- include: Optional[list[str]] = None,
) -> dict[str, list[Any]]:
- from supermemory import AsyncSupermemory
+ async with supermemory_http(sm_key) as http:
+ profile_request = http.post(namespace_path(namespace, "/profile"), json={})
+ search_request = (
+ http.post(
+ namespace_path(namespace, "/search"),
+ json={
+ "query": query,
+ "searchMode": "memories",
+ "threshold": PROFILE_SEARCH_THRESHOLD,
+ },
+ )
+ if query
+ else None
+ )
+ responses = await asyncio.gather(
+ profile_request, *([search_request] if search_request else [])
+ )
+ for response in responses:
+ response.raise_for_status()
- client = AsyncSupermemory(
- api_key=sm_key,
- base_url=supermemory_base_url(),
- timeout=HTTP_TIMEOUT_SECONDS,
- )
- request: dict[str, Any] = {"container_tag": container_tag}
- if query:
- request["q"] = query
- if include is not None:
- request["include"] = include
- profile_response = await client.profile(**request)
- return extract_profile_context(profile_response)
+ profile = responses[0].json().get("profile") or {}
+ search = responses[1].json() if len(responses) > 1 else {}
+ return {
+ "static": profile.get("static") or [],
+ "dynamic": profile.get("dynamic") or [],
+ "searchResults": search.get("results") or [],
+ }
async def chat_supermemory_direct(
messages: list[ChatMessage],
- container_tag: str,
+ namespace: str,
conversation_id: str,
sm_key: str,
oai_key: str,
) -> tuple[str, str, dict[str, list[Any]]]:
- """Manual pattern: profile() for context, then OpenAI, then add() conversation."""
+ """Manual pattern: profile + search for context, then OpenAI, then add the conversation."""
from openai import AsyncOpenAI
- from supermemory import AsyncSupermemory
- sm_client = AsyncSupermemory(
- api_key=sm_key,
- base_url=supermemory_base_url(),
- timeout=HTTP_TIMEOUT_SECONDS,
- )
openai_client = AsyncOpenAI(
api_key=oai_key,
timeout=HTTP_TIMEOUT_SECONDS,
@@ -387,11 +392,7 @@ async def chat_supermemory_direct(
user_messages = [m for m in messages if m.role == "user"]
last_user = user_messages[-1].content if user_messages else ""
- profile_response = await sm_client.profile(
- container_tag=container_tag,
- **({"q": last_user} if last_user else {}),
- )
- profile_context = extract_profile_context(profile_response)
+ profile_context = await fetch_profile_context(namespace, sm_key, last_user)
context = "\n".join(
(
"Profile static: "
@@ -418,40 +419,39 @@ async def chat_supermemory_direct(
)
assistant_text = response.choices[0].message.content or ""
- custom_id = direct_conversation_custom_id(conversation_id)
- return assistant_text, custom_id, profile_context
+ document_id = direct_conversation_id(conversation_id)
+ return assistant_text, document_id, profile_context
async def save_direct_conversation(
messages: list[ChatMessage],
assistant_text: str,
- container_tag: str,
- custom_id: str,
+ namespace: str,
+ document_id: str,
sm_key: str,
) -> dict[str, Any]:
- from supermemory import AsyncSupermemory
-
try:
- client = AsyncSupermemory(
- api_key=sm_key,
- base_url=supermemory_base_url(),
- timeout=DIRECT_SAVE_TIMEOUT_SECONDS,
- )
async with asyncio.timeout(DIRECT_SAVE_TIMEOUT_SECONDS):
- response = await client.add(
- content=conversation_transcript(messages, assistant_text),
- container_tag=container_tag,
- custom_id=custom_id,
- )
+ async with supermemory_http(sm_key, DIRECT_SAVE_TIMEOUT_SECONDS) as http:
+ response = await http.post(
+ namespace_path(namespace, "/document"),
+ json={
+ "content": conversation_transcript(messages, assistant_text),
+ "id": document_id,
+ "dreaming": "instant",
+ },
+ )
+ response.raise_for_status()
+ accepted = response.json()
return {
"type": "conversation_save_accepted",
"label": "Full conversation accepted for processing",
"detail": {
"nonFatal": True,
- "containerTag": container_tag,
- "customId": custom_id,
- "documentId": object_field(response, "id"),
- "status": object_field(response, "status"),
+ "namespace": namespace,
+ "id": document_id,
+ "documentId": accepted.get("id"),
+ "status": accepted.get("status"),
},
}
except Exception as error:
@@ -460,77 +460,74 @@ async def save_direct_conversation(
"label": "Conversation save unavailable",
"detail": {
"nonFatal": True,
- "containerTag": container_tag,
- "customId": custom_id,
+ "namespace": namespace,
+ "id": document_id,
"error": public_error(error, sm_key),
},
}
-async def fetch_container_context(
- container_tag: str,
+async def fetch_document_memories(http: Any, namespace: str, document_id: str) -> list[Any]:
+ try:
+ response = await http.get(
+ namespace_path(namespace, f"/document/{quote(document_id, safe='')}"),
+ params={"include": "memories"},
+ )
+ response.raise_for_status()
+ return response.json().get("memories") or []
+ except Exception:
+ return []
+
+
+async def fetch_namespace_context(
+ namespace: str,
sm_key: str,
query: Optional[str] = None,
) -> dict[str, Any]:
if not sm_key:
raise RuntimeError("Supermemory API key must be supplied")
- profile_context = await fetch_profile_context(container_tag, sm_key, query)
- base_url = supermemory_base_url()
+ profile_context = await fetch_profile_context(namespace, sm_key, query)
- import httpx
-
- async with httpx.AsyncClient(
- timeout=HTTP_TIMEOUT_SECONDS,
- follow_redirects=False,
- ) as http:
+ async with supermemory_http(sm_key) as http:
docs_response = await http.post(
- f"{base_url}/v3/documents/documents",
- headers={
- "Authorization": f"Bearer {sm_key}",
- "Content-Type": "application/json",
- },
- json={
- "containerTags": [container_tag],
- "limit": 25,
+ namespace_path(namespace, "/list/documents"),
+ params={
+ "limit": DOCUMENT_LIST_LIMIT,
"sort": "createdAt",
"order": "desc",
},
+ json={},
)
docs_response.raise_for_status()
docs = docs_response.json()
-
- raw_documents = docs.get("documents", []) if isinstance(docs, dict) else []
+ raw_documents = docs.get("documents") or []
+ # v5 lists omit memories, so each document is fetched with include=memories.
+ memories = await asyncio.gather(
+ *(fetch_document_memories(http, namespace, doc["id"]) for doc in raw_documents)
+ )
documents = []
- for doc in raw_documents:
- record = doc if isinstance(doc, dict) else getattr(doc, "__dict__", {})
- memory_entries = (
- record.get("memoryEntries") or record.get("memory_entries") or []
- )
- if not memory_entries and isinstance(record.get("memories"), list):
- nested = record.get("memories") or []
- if nested and isinstance(nested[0], dict) and nested[0].get("memory"):
- memory_entries = nested
+ for doc, memory_entries in zip(raw_documents, memories):
+ system = doc.get("system") or {}
documents.append(
{
- "id": record.get("id"),
- "title": record.get("title"),
- "status": record.get("status"),
- "customId": record.get("customId") or record.get("custom_id"),
- "createdAt": record.get("createdAt") or record.get("created_at"),
- "updatedAt": record.get("updatedAt") or record.get("updated_at"),
- "summary": record.get("summary"),
+ "id": doc.get("id"),
+ "title": doc.get("title"),
+ "status": system.get("status"),
+ "createdAt": system.get("createdAt"),
+ "updatedAt": system.get("updatedAt"),
+ "summary": doc.get("summary"),
"memoryEntries": memory_entries,
}
)
return {
- "containerTag": container_tag,
+ "namespace": namespace,
"query": query,
"profile": profile_context,
"documents": documents,
- "pagination": docs.get("pagination") if isinstance(docs, dict) else None,
+ "pagination": docs.get("pagination"),
}
@@ -578,7 +575,7 @@ def reconstruct_python_sdk_memory_block(
def build_middleware_memory_debug(
- container_tag: str,
+ namespace: str,
conversation_id: str,
memory_mode: str,
last_user_message: str,
@@ -610,9 +607,11 @@ def build_middleware_memory_debug(
"detail": {
"authoritativeMiddlewareCapture": False,
"timing": "after model response",
- "endpoint": "POST /v4/profile",
- "containerTag": container_tag,
- "customId": conversation_id,
+ "endpoint": (
+ "POST /ns/{namespace}/profile + POST /ns/{namespace}/search"
+ ),
+ "namespace": namespace,
+ "id": conversation_id,
"memoryMode": memory_mode,
"query": context.get("query"),
"staticCount": len(profile.get("static", [])),
@@ -640,8 +639,8 @@ def build_middleware_memory_debug(
)
save_detail = {
- "containerTag": container_tag,
- "customId": f"conversation:{conversation_id}",
+ "namespace": namespace,
+ "id": f"conversation:{conversation_id}",
"addMemory": middleware_config.addMemory,
"verbose": middleware_config.verbose,
}
@@ -665,21 +664,16 @@ def build_middleware_memory_debug(
async def fetch_context_for_debug(
- container_tag: str,
+ namespace: str,
query: Optional[str],
sm_key: str,
) -> tuple[Optional[dict[str, Any]], Optional[str]]:
try:
async with asyncio.timeout(CONTEXT_DEBUG_TIMEOUT_SECONDS):
- profile = await fetch_profile_context(
- container_tag,
- sm_key,
- query,
- include=["static", "dynamic"],
- )
+ profile = await fetch_profile_context(namespace, sm_key, query)
return (
{
- "containerTag": container_tag,
+ "namespace": namespace,
"query": query,
"profile": profile,
},
@@ -691,12 +685,12 @@ async def fetch_context_for_debug(
@app.get("/context")
async def context_get(
- containerTag: Annotated[
+ namespace: Annotated[
str,
Query(
min_length=1,
- max_length=MAX_CONTAINER_TAG_LENGTH,
- pattern=CONTAINER_TAG_PATTERN,
+ max_length=MAX_NAMESPACE_LENGTH,
+ pattern=NAMESPACE_PATTERN,
),
] = "sdk-playground",
query: Annotated[Optional[str], Query(max_length=MAX_MESSAGE_LENGTH)] = None,
@@ -712,7 +706,7 @@ async def context_get(
"X-Supermemory-API-Key header",
)
async with asyncio.timeout(HTTP_TIMEOUT_SECONDS):
- ctx = await fetch_container_context(containerTag, sm_key, query)
+ ctx = await fetch_namespace_context(namespace, sm_key, query)
return {"ok": True, "context": ctx}
except Exception as error:
return JSONResponse(
@@ -731,7 +725,7 @@ async def context_post(req: ContextRequest):
try:
sm_key = resolve_supermemory_key(req.apiKeys)
async with asyncio.timeout(HTTP_TIMEOUT_SECONDS):
- ctx = await fetch_container_context(req.containerTag, sm_key, req.query)
+ ctx = await fetch_namespace_context(req.namespace, sm_key, req.query)
return {"ok": True, "context": ctx}
except Exception as error:
return JSONResponse(
@@ -775,7 +769,7 @@ async def chat(req: ChatRequest):
middleware_config = req.middlewareConfig or MiddlewareConfig()
text = await chat_openai_middleware(
req.messages,
- req.containerTag,
+ req.namespace,
req.conversationId,
req.memoryMode or "full",
middleware_config,
@@ -790,12 +784,12 @@ async def chat(req: ChatRequest):
middleware_debug = (middleware_config, last_user, query)
elif req.sdkId == "py-openai-tools":
text, tool_trace = await chat_openai_tools(
- req.messages, req.containerTag, sm_key, oai_key
+ req.messages, req.namespace, sm_key, oai_key
)
elif req.sdkId == "py-supermemory-direct":
- text, custom_id, profile_context = await chat_supermemory_direct(
+ text, document_id, profile_context = await chat_supermemory_direct(
req.messages,
- req.containerTag,
+ req.namespace,
req.conversationId,
sm_key,
oai_key,
@@ -804,19 +798,19 @@ async def chat(req: ChatRequest):
(m.content for m in reversed(req.messages) if m.role == "user"),
"",
)
- direct_debug = (custom_id, profile_context, last_user)
+ direct_debug = (document_id, profile_context, last_user)
else:
raise RuntimeError(f"Unsupported Python SDK: {req.sdkId}")
if middleware_debug is not None:
middleware_config, last_user, query = middleware_debug
ctx, context_error = await fetch_context_for_debug(
- req.containerTag,
+ req.namespace,
query,
sm_key,
)
memory_debug = build_middleware_memory_debug(
- req.containerTag,
+ req.namespace,
req.conversationId,
req.memoryMode or "full",
last_user,
@@ -825,12 +819,12 @@ async def chat(req: ChatRequest):
middleware_config,
)
elif direct_debug is not None:
- custom_id, profile_context, last_user = direct_debug
+ document_id, profile_context, last_user = direct_debug
save_debug = await save_direct_conversation(
req.messages,
text,
- req.containerTag,
- custom_id,
+ req.namespace,
+ document_id,
sm_key,
)
memory_debug = [
@@ -838,7 +832,7 @@ async def chat(req: ChatRequest):
"type": "manual_profile",
"label": "Profile context used for this response",
"detail": {
- "containerTag": req.containerTag,
+ "namespace": req.namespace,
"query": last_user,
"staticCount": len(profile_context["static"]),
"dynamicCount": len(profile_context["dynamic"]),
diff --git a/apps/sdk-playground/python/uv.lock b/apps/sdk-playground/python/uv.lock
index 60b5d5de..a96b1df7 100644
--- a/apps/sdk-playground/python/uv.lock
+++ b/apps/sdk-playground/python/uv.lock
@@ -1043,7 +1043,6 @@ dependencies = [
{ name = "httpx" },
{ name = "openai" },
{ name = "python-dotenv" },
- { name = "supermemory" },
{ name = "supermemory-openai-sdk", extra = ["async"] },
{ name = "uvicorn", extra = ["standard"] },
]
@@ -1054,7 +1053,6 @@ requires-dist = [
{ name = "httpx", specifier = ">=0.28.0" },
{ name = "openai", specifier = ">=1.102.0" },
{ name = "python-dotenv", specifier = ">=1.0.1" },
- { name = "supermemory", specifier = ">=3.50.0" },
{ name = "supermemory-openai-sdk", extras = ["async"], editable = "../../../packages/openai-sdk-python" },
{ name = "uvicorn", extras = ["standard"], specifier = ">=0.32.0" },
]
@@ -1100,7 +1098,7 @@ wheels = [
[[package]]
name = "supermemory-openai-sdk"
-version = "1.0.8"
+version = "1.0.9"
source = { editable = "../../../packages/openai-sdk-python" }
dependencies = [
{ name = "openai" },
@@ -1116,17 +1114,17 @@ async = [
[package.metadata]
requires-dist = [
- { name = "aiohttp", marker = "extra == 'async'", specifier = ">=3.8.0" },
+ { name = "aiohttp", marker = "extra == 'async'", specifier = ">=3.14.3" },
{ name = "openai", specifier = ">=1.102.0" },
{ name = "requests", specifier = ">=2.25.0" },
- { name = "supermemory", specifier = ">=3.50.0" },
+ { name = "supermemory", specifier = ">=3.50.0,<5" },
{ name = "typing-extensions", specifier = ">=4.0.0" },
]
provides-extras = ["async"]
[package.metadata.requires-dev]
dev = [
- { name = "black", specifier = ">=24.8.0" },
+ { name = "black", specifier = ">=26.3.1" },
{ name = "flake8", specifier = ">=7.1.2" },
{ name = "isort", specifier = ">=5.13.2" },
{ name = "mypy", specifier = ">=1.14.1" },
diff --git a/apps/sdk-playground/src/app/api/chat/route.ts b/apps/sdk-playground/src/app/api/chat/route.ts
index 77720664..2b53dec5 100644
--- a/apps/sdk-playground/src/app/api/chat/route.ts
+++ b/apps/sdk-playground/src/app/api/chat/route.ts
@@ -14,7 +14,7 @@ import {
assertTrustedBrowserRequest,
mayUseEnvironmentKeys,
parseApiKeys,
- parseContainerTag,
+ parseNamespace,
parseConversationId,
parseIdentifier,
parseMemoryMode,
@@ -63,7 +63,7 @@ export async function POST(req: Request) {
const body = await readJsonObject(req)
const sdkId = parseIdentifier(body.sdkId, "sdkId")
const messages = parseMessages(body.messages)
- const containerTag = parseContainerTag(body.containerTag)
+ const namespace = parseNamespace(body.namespace)
const conversationId = parseConversationId(body.conversationId)
const memoryMode = parseMemoryMode(body.memoryMode)
const middlewareConfig = parseMiddlewareConfig(body.middlewareConfig)
@@ -97,7 +97,7 @@ export async function POST(req: Request) {
body: JSON.stringify({
sdkId,
messages,
- containerTag,
+ namespace,
conversationId,
memoryMode,
middlewareConfig,
@@ -129,12 +129,11 @@ export async function POST(req: Request) {
{
sdkId,
messages,
- containerTag,
+ namespace,
conversationId,
memoryMode,
middlewareConfig,
apiKeys,
- containerTags: [containerTag],
},
apiKeys,
)
diff --git a/apps/sdk-playground/src/app/api/context/route.ts b/apps/sdk-playground/src/app/api/context/route.ts
index 6e1ce336..29efb221 100644
--- a/apps/sdk-playground/src/app/api/context/route.ts
+++ b/apps/sdk-playground/src/app/api/context/route.ts
@@ -1,12 +1,12 @@
import { NextResponse } from "next/server"
import { resolveSupermemoryApiKey } from "@/lib/api-keys"
-import { fetchContainerContext } from "@/lib/context-api"
+import { fetchNamespaceContext } from "@/lib/context-api"
import {
PlaygroundRequestError,
assertTrustedBrowserRequest,
mayUseEnvironmentKeys,
parseApiKeys,
- parseContainerTag,
+ parseNamespace,
parseOptionalText,
readJsonObject,
} from "@/lib/request-validation"
@@ -15,7 +15,7 @@ export async function GET(req: Request) {
try {
assertTrustedBrowserRequest(req)
const { searchParams } = new URL(req.url)
- const containerTag = parseContainerTag(searchParams.get("containerTag"))
+ const namespace = parseNamespace(searchParams.get("namespace"))
const query = parseOptionalText(searchParams.get("query"), "query")
const supermemoryApiKey = resolveSupermemoryApiKey(null, {
allowEnvironment: mayUseEnvironmentKeys(req),
@@ -32,8 +32,8 @@ export async function GET(req: Request) {
)
}
- const context = await fetchContainerContext(
- containerTag,
+ const context = await fetchNamespaceContext(
+ namespace,
query,
supermemoryApiKey,
)
@@ -54,7 +54,7 @@ export async function POST(req: Request) {
try {
assertTrustedBrowserRequest(req)
const body = await readJsonObject(req)
- const containerTag = parseContainerTag(body.containerTag)
+ const namespace = parseNamespace(body.namespace)
const query = parseOptionalText(body.query, "query")
const supermemoryApiKey = resolveSupermemoryApiKey(
parseApiKeys(body.apiKeys),
@@ -71,8 +71,8 @@ export async function POST(req: Request) {
)
}
- const context = await fetchContainerContext(
- containerTag,
+ const context = await fetchNamespaceContext(
+ namespace,
query,
supermemoryApiKey,
)
diff --git a/apps/sdk-playground/src/app/page.tsx b/apps/sdk-playground/src/app/page.tsx
index 27ee50cd..a19df7e3 100644
--- a/apps/sdk-playground/src/app/page.tsx
+++ b/apps/sdk-playground/src/app/page.tsx
@@ -52,7 +52,7 @@ export default function AgentPlaygroundPage() {
const keysReady = supermemoryKeyReady && openAiKeyReady
const [sdkId, setSdkId] = useState("ts-ai-sdk-middleware")
- const [containerTag, setContainerTag] = useState("sdk-playground")
+ const [namespace, setNamespace] = useState("sdk-playground")
const [memoryMode, setMemoryMode] = useState<"profile" | "query" | "full">(
"full",
)
@@ -134,7 +134,7 @@ export default function AgentPlaygroundPage() {
...chatHistory,
{ role: "user", content: userMessage.content },
],
- containerTag,
+ namespace,
conversationId: activeConversationId,
memoryMode:
selectedSdk.mode === "middleware" ? memoryMode : undefined,
@@ -301,16 +301,16 @@ export default function AgentPlaygroundPage() {