Vercel AI SDK memory and persistence, with supermemory

The Vercel AI SDK doesn't store anything between requests for you. To keep a chat, you save its messages yourself and load them by chat ID, which the SDK's message persistence guide walks through. To remember a user across chats, you add a memory layer. With supermemory that's one wrapper around your model, withSupermemory from @supermemory/tools, and version 3.1 and later work with AI SDK 5, 6 and 7.
This guide covers both halves, the middleware setup, and how to test it. Versions and options were checked against npm and our AI SDK docs on October 16, 2026.
Persistence vs memory: what's the difference?
Persistence keeps one conversation so the user can reopen it. Memory carries what matters from one conversation into the next.
| Chat persistence | Memory | |
|---|---|---|
| What it keeps | Every message of one chat, as the user saw it | Facts and preferences that matter across chats |
| Keyed by | Chat ID | User or workspace |
| Who builds it | You: a database and the SDK's UIMessage format |
A memory layer such as supermemory, or your own |
| What the model gets | The current chat's messages | The relevant facts, added to the prompt |
| What it's for | Reopening and continuing a chat | Starting a new chat that already knows the user |
The AI SDK recommends storing messages in its UIMessage format and saving them when the response stream ends (persistence guide). Its memory guide then lists three ways to add memory: a provider-defined memory tool, a memory provider, or a custom tool.
You need both. Retrieved memory is a selection of facts, not a transcript, so keep your message store for displaying and resuming chats, and check permissions, plans and other transactional data against the systems that own them. A remembered subscription tier shouldn't grant an entitlement.
How do you add memory to an AI SDK app?
Install the integration package next to the SDK and your model provider:
npm install @supermemory/tools ai @ai-sdk/openai
Installing the supermemory client alone doesn't include the @supermemory/tools/ai-sdk import used below. Keep OPENAI_API_KEY and SUPERMEMORY_API_KEY on the server, in your runtime's secret settings. You can get a supermemory key from the console.
Then wrap the model. This helper belongs in server code, and memoryScope and conversationId must come from application logic that has already authenticated the request and checked access to the conversation:
import { generateText, type ModelMessage } from "ai";
import { openai } from "@ai-sdk/openai";
import { withSupermemory } from "@supermemory/tools/ai-sdk";
export async function answerWithMemory(input: {
memoryScope: string;
conversationId: string;
messages: ModelMessage[];
}) {
const apiKey = process.env.SUPERMEMORY_API_KEY;
if (!apiKey) throw new Error("SUPERMEMORY_API_KEY is required");
const model = withSupermemory(openai("gpt-5"), {
apiKey,
namespace: input.memoryScope,
id: input.conversationId,
mode: "full",
addMemory: "always",
skipMemoryOnError: false,
});
const result = await generateText({
model,
messages: input.messages,
});
return result.text;
}
Two IDs do the work. namespace is who the memories belong to, so keep it stable for a user or workspace across all their chats. id is which conversation this is, so each chat becomes one document instead of mixing with the others. Reusing the conversation ID as the namespace breaks the continuity you're adding, because a new chat would start with an empty scope.
The wrapper runs before and after each model call (AI SDK docs):
- Before, it adds memories to the system prompt.
mode: "profile"(the default) adds the user's profile,"query"searches memories with the user's message, and"full"does both. - After, it saves the conversation to the namespace.
addMemory: "always"is the default, and tool calls are left out unless you setincludeToolCalls: true. - If memory retrieval fails, it carries on without memories by default. This example sets
skipMemoryOnError: falseso the call fails instead, because a support agent shouldn't pretend to remember a ticket it couldn't look up. A failed save is only logged either way. Choose per product.
The same wrapped model works with streamText.
Which AI SDK versions does it support?
@supermemory/tools |
AI SDK (ai) |
supermemory API |
|---|---|---|
| 3.1.0 and later | 5, 6 and 7 | v5 |
| 3.0.x | 5 and 6 | v5 |
| 2.x | Upgrade first | v3/v4, shutting down on December 31, 2026 |
Version 3.1.2, published October 12, declares ai 5, 6 or 7 as a peer dependency (npm), and the example above type-checks with ai 7.0.142, @ai-sdk/openai 4.0.95 and @supermemory/tools 3.1.2. Coming from 2.x, where the options were containerTag and customId, run npx supermemory@latest migrate in the repo or follow the 3.0 upgrade guide.
Should you use middleware or memory tools?
Use the middleware when every request should get relevant memory: recall and saving happen around each call without the model deciding. Use the tools when the agent should choose. supermemoryTools gives the model operations such as searchMemories and addMemory, and it calls them when it thinks they'll help:
import { streamText } from "ai";
import { openai } from "@ai-sdk/openai";
import { supermemoryTools } from "@supermemory/tools/ai-sdk";
const result = streamText({
model: openai("gpt-5"),
prompt: "Remember that I'm allergic to peanuts",
tools: supermemoryTools(process.env.SUPERMEMORY_API_KEY!, { namespace: "user_123" }), // without it, every user shares one namespace
});
With tools alone, whether anything is saved or recalled depends on the model, so test it more carefully.
Add memory to an existing app incrementally
Keep your current chat-history store and introduce memory at one server-side generation boundary. Derive the namespace from the authenticated tenant and user, and keep your existing conversation ID. A request body must never choose whose memories to retrieve.
A v5 namespace allows letters, numbers, underscores, colons and hyphens, up to 100 characters (API reference), and raw IDs such as email addresses or domains often break that. This helper hashes each identifier, so any ID fits and a colon inside one can't blur the boundary between tenant and user:
import { createHash } from "node:crypto";
export function memoryScope(tenantId: string, userId: string): string {
if (!tenantId.trim() || !userId.trim()) {
throw new Error("Tenant and user identifiers are required");
}
const hash = (value: string) => createHash("sha256").update(value).digest("hex").slice(0, 32);
return `tenant_${hash(tenantId)}:user_${hash(userId)}`;
}
It doesn't authenticate anything, so call it only with identifiers that have already passed your access checks. A workspace-wide feature needs its own, separately authorized scope rather than a reused personal one.
Start with one route and a synthetic user. Compare the assembled context and answer with the existing path, then turn the feature on for a small cohort. Don't save each interaction through both the middleware and a separate write hook unless you've confirmed how they deduplicate, and decide how timeouts behave before you roll out.
Test across two sessions
Use synthetic data before a customer pilot:
- In conversation A, state a harmless preference such as "use concise bullet points."
- Memories form in the background after the save, so if step 4 finds nothing at first, check that the conversation document's
system.dreamingStatusisdonebefore you try again. - Start conversation B with the same namespace and a new conversation ID.
- Check that the preference is retrieved and used where it's relevant.
- Repeat with another user's namespace and check that the preference is absent.
- Correct the preference and check that the new version wins.
The user preferences guide shows how to save, read and forget a preference through the profile.
Add a case where memory is unreachable and a deletion case, and inspect the context sent to the model, not only the final answer. When a step fails, the debugging guide walks through where facts get lost.
Measure the cost of adding memory
Record retrieval latency, time to first token, total response time and context tokens separately, and compare a baseline that sends your existing conversation context with the memory version, using the same questions and model settings. If the context gets smaller, check that hard questions still get the evidence they need. Saves and searches draw on your plan's credits, and the pricing page has the rates.
For the bigger build-or-buy question, compare three ways to add long-term memory. For other frameworks, there are guides for the OpenAI Agents SDK and Mastra, and the quickstart shows the same pattern without a framework.
Common questions
Does the Vercel AI SDK have memory?
Not on its own. Each request only knows the messages you send, so you persist chats yourself and add memory through a provider-defined tool, a memory provider such as supermemory, or a custom tool, as the SDK's memory guide describes.
How do I persist chat history in the AI SDK?
Save the chat's messages in your own database, keyed by chat ID, in the SDK's UIMessage format, and load them when the chat reopens. The persistence guide shows where to save them as the response stream ends.
Does @supermemory/tools work with AI SDK 7?
Yes, from version 3.1.0. Version 3.1.2 declares ai 5, 6 or 7 as a peer dependency. Version 3.0 supports AI SDK 5 and 6.
Do I still need a database for my chats?
Yes. Memory gives the model the facts that matter from earlier chats. It doesn't replace the transcript your users scroll back through.
What does it cost?
The SDK and @supermemory/tools are free to install. Memory saves and searches draw on your supermemory plan's credits, and the Free plan includes $5 of credits a month (pricing).