MemorySync
Getting Started

Vercel AI SDK Memory

Give any AI SDK app long-term memory with one wrapLanguageModel call: recalled context injected before generation, the exchange persisted after it, streaming-safe, on AI SDK v6 and v7 — plus standalone helpers and five agent memory tools.

What the integration provides

LayerWhat it does
createMemorySyncMiddlewareWrap a model once: recall runs before each generation and injects a system context block; the exchange persists verbatim after it. Works with generateText, streamText, and every provider.
HelpersgetMemoryContext, searchMemories, saveTurn, createMemorySyncOnFinish — for apps that wire memory around calls themselves.
Agent toolsmemorySyncTools() — five structured tools (add, search, list, update, delete) the model can call, which never throw.
Mem0SupermemoryZepMemorySync
AI SDK v7 (current)✗ (v6, bundled as a hard dep)✗ (v5 — two majors behind)✗ (v6)✓ v6 AND v7, one build
Patterncustom provider wrapping 5 vendorstools onlymiddlewaremiddleware + helpers + tools
Guaranteed persistence✓ (opaque)✗ — the LLM decides✓ deterministic, with an off switch
Extra LLM provider deps5 bundled000 — provider-agnostic

Install

npm install memorysync-ai-sdk ai

Set MEMORYSYNC_API_KEY in the environment, or pass apiKey explicitly. Supports ai 6.x and 7.x (Node 18+ for the package; the ai package itself requires Node 22+ on v7). This is a JavaScript/TypeScript surface — for Python agents use the LangChain, LangGraph or CrewAI integrations.

Wrap the model once

import { generateText, wrapLanguageModel } from "ai";
import { openai } from "@ai-sdk/openai";
import { createMemorySyncMiddleware } from "memorysync-ai-sdk";
const model = wrapLanguageModel({
model: openai("gpt-4o-mini"),
middleware: createMemorySyncMiddleware({
userId: "customer-7", // per-end-user scoping — required
sessionId: "thread-42", // groups the stored transcript
}),
});
// First conversation
await generateText({ model, prompt: "I'm vegetarian and I fly aisle." });
// Any later call — same user, any process, any provider
const { text } = await generateText({
model,
prompt: "Book my trip: flight plus a dinner spot.",
});
// The model already saw: vegetarian, aisle seat — injected from memory.

Injection happens once per user turn — on tool-loop continuation steps (where the last message is a tool result, not the user) the prompt passes through untouched, so multi-step agents never pay for the context block twice. Your app’s own system prompt stays first; the memory block is inserted after it.

Streaming and Next.js

// app/api/chat/route.ts
import { convertToModelMessages, streamText, wrapLanguageModel } from "ai";
import { openai } from "@ai-sdk/openai";
import { createMemorySyncMiddleware } from "memorysync-ai-sdk";
export async function POST(req: Request) {
const { messages, userId, sessionId } = await req.json();
const model = wrapLanguageModel({
model: openai("gpt-4o-mini"),
middleware: createMemorySyncMiddleware({ userId, sessionId }),
});
const result = streamText({
model,
messages: convertToModelMessages(messages),
});
return result.toUIMessageStreamResponse();
}

Persistence accumulates the text deltas and writes once on the stream’s finish part — chunk latency is untouched, and the write completes before the stream closes, which is what guarantees it happened before a serverless runtime freezes the sandbox. A stream that errors or is aborted never reaches its finish, so nothing partial is ever stored.

Recall-only and persist-only

// Read-only wrap: recalls context, never writes anything.
createMemorySyncMiddleware({ userId, persist: false });
// Write-only wrap: persists exchanges, injects nothing.
createMemorySyncMiddleware({ userId, recall: false });
// Tune recall depth and the injected block's shape.
createMemorySyncMiddleware({
userId,
k: 12,
template: "What you know about this user:\n{context}",
});

Standalone helpers

For apps that keep their model calls unwrapped: the same recall pipeline and the same idempotent persistence as the middleware, callable directly. Mixing styles is safe — both write the same idempotency seeds, so a turn stored by the middleware and again by saveTurn lands once.

import {
createMemorySyncOnFinish,
getMemoryContext,
saveTurn,
searchMemories,
} from "memorysync-ai-sdk";
// 1. Prompt-ready context block ("" for a new user)
const context = await getMemoryContext("what should I cook?", {
userId: "customer-7",
});
// 2. Scored raw results
const hits = await searchMemories("dietary preferences", { userId: "customer-7" });
// [{ id: "m_123", text: "human: I'm vegetarian", score: 0.62 }, ...]
// 3. Explicit persistence — THROWS on failure (an explicit call is
// owed the truth), unlike the middleware's reported-never-thrown.
await saveTurn(
{ user: "I'm vegetarian", assistant: "Noted!", sessionId: "thread-42" },
{ userId: "customer-7" },
);
// 4. Or persist from onFinish — fires once after the full tool loop.
const result = streamText({
model,
prompt,
onFinish: createMemorySyncOnFinish({ userId: "customer-7", user: prompt }),
});

Agent memory tools

import { generateText, stepCountIs } from "ai";
import { memorySyncTools } from "memorysync-ai-sdk";
const { text } = await generateText({
model: openai("gpt-4o-mini"),
tools: { ...memorySyncTools({ userId: "customer-7" }) },
stopWhen: stepCountIs(5),
prompt: "Remember that I prefer aisle seats.",
});
// Untrusted agents: search + list only.
memorySyncTools({ userId: "customer-7", readOnly: true });
ToolWhat it doesFailure behaviour
addMemorySave one durable fact; duplicate saves answer “already stored”.Readable error string — never throws.
searchMemorySemantic search with relevance scores.Readable error string.
listMemoriesNewest-first listing.Readable error string.
updateMemoryChange tags/importance. Memory text is immutable.Readable error string.
deleteMemoryPermanent delete by id, scoped to the configured user.Readable error string.

Same five operations, same response strings as the LangChain tools and the CrewAI tools — an agent moved between frameworks keeps behaving the same way. A memory failure can never abort the tool loop: tools return error strings instead of throwing.

See which memories were used

const result = await generateText({ model, prompt });
const memories = result.providerMetadata?.memorysync?.memories;
// [{ id: "m_812", text: "human: I'm vegetarian", score: 0.62 }, ...]
// Render "why this answer" UI, log for evals, or audit recalls.
// Streaming: the same field on the awaited stream result.
const streamed = streamText({ model, prompt });
const meta = (await streamed.providerMetadata)?.memorysync;

Public API

ExportKindNotes
createMemorySyncMiddlewareMiddleware factoryuserId required; sessionId, recall, persist, k, template, specificationVersion, onError optional.
getMemoryContextHelperPrompt-ready context block, "" for a new user.
searchMemoriesHelperScored {id, text, score} results.
saveTurnHelperExplicit idempotent persist — throws on failure.
createMemorySyncOnFinishCallback factoryonFinish persist for generateText/streamText — never throws.
memorySyncToolsTool factoryFive tools; readOnly: true returns search + list only.
MemorySyncContextProviderClassThe recall pipeline behind the middleware, directly usable.

Supported versions

PackageRegistryRequiresRuntime
memorysync-ai-sdk 1.0.0npmai >=6 <8 (peer)Node 18+ (ai@7 itself requires Node 22+)

One build serves both majors: the package touches only fields that are identical across the v3 and v4 provider specs and imports nothing from ai at runtime. The CI suite runs identically against ai@6 + zod 3 and ai@7 + zod 4. Edge-safe by construction — no Node-only imports, so it runs on Vercel Edge and Cloudflare Workers.

Where to go next

Was this page helpful?