memorysync-mastra
v1.1.0
Published
MemorySync memory for Mastra agents: input/output processors for automatic recall and persistence, a zero-config agent wrapper, five agent memory tools, and helpers. Works on current @mastra/core.
Maintainers
Readme
memorysync-mastra
Long-term memory for Mastra agents, backed by MemorySync — on Mastra's native processor pipeline.
- Processors — recalled context injected as a system message before each
generate/streamcall, the user's message sent after it for fact extraction (only durable facts are stored; assistant replies are not). withMemorySync— zero-config wrapper that merges the processors into any agent config.- Five agent tools — add, search, list, update, delete; they never throw.
- Helpers —
getMemoryContext,searchMemories,saveTurnfor hand-wired setups.
npm install memorysync-mastra @mastra/coreSet MEMORYSYNC_API_KEY in the environment (create a key at app.memorysync.io), or pass apiKey explicitly.
Processors — memory on the native pipeline
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 facts by thread
});
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.Or wrap the config:
import { withMemorySync } from "memorysync-mastra";
const agent = new Agent(withMemorySync(
{ id: "assistant", name: "Assistant", instructions, model },
{ userId: "customer-7" },
));Injection happens once per call — the processors use processInput, not the per-step hook, so multi-step tool loops never pay for the context block twice. Recall failing means the call proceeds without context; persistence failing is reported through onError and never thrown. A stream that dies mid-flight sends nothing (the failed run is detected via finishReason: "error" and skipped); the retry sends the turn.
What gets stored: after each call the user's message is sent to MemorySync's fact extraction, and only the durable facts it contains ("vegetarian", "flies aisle") are stored, tagged with the session (sessionId, else the Mastra thread). Filler like "ok" or "thanks" yields nothing. Assistant replies are not stored. Retries of the same turn in the same session are recognised server-side and not extracted twice.
Who is the agent acting for?
Identity resolves per call from four sources, in priority order:
// 1. Your own resolver — wins over everything (multi-user servers).
createMemorySyncProcessors({
resolveIdentity: (requestContext) => ({ userId: session.userId }),
});
// 2. Static ids — one agent per user (scripts, workers).
createMemorySyncProcessors({ userId: "customer-7" });
// 3. Mastra's RequestContext — set by server middleware.
import { RequestContext } from "@mastra/core/request-context";
import { MASTRA_RESOURCE_ID_KEY } from "memorysync-mastra";
const ctx = new RequestContext();
ctx.set(MASTRA_RESOURCE_ID_KEY, "customer-7");
await agent.generate(messages, { requestContext: ctx });
// 4. Mastra's own memory plumbing.
await agent.generate("...", { memory: { resource: "customer-7", thread: "t-1" } });No resolved user means no write. If none of the sources yields a user id, recall is skipped and the persist is refused — reported through onError, never written to a default scope.
Recall modes and switches
createMemorySyncProcessors({ userId, mode: "query" }); // relevant to the latest message (default)
createMemorySyncProcessors({ userId, mode: "profile" }); // overview of the user: newest facts, listed (1.0.1; 1.0.0 searched with a generic prompt and injected nothing)
createMemorySyncProcessors({ userId, mode: "full" }); // both
createMemorySyncProcessors({ userId, persist: false }); // read-only
createMemorySyncProcessors({ userId, recall: false }); // write-only
createMemorySyncProcessors({ userId, k: 12, template: "What you know:\n{context}" });Agent tools
import { createMemorySyncTools } from "memorysync-mastra";
const agent = new Agent({
// ...
tools: { ...createMemorySyncTools({ userId: "customer-7" }) },
});
// Untrusted agents: search + list only.
createMemorySyncTools({ userId: "customer-7", readOnly: true });add_memory, search_memory, list_memories, update_memory, delete_memory — the same five operations, same response strings as the MemorySync LangChain, AI SDK and CrewAI tool sets. A memory failure can never abort the agent run: tools return short readable error strings instead of throwing, and add_memory derives a client ref from the content so a repeating agent gets "already stored", never a duplicate.
Helpers
import { getMemoryContext, saveTurn, searchMemories } from "memorysync-mastra";
// Prompt-ready context block ("" for a new user)
const context = await getMemoryContext("what should I cook?", { userId: "customer-7" });
// Scored raw results
const hits = await searchMemories("dietary preferences", { userId: "customer-7" });
// Explicit persistence: sends the user's message for fact extraction
// (`assistant` is accepted and not stored). THROWS on failure (an explicit
// call is owed the truth), unlike the processors' reported-never-thrown
// discipline.
await saveTurn(
{ user: "I'm vegetarian", assistant: "Noted!", sessionId: "thread-42" },
{ userId: "customer-7" },
);All surfaces send the same idempotency seeds, so a retried turn — or the same turn sent by the processors and saveTurn — is recognised server-side and extracted once.
Version support
| Package | Requires | Runtime |
| --- | --- | --- |
| memorysync-mastra 1.1.0 | @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.
