{"slug": "add-long-term-memory-to-a-mastra-agent-input-output-processors-per-user-recall", "title": "Add long-term memory to a Mastra agent (input/output processors, per-user recall)", "summary": "Mohammed, founder of MemorySync, published a guide showing how to add per-user long-term memory to Mastra agents using the memorysync-mastra package's input and output processors. The integration hooks into Mastra's processor pipeline to extract durable facts about each user and recall them across threads, with identity resolved per call via a custom resolver, static IDs, or Mastra's RequestContext. The author notes the code is adapted from documentation and was not run for the post.", "body_md": "*Disclosure: I'm Mohammed, founder of MemorySync. I drafted this article with AI assistance and reviewed it for accuracy against the official MemorySync and Mastra docs. The code is adapted from the docs and I haven't run it.*\n\nMastra agents already have memory. Give an agent a `Memory` from `@mastra/memory` and pass `memory: { resource, thread }` on each call: `resource` is a stable id for the user and `thread` isolates one conversation ([Mastra memory docs](https://mastra.ai/docs/memory/overview)). Mastra also has working memory and semantic recall, stored in whichever storage provider you configure.\n\nIf that's working for you, keep it. This post is for when you want durable facts about each user extracted and recalled by a hosted service, wired in through Mastra's own processor pipeline. It uses MemorySync's Mastra integration, `memorysync-mastra`. MemorySync is a hosted memory layer, and I'm building it. Mastra behaviour comes from its docs on [memory](https://mastra.ai/docs/memory/overview) and [processors](https://mastra.ai/docs/agents/processors). Everything about MemorySync comes from its [Mastra Memory guide](https://docs.memorysync.io/guides/mastra?utm_source=devto&utm_medium=social&utm_campaign=pivot_oct19&utm_content=mastra-agent-long-term-memory-processors). The code is adapted from those docs. I haven't run it for this post, so check it against your versions.\n\n```\nnpm install memorysync-mastra\n```\n\nPer the guide it supports `@mastra/core` 1.42+ (Node 20+ for the package; current `@mastra/core` itself requires Node 22+). npm currently lists 1.1.0 with peer `@mastra/core >=1.42.0 <2.0.0`. Set `MEMORYSYNC_API_KEY` in the environment, or pass `apiKey`, and keep it server-side.\n\nMastra processors are hooks in the agent pipeline: `inputProcessors` run before messages reach the model, `outputProcessors` run after the model responds and before the result is returned ([processors docs](https://mastra.ai/docs/agents/processors)). MemorySync uses one of each:\n\n``` js\nimport { Agent } from \"@mastra/core/agent\";\nimport { openai } from \"@ai-sdk/openai\";\nimport { createMemorySyncProcessors } from \"memorysync-mastra\";\n\nconst { inputProcessor, outputProcessor } = createMemorySyncProcessors({\n  userId: \"customer-7\",     // scopes memory to one end user\n  sessionId: \"thread-42\",   // groups the stored facts by thread\n});\n\nconst agent = new Agent({\n  id: \"assistant\",\n  name: \"Assistant\",\n  instructions: \"You are a helpful assistant.\",\n  model: openai(\"your-model\"),\n  inputProcessors: [inputProcessor],\n  outputProcessors: [outputProcessor],\n});\n\nawait agent.generate(\"I'm vegetarian and I fly aisle.\");\n\n// Later, same user, any thread\nconst { text } = await agent.generate(\"Book my trip: flight plus a dinner spot.\");\n```\n\nPer the guide:\n\n`generate`/` stream` call (it uses `processInput`, not the per-step hook), so multi-step tool loops don't add the block twice. Your instructions stay first; the memory block is appended after them.`onError` (a console warning by default) and never thrown.\nPrefer less wiring? `withMemorySync(config, { userId })` wraps your agent config and merges both processors in, keeping any processors you already have (MemorySync's run after yours).\n\nA Mastra server agent serves many users, so identity is resolved per call, in this priority order:\n\n``` js\nimport { RequestContext } from \"@mastra/core/request-context\";\nimport {\n  createMemorySyncProcessors,\n  MASTRA_RESOURCE_ID_KEY,\n  MASTRA_THREAD_ID_KEY,\n} from \"memorysync-mastra\";\n\n// 1. Your own resolver wins over everything\ncreateMemorySyncProcessors({\n  resolveIdentity: (requestContext) => ({\n    userId: currentSession.userId,\n    sessionId: currentSession.threadId,\n  }),\n});\n\n// 2. Static ids (scripts, workers)\ncreateMemorySyncProcessors({ userId: \"customer-7\", sessionId: \"thread-42\" });\n\n// 3. Mastra's RequestContext, set by your server middleware\nconst ctx = new RequestContext();\nctx.set(MASTRA_RESOURCE_ID_KEY, \"customer-7\");\nctx.set(MASTRA_THREAD_ID_KEY, \"thread-42\");\nawait agent.generate(messages, { requestContext: ctx });\n\n// 4. Mastra's own memory ids on the call\nawait agent.generate(\"I moved to Lisbon.\", {\n  memory: { resource: \"customer-7\", thread: \"thread-42\" },\n});\n```\n\nOption 4 is handy if you already use Mastra memory: the same `resource` id becomes the MemorySync user. If no source yields a user id, recall is skipped and the write is refused (reported through `onError`), never sent to a default scope. Take the id from your auth session, not from the request body.\n\n``` js\nconst stream = await agent.stream(\"Plan a dinner for my team.\", {\n  memory: { resource: \"customer-7\", thread: \"thread-42\" },\n});\nfor await (const chunk of stream.textStream) process.stdout.write(chunk);\n```\n\nThe user's message is sent once, after the full stream. Per the guide, a stream that errors mid-flight (`finishReason: \"error\"`) sends nothing, so the retry sends the message once.\n\n```\ncreateMemorySyncProcessors({ userId, mode: \"query\" });   // relevant to the latest message (default)\ncreateMemorySyncProcessors({ userId, mode: \"profile\" }); // stable overview of the user\ncreateMemorySyncProcessors({ userId, mode: \"full\" });    // both\n\ncreateMemorySyncProcessors({ userId, persist: false });  // read-only\ncreateMemorySyncProcessors({ userId, recall: false });   // write-only\n\ncreateMemorySyncProcessors({\n  userId,\n  k: 12,\n  template: \"What you know about this user:\\n{context}\",\n});\njs\nimport { createMemorySyncTools } from \"memorysync-mastra\";\n\nconst agent = new Agent({\n  id: \"assistant\",\n  name: \"Assistant\",\n  instructions: \"Use the memory tools to remember durable facts.\",\n  model: openai(\"your-model\"),\n  tools: { ...createMemorySyncTools({ userId: \"customer-7\" }) },\n});\n\n// For agents you don't fully trust: search and list only\ncreateMemorySyncTools({ userId: \"customer-7\", readOnly: true });\n```\n\nThe five tools are `add_memory`, `search_memory`, `list_memories`, `update_memory` (tags and importance only) and `delete_memory`; they return readable error strings instead of throwing. For workflows there are `getMemoryContext`, `searchMemories` and `saveTurn`. `saveTurn` sends only the user's message and throws on failure. The guide says a turn sent by both the processors and `saveTurn` is recognised server-side and extracted once.\n\n`resource`) must match across calls. The thread can differ.`@mastra/core` 1.x from 1.42, and current core needs Node 22+.\n**Does a Mastra agent remember previous conversations?**\n\nYes, if you give it a `Memory` from `@mastra/memory` and pass `memory: { resource, thread }` on each call. To recall earlier messages, call the agent with the same `resource` and `thread` values. `lastMessages` controls how many recent messages are included ([Mastra memory docs](https://mastra.ai/docs/memory/overview)).\n\n**Where does Mastra store memory?**\n\nIn the storage provider you configure. Memory needs a storage provider to persist message history; the quickstart uses `@mastra/libsql` ([Mastra memory docs](https://mastra.ai/docs/memory/overview)).\n\n**What's the difference between resource and thread in Mastra memory?**\n\n`resource` is a stable id for the user or entity, and `thread` isolates one conversation. Each thread's owner can't be changed after creation, so don't reuse a thread id across owners. Working memory and semantic recall are resource-scoped by default, so they carry across that user's threads ([Mastra memory docs](https://mastra.ai/docs/memory/overview)).\n\n**Can I use Mastra's Memory and the MemorySync processors together?**\n\nYes. MemorySync's guide recommends running both: Mastra's `Memory` for the conversation history, and the MemorySync processors for durable per-user facts across threads. The processors can also read the user and thread from the same `memory: { resource, thread }` option ([MemorySync Mastra guide](https://docs.memorysync.io/guides/mastra?utm_source=devto&utm_medium=social&utm_campaign=pivot_oct19&utm_content=mastra-agent-long-term-memory-processors)).\n\nIf you want to see the save, reload and recall loop before wiring up an agent, the MemorySync playground runs it in your browser: [memorysync.io/try](https://memorysync.io/try?utm_source=devto&utm_medium=social&utm_campaign=pivot_oct19&utm_content=mastra-agent-long-term-memory-processors).", "url": "https://wpnews.pro/news/add-long-term-memory-to-a-mastra-agent-input-output-processors-per-user-recall", "canonical_source": "https://dev.to/memorysync_rafay/add-long-term-memory-to-a-mastra-agent-inputoutput-processors-per-user-recall-74e", "published_at": "2026-10-24 16:09:52+00:00", "updated_at": "2026-10-24 16:13:38.214764+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "large-language-models"], "entities": ["MemorySync", "Mastra", "Mohammed", "OpenAI", "Node.js"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/add-long-term-memory-to-a-mastra-agent-input-output-processors-per-user-recall", "markdown": "https://wpnews.pro/news/add-long-term-memory-to-a-mastra-agent-input-output-processors-per-user-recall.md", "text": "https://wpnews.pro/news/add-long-term-memory-to-a-mastra-agent-input-output-processors-per-user-recall.txt", "jsonld": "https://wpnews.pro/news/add-long-term-memory-to-a-mastra-agent-input-output-processors-per-user-recall.jsonld"}}