MemorySync
Getting Started

Mastra Memory

Give Mastra agents long-term memory on the native processor pipeline: recalled context injected before each call, the exchange persisted after it, streaming-safe, with four ways to resolve which user the agent is acting for — plus a zero-config wrapper, five agent memory tools, and standalone helpers.

What the integration provides

LayerWhat it does
createMemorySyncProcessorsAn input processor (recall injected as a system message before the model call) and an output processor (the exchange persisted verbatim after it) for new Agent({ inputProcessors, outputProcessors }).
withMemorySyncZero-config wrapper: pass your agent config through it and both processors are merged in. Your own processors are preserved.
Agent toolscreateMemorySyncTools() — five structured tools (add, search, list, update, delete) the model can call, which never throw.
HelpersgetMemoryContext, searchMemories, saveTurn — for hand-wired setups.
Mem0SupermemoryZepMemorySync
Works on @mastra/core 1.x (current)✗ (peer <0.17 — predates 1.0, removed from their monorepo)△ (alpha dependency chain)✓ (>=1.42)✓ >=1.42 <2, CI-tested on the latest 1.x
Pattern2 tools — the LLM decidesprocessors + wrapperprocessors + 3 toolsprocessors + wrapper + 5 tools + helpers
Guaranteed persistence✗ — the LLM decides✓ deterministic, with an off switch
Identity resolutionstatic configstatic configstatic config4 sources: static, resolver, RequestContext, memory.resource
Failed-stream half-turn guard✓ a dead stream stores nothing

Install

npm install memorysync-mastra @mastra/core

Set MEMORYSYNC_API_KEY in the environment, or pass apiKey explicitly. Supports @mastra/core 1.42+ (Node 20+ for the package; current @mastra/core itself requires Node 22+). This is a JavaScript/TypeScript surface — for Python agents use the LangChain, LangGraph or CrewAI integrations.

Add the processors

import { Agent } from "@mastra/core/agent";
import { openai } from "@ai-sdk/openai";
import { createMemorySyncProcessors } from "memorysync-mastra";
const { inputProcessor, outputProcessor } = createMemorySyncProcessors({
userId: "customer-7", // per-end-user scoping
sessionId: "thread-42", // groups the stored transcript
});
const agent = new Agent({
id: "assistant",
name: "Assistant",
instructions: "You are a helpful assistant.",
model: openai("gpt-4o-mini"),
inputProcessors: [inputProcessor],
outputProcessors: [outputProcessor],
});
// First conversation
await agent.generate("I'm vegetarian and I fly aisle.");
// Any later call — same user, any thread, any model
const { text } = await agent.generate(
"Book my trip: flight plus a dinner spot.",
);
// The model already saw: vegetarian, aisle seat — injected from memory.

Injection happens once per generate/stream call — the processors use processInput, not the per-step hook, so multi-step tool loops never pay for the context block twice. Your agent’s own instructions stay first; the memory block is appended to the system messages after them.

Or wrap the config

import { Agent } from "@mastra/core/agent";
import { openai } from "@ai-sdk/openai";
import { withMemorySync } from "memorysync-mastra";
const agent = new Agent(withMemorySync(
{
id: "assistant",
name: "Assistant",
instructions: "You are a helpful assistant.",
model: openai("gpt-4o-mini"),
},
{ userId: "customer-7" },
));
// Identical behaviour to wiring the processors yourself. Your own
// input/output processors are preserved — MemorySync's run after them.

Who is the agent acting for?

One Mastra agent typically serves many end users, so the processors resolve identity per call from four sources, in priority order. Whichever you use, memories stay scoped to that user — server-side, not by convention.

import { RequestContext } from "@mastra/core/request-context";
import {
createMemorySyncProcessors,
MASTRA_RESOURCE_ID_KEY,
MASTRA_THREAD_ID_KEY,
} from "memorysync-mastra";
// 1. Your own resolver — wins over everything (multi-user servers).
createMemorySyncProcessors({
resolveIdentity: (requestContext) => ({
userId: currentSession.userId,
sessionId: currentSession.threadId,
}),
});
// 2. Static ids — one agent per user (scripts, workers).
createMemorySyncProcessors({ userId: "customer-7", sessionId: "thread-42" });
// 3. Mastra's RequestContext — set by your server middleware.
const ctx = new RequestContext();
ctx.set(MASTRA_RESOURCE_ID_KEY, "customer-7");
ctx.set(MASTRA_THREAD_ID_KEY, "thread-42");
await agent.generate(messages, { requestContext: ctx });
// 4. Mastra's own memory plumbing — the ids ride the call.
await agent.generate("I moved to Lisbon.", {
memory: { resource: "customer-7", thread: "thread-42" },
});

Streaming

const stream = await agent.stream("Plan a dinner for my team.", {
memory: { resource: "customer-7", thread: "thread-42" },
});
for await (const chunk of stream.textStream) {
process.stdout.write(chunk);
}
// Persistence runs once, after the full stream — chunk latency is
// untouched.

A stream that errors mid-flight persists nothing: Mastra still fires the output hook on failed runs (finishReason: "error" — verified against a real agent in our test suite), and the processor detects that and skips the write, so a dead stream never leaves a half-turn behind. The retry that follows stores the exchange once.

Recall modes and switches

// Recall shape — pick per agent:
createMemorySyncProcessors({ userId, mode: "query" }); // relevant to the latest message (default)
createMemorySyncProcessors({ userId, mode: "profile" }); // stable overview of the user
createMemorySyncProcessors({ userId, mode: "full" }); // both
// Read-only agent: recalls context, never writes anything.
createMemorySyncProcessors({ userId, persist: false });
// Write-only agent: persists exchanges, injects nothing.
createMemorySyncProcessors({ userId, recall: false });
// Tune recall depth and the injected block's shape.
createMemorySyncProcessors({
userId,
k: 12,
template: "What you know about this user:\n{context}",
});

Agent memory tools

import { Agent } from "@mastra/core/agent";
import { createMemorySyncTools } from "memorysync-mastra";
const agent = new Agent({
id: "assistant",
name: "Assistant",
instructions: "Use the memory tools to remember durable facts.",
model: openai("gpt-4o-mini"),
tools: { ...createMemorySyncTools({ userId: "customer-7" }) },
});
await agent.generate("Remember that I prefer aisle seats.");
// Untrusted agents: search + list only.
createMemorySyncTools({ userId: "customer-7", readOnly: true });
ToolWhat it doesFailure behaviour
add_memorySave one durable fact; duplicate saves answer “already stored”.Readable error string — never throws.
search_memorySemantic search with relevance scores.Readable error string.
list_memoriesNewest-first listing.Readable error string.
update_memoryChange tags/importance. Memory text is immutable.Readable error string.
delete_memoryPermanent delete by id, scoped to the configured user.Readable error string.

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

Standalone helpers

For workflows and hand-wired setups: the same recall pipeline and the same idempotent persistence as the processors, callable directly. Mixing styles is safe — both write the same idempotency seeds, so a turn stored by the processors and again by saveTurn lands once.

import {
getMemoryContext,
saveTurn,
searchMemories,
} from "memorysync-mastra";
// 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 processors' reported-never-thrown.
await saveTurn(
{ user: "I'm vegetarian", assistant: "Noted!", sessionId: "thread-42" },
{ userId: "customer-7" },
);

Public API

ExportKindNotes
createMemorySyncProcessorsProcessor factoryReturns { inputProcessor, outputProcessor }; userId, sessionId, resolveIdentity, mode, recall, persist, k, template, onError optional.
withMemorySyncConfig wrapperMerges both processors into an agent config; preserves your own.
createMemorySyncToolsTool factoryFive tools; readOnly: true returns search + list only.
getMemoryContextHelperPrompt-ready context block, "" for a new user.
searchMemoriesHelperScored {id, text, score} results.
saveTurnHelperExplicit idempotent persist — throws on failure.
resolveCallIdentityFunctionThe four-source identity resolution, directly usable.
MASTRA_RESOURCE_ID_KEY / MASTRA_THREAD_ID_KEYConstantsMastra’s RequestContext keys, re-exported for middleware.
MemorySyncContextProviderClassThe recall pipeline behind the processors, directly usable.

Supported versions

PackageRegistryRequiresRuntime
memorysync-mastra 1.0.0npm@mastra/core >=1.42 <2 (peer)Node 20+ (@mastra/core itself requires Node 22+)

The CI suite drives a real @mastra/core Agent through the processors — hook timing, RequestContext keys, message shapes — on the pinned core and again on the latest 1.x release, so a breaking change in a new Mastra version fails our pipeline before it can fail your agent.

Where to go next

Was this page helpful?