<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mohammed Rafay</title>
    <description>The latest articles on DEV Community by Mohammed Rafay (@memorysync_rafay).</description>
    <link>https://dev.to/memorysync_rafay</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4123021%2F3af1ce08-e8c9-461f-9e60-8f5e739287cd.jpeg</url>
      <title>DEV Community: Mohammed Rafay</title>
      <link>https://dev.to/memorysync_rafay</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/memorysync_rafay"/>
    <language>en</language>
    <item>
      <title>Fix "ModuleNotFoundError: No module named 'agno.memory.v2'" (Agno v1 v2+ memory migration)</title>
      <dc:creator>Mohammed Rafay</dc:creator>
      <pubDate>Sun, 25 Oct 2026 16:40:14 +0000</pubDate>
      <link>https://dev.to/memorysync_rafay/fix-modulenotfounderror-no-module-named-agnomemoryv2-agno-v1-v2-memory-migration-onl</link>
      <guid>https://dev.to/memorysync_rafay/fix-modulenotfounderror-no-module-named-agnomemoryv2-agno-v1-v2-memory-migration-onl</guid>
      <description>&lt;p&gt;&lt;em&gt;Disclosure: I'm Mohammed, founder of MemorySync. I drafted this with AI assistance and checked it against Agno's official migration guide and a real install. Code status: on Oct 25, 2026 I installed agno 3.1.10 (Python 3.13) and reproduced the errors below. I then ran the "after" imports, the Agent construction and &lt;code&gt;get_user_memories&lt;/code&gt; successfully. No model was called (no key), so memory creation itself was not run.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The error
&lt;/h2&gt;

&lt;p&gt;You upgrade Agno and memory code that used to work fails on import:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ModuleNotFoundError: No module named 'agno.memory.v2'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or, in VS Code / Pylance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Import "agno.memory.v2.memory" could not be resolved
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;often with a companion:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ModuleNotFoundError: No module named 'agno.storage'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This was reported in &lt;a href="https://github.com/agno-agi/agno/issues/5017" rel="noopener noreferrer"&gt;agno-agi/agno#5017&lt;/a&gt; (Agno 2.1.4, Python 3.11). The maintainers' answer was to follow the memory section of the v2 migration guide. I reproduced all three errors on Agno 3.1.10:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ python -c "from agno.memory.v2.memory import Memory"
ModuleNotFoundError: No module named 'agno.memory.v2'
$ python -c "from agno.storage.sqlite import SqliteStorage"
ModuleNotFoundError: No module named 'agno.storage'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why it happens
&lt;/h2&gt;

&lt;p&gt;Agno v2 removed the v1 memory and storage modules rather than aliasing them. Per the &lt;a href="https://docs.agno.com/other/v2-migration" rel="noopener noreferrer"&gt;v2 migration guide&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Storage&lt;/code&gt; classes moved from &lt;code&gt;agno/storage&lt;/code&gt; to &lt;code&gt;agno/db&lt;/code&gt; and are now called &lt;code&gt;Db&lt;/code&gt; classes. One instance serves agents, teams and workflows, and the &lt;code&gt;mode&lt;/code&gt; and &lt;code&gt;table_name&lt;/code&gt; parameters are gone.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;MemoryDb&lt;/code&gt; classes are deprecated in favour of the main &lt;code&gt;Db&lt;/code&gt; classes.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;Memory&lt;/code&gt; class is deprecated. You set &lt;code&gt;update_memory_on_run=True&lt;/code&gt; on an Agent that has a &lt;code&gt;db&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The fix: before and after
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;v1 (breaks):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;agno.agent&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;agno.memory.v2.db.sqlite&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SqliteMemoryDb&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;agno.memory.v2.memory&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Memory&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;agno.storage.sqlite&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SqliteStorage&lt;/span&gt;

&lt;span class="n"&gt;memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Memory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;SqliteMemoryDb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;table_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user_memories&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;db_file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agno.db&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;storage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SqliteStorage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;table_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agent_sessions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;db_file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agno.db&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;enable_user_memories&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;v2+ (ran on 3.1.10):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;agno.agent&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;agno.db.sqlite&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SqliteDb&lt;/span&gt;

&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SqliteDb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db_file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agno.db&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;# sessions AND memories
&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;update_memory_on_run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_user_memories&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;john@example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# [] until a run stores something
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Things to check after the swap:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pass &lt;code&gt;user_id&lt;/code&gt; on every run.&lt;/strong&gt; Memories are stored per user, so a missing or changing &lt;code&gt;user_id&lt;/code&gt; looks like "memory isn't stored".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Table names.&lt;/strong&gt; If you need your old table names, &lt;code&gt;SqliteDb&lt;/code&gt; takes &lt;code&gt;session_table=&lt;/code&gt; and &lt;code&gt;memory_table=&lt;/code&gt; instead of &lt;code&gt;table_name=&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom memory model or instructions.&lt;/strong&gt; Build a &lt;code&gt;MemoryManager&lt;/code&gt; (&lt;code&gt;from agno.memory import MemoryManager&lt;/code&gt;) and pass &lt;code&gt;memory_manager=&lt;/code&gt; to the Agent. The parameter names I found on the current &lt;code&gt;Agent&lt;/code&gt; are &lt;code&gt;memory_manager&lt;/code&gt;, &lt;code&gt;enable_agentic_memory&lt;/code&gt;, &lt;code&gt;update_memory_on_run&lt;/code&gt; and &lt;code&gt;add_memories_to_context&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Existing v1 data.&lt;/strong&gt; The migration guide links a v1-to-v2 migration script. It says to use the script from the v2 release matching your environment and not to run it under Agno v3, so migrate to v2 first, verify, then upgrade. Back up your database before running anything against it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;TypeError: UserMemory.__init__() got an unexpected keyword argument 'summaries'&lt;/code&gt;.&lt;/strong&gt; This is a different, older error from mixed v1 memory data (&lt;a href="https://github.com/agno-agi/agno/issues/2955" rel="noopener noreferrer"&gt;#2955&lt;/a&gt;, Agno 1.4.1). The reporter confirmed it was gone after the maintainers' follow-up, so upgrade first.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you get &lt;code&gt;ImportError: The SQLAlchemy asyncio module requires that the Python 'greenlet' library is installed&lt;/code&gt; when importing &lt;code&gt;agno.db.sqlite&lt;/code&gt;, run &lt;code&gt;pip install greenlet&lt;/code&gt;. I hit this on a clean Python 3.13 venv.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you want memories outside the local database
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;update_memory_on_run&lt;/code&gt; writes to whatever &lt;code&gt;Db&lt;/code&gt; you pass. If you want sessions to stay local but user memories to live in a hosted store shared across instances, MemorySync's &lt;code&gt;agno-memorysync&lt;/code&gt; package backs Agno's &lt;code&gt;MemoryManager&lt;/code&gt; (&lt;a href="https://docs.memorysync.io/guides/agno?utm_source=devto&amp;amp;utm_medium=organic_page&amp;amp;utm_campaign=v5_oct25&amp;amp;utm_content=pseo_agno-memory-v2-module-not-found" rel="noopener noreferrer"&gt;guide&lt;/a&gt;). The guide says it needs agno 2.8+, and this constructed fine on 3.1.10:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;agno.agent&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;agno.db.sqlite&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SqliteDb&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;agno.memory&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MemoryManager&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;agno_memorysync&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MemorySyncDb&lt;/span&gt;   &lt;span class="c1"&gt;# pip install agno-memorysync; needs MEMORYSYNC_API_KEY
&lt;/span&gt;
&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;SqliteDb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db_file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agent.db&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;                  &lt;span class="c1"&gt;# sessions: local
&lt;/span&gt;    &lt;span class="n"&gt;memory_manager&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;MemoryManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;MemorySyncDb&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;  &lt;span class="c1"&gt;# memories: hosted
&lt;/span&gt;    &lt;span class="n"&gt;update_memory_on_run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer-42&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The guide notes that &lt;code&gt;MemorySyncDb&lt;/code&gt; is memory-only on purpose. Pointing sessions at it raises &lt;code&gt;MemorySyncMemoryOnlyError&lt;/code&gt;, so keep &lt;code&gt;db=&lt;/code&gt; local.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Is there a compatibility shim for &lt;code&gt;agno.memory.v2&lt;/code&gt;?&lt;/strong&gt; Not in 3.1.10. The import fails. Update the imports.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I still need a separate storage object?&lt;/strong&gt; No. One &lt;code&gt;Db&lt;/code&gt; instance handles sessions, memories, metrics, evals and knowledge in separate tables.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My memories are stored but never show up in replies.&lt;/strong&gt; If you pass a verbatim &lt;code&gt;system_message=&lt;/code&gt;, it replaces Agno's built system prompt, which is where memories are injected. Use &lt;code&gt;instructions=&lt;/code&gt; instead. (This tip is from MemorySync's Agno guide and applies to any memory db.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which Agno version is current?&lt;/strong&gt; PyPI served 3.1.10 on Oct 25, 2026. The v2 migration steps above still applied to it.&lt;/p&gt;

&lt;p&gt;To see a stored fact survive a reload before wiring anything in: &lt;strong&gt;&lt;a href="https://memorysync.io/try?utm_source=devto&amp;amp;utm_medium=organic_page&amp;amp;utm_campaign=v5_oct25&amp;amp;utm_content=pseo_agno-memory-v2-module-not-found" rel="noopener noreferrer"&gt;try MemorySync in your browser&lt;/a&gt;&lt;/strong&gt;. No signup is needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;agno-agi/agno#5017 (the import error): &lt;a href="https://github.com/agno-agi/agno/issues/5017" rel="noopener noreferrer"&gt;https://github.com/agno-agi/agno/issues/5017&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;agno-agi/agno#2955 (&lt;code&gt;summaries&lt;/code&gt; TypeError): &lt;a href="https://github.com/agno-agi/agno/issues/2955" rel="noopener noreferrer"&gt;https://github.com/agno-agi/agno/issues/2955&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Agno v2 migration guide: &lt;a href="https://docs.agno.com/other/v2-migration" rel="noopener noreferrer"&gt;https://docs.agno.com/other/v2-migration&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Agno, Agent with Memory: &lt;a href="https://docs.agno.com/memory/agent/agent-with-memory" rel="noopener noreferrer"&gt;https://docs.agno.com/memory/agent/agent-with-memory&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;MemorySync Agno guide: &lt;a href="https://docs.memorysync.io/guides/agno" rel="noopener noreferrer"&gt;https://docs.memorysync.io/guides/agno&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>agents</category>
      <category>debugging</category>
    </item>
    <item>
      <title>Strands Agents memory: how to add long-term memory to a Strands agent (MemoryManager, stores and per-user recall)</title>
      <dc:creator>Mohammed Rafay</dc:creator>
      <pubDate>Sun, 25 Oct 2026 16:39:11 +0000</pubDate>
      <link>https://dev.to/memorysync_rafay/strands-agents-memory-how-to-add-long-term-memory-to-a-strands-agent-memorymanager-stores-and-26al</link>
      <guid>https://dev.to/memorysync_rafay/strands-agents-memory-how-to-add-long-term-memory-to-a-strands-agent-memorymanager-stores-and-26al</guid>
      <description>&lt;p&gt;&lt;em&gt;Disclosure: I'm Mohammed, founder of MemorySync. I drafted this with AI assistance and checked it against the Strands Agents docs and MemorySync's Strands guide. Code status: on Oct 25, 2026 I installed strands-agents 1.62.0 and strands-memorysync 1.1.0 on Python 3.13 and ran every import and constructor below (agent, MemoryManager, TestMemoryStore, MemorySyncStore with InvocationTrigger, &lt;code&gt;get_tools()&lt;/code&gt;). I also ran one &lt;code&gt;agent(...)&lt;/code&gt; turn end to end with a scripted stand-in model and MemorySync's HTTP responses mocked locally, to see what the store sends and what reaches the model. No live LLM or live MemorySync API call was made.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;By default a Strands agent starts each conversation from zero. Strands Agents memory is one of three kinds of state the Strands docs separate (&lt;a href="https://strandsagents.com/docs/user-guide/sdk/memory/overview/" rel="noopener noreferrer"&gt;memory overview&lt;/a&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Session management&lt;/strong&gt; persists the full conversation so you can resume it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conversation management&lt;/strong&gt; keeps a session inside the context window.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory&lt;/strong&gt; carries durable knowledge &lt;em&gt;across&lt;/em&gt; sessions without replaying old conversations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your agent "forgets the user" between sessions, you need the third one.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Strands memory works
&lt;/h2&gt;

&lt;p&gt;You attach a &lt;code&gt;MemoryManager&lt;/code&gt; with one or more &lt;strong&gt;stores&lt;/strong&gt;. Across those stores, the manager does three jobs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Recall&lt;/strong&gt;: the agent searches memory on demand through a tool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Injection&lt;/strong&gt;: relevant memories are added to the prompt automatically before the model runs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extraction&lt;/strong&gt;: conversation messages are turned into memories and written to stores.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Recall and injection are on by default when you attach a store. Writing is opt-in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Prototype with the local test store
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands.memory&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MemoryManager&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands.vended_memory_stores.test_memory_store&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TestMemoryStore&lt;/span&gt;

&lt;span class="c1"&gt;# Persists to ~/.strands/memory/notes.json by default.
&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TestMemoryStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;notes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memory_manager&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;MemoryManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stores&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ran as written. It's fine on your laptop, but a JSON file in a home directory isn't per-user and won't follow a container redeploy. For production, Strands documents an Amazon Bedrock Knowledge Base store, a file store on any storage backend, and a custom-store interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Swap in a per-user hosted store
&lt;/h2&gt;

&lt;p&gt;MemorySync ships &lt;code&gt;strands-memorysync&lt;/code&gt;, a store for the same MemoryManager (&lt;a href="https://docs.memorysync.io/guides/strands?utm_source=devto&amp;amp;utm_medium=organic_page&amp;amp;utm_campaign=v5_oct25&amp;amp;utm_content=pseo_strands-agents-long-term-memory" rel="noopener noreferrer"&gt;guide&lt;/a&gt;). It needs Python 3.10+, a Strands release with the MemoryManager (2026+), and a MemorySync API key in &lt;code&gt;MEMORYSYNC_API_KEY&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;strands-agents strands-memorysync
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands.memory&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MemoryManager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;InvocationTrigger&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands_memorysync&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MemorySyncStore&lt;/span&gt;

&lt;span class="n"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MemorySyncStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer-42&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;# required: whose memories these are
&lt;/span&gt;    &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;support&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         &lt;span class="c1"&gt;# optional: tags this conversation's facts
&lt;/span&gt;    &lt;span class="n"&gt;extraction&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;trigger&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;InvocationTrigger&lt;/span&gt;&lt;span class="p"&gt;()},&lt;/span&gt;  &lt;span class="c1"&gt;# send every turn, see below
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memory_manager&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;MemoryManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stores&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
&lt;span class="nf"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Which seat should I book?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What happens: before every model call, the manager asks the store for relevant memories and injects them. After each exchange, only the &lt;strong&gt;user's&lt;/strong&gt; messages are sent for fact extraction. Assistant replies, tool messages and system messages are not sent. In my local run (stand-in model, mocked API), the recalled memory showed up in the model's input, and the only write was the user's message with &lt;code&gt;role: "user"&lt;/code&gt;. The assistant's reply was not sent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Fix the "first few turns are forgotten" surprise
&lt;/h2&gt;

&lt;p&gt;Strands' default extraction trigger fires every five invocations, so a user's first exchanges aren't written yet and early recalls come back empty. That's by design. The synchronous &lt;code&gt;agent(...)&lt;/code&gt; call also flushes after each call, but &lt;code&gt;invoke_async&lt;/code&gt; does not. If you want memories from the first exchange, pass &lt;code&gt;InvocationTrigger()&lt;/code&gt; as above. In my local run, the first turn's user message was written right after that first call. The MemorySync guide recommends this because its writes are idempotent (a retry isn't extracted twice).&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Pick a stable &lt;code&gt;user_id&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Use your own user ID, an email or a UUID, and use the &lt;strong&gt;same&lt;/strong&gt; value every session. A different value means an empty recall. The &lt;code&gt;user_id&lt;/code&gt; is also the isolation boundary between your users. Create one store per user (or per request) rather than sharing one store across users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Optional: explicit memory tools
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_tools&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;   &lt;span class="c1"&gt;# -&amp;gt; memorysync_search, memorysync_save
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I confirmed these two names on the box, and the defaults in the table below against the installed &lt;code&gt;MemorySyncStore&lt;/code&gt; signature. There's deliberately no delete tool, so deletion stays an API or admin action, and the user identity is never a tool parameter the model can change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Useful knobs
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;top_k&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Memories considered per model call&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;min_prompt_chars&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Skip recall for trivial prompts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;writable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;True&lt;/td&gt;
&lt;td&gt;Allow extraction writes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Common questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Do I still need a session manager?&lt;/strong&gt; Yes, if you want to resume the exact conversation. Memory and session persistence solve different problems, and Strands documents them separately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why are recalls empty right after I store something?&lt;/strong&gt; Extraction runs asynchronously, so give it a moment. Also check that both runs use the same &lt;code&gt;user_id&lt;/code&gt;, and that you're not on the five-turn default trigger.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;ModuleNotFoundError: strands&lt;/code&gt;?&lt;/strong&gt; Install the framework itself: &lt;code&gt;pip install strands-agents&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Want to watch a fact survive a page reload before you wire it in? &lt;strong&gt;&lt;a href="https://memorysync.io/try?utm_source=devto&amp;amp;utm_medium=organic_page&amp;amp;utm_campaign=v5_oct25&amp;amp;utm_content=pseo_strands-agents-long-term-memory" rel="noopener noreferrer"&gt;Try MemorySync in the browser&lt;/a&gt;&lt;/strong&gt;. No signup is needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Strands Agents, Memory overview: &lt;a href="https://strandsagents.com/docs/user-guide/sdk/memory/overview/" rel="noopener noreferrer"&gt;https://strandsagents.com/docs/user-guide/sdk/memory/overview/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Strands Agents, Control what the agent remembers: &lt;a href="https://strandsagents.com/docs/user-guide/sdk/memory/managing-memory/" rel="noopener noreferrer"&gt;https://strandsagents.com/docs/user-guide/sdk/memory/managing-memory/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;MemorySync Strands Agents guide: &lt;a href="https://docs.memorysync.io/guides/strands" rel="noopener noreferrer"&gt;https://docs.memorysync.io/guides/strands&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;strands-memorysync on PyPI: &lt;a href="https://pypi.org/project/strands-memorysync/" rel="noopener noreferrer"&gt;https://pypi.org/project/strands-memorysync/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>agents</category>
      <category>aws</category>
    </item>
    <item>
      <title>How to add memory to a Google ADK agent that survives restarts (MemoryService, preload_memory, per-user recall)</title>
      <dc:creator>Mohammed Rafay</dc:creator>
      <pubDate>Sun, 25 Oct 2026 16:39:02 +0000</pubDate>
      <link>https://dev.to/memorysync_rafay/how-to-add-memory-to-a-google-adk-agent-that-survives-restarts-memoryservice-preloadmemory-2jcb</link>
      <guid>https://dev.to/memorysync_rafay/how-to-add-memory-to-a-google-adk-agent-that-survives-restarts-memoryservice-preloadmemory-2jcb</guid>
      <description>&lt;p&gt;&lt;em&gt;Disclosure: I'm Mohammed, founder of MemorySync. I drafted this with AI assistance and checked it against Google's ADK docs and MemorySync's ADK guide. Code status: on Oct 25, 2026 I installed google-adk 2.13.0 and google-adk-memorysync 1.1.0 (Python 3.13) and ran all imports and the agent, Runner, context tool, callbacks and tools construction shown. I then ran the whole flow locally with a stand-in model and MemorySync's HTTP responses mocked: one turn, &lt;code&gt;add_session_to_memory&lt;/code&gt;, a second ingest of the same session, and a turn in a fresh Runner and session that used &lt;code&gt;preload_memory&lt;/code&gt;. Later the same day I reran the flow against the live MemorySync API (no live Gemini call; stand-in model).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In Google's Agent Development Kit (ADK), a &lt;strong&gt;Session&lt;/strong&gt; holds the events and state of one conversation. The ADK docs call that short-term memory. Recalling things from &lt;em&gt;earlier&lt;/em&gt; conversations is the job of a separate &lt;strong&gt;MemoryService&lt;/strong&gt; (&lt;a href="https://adk.dev/sessions/memory/" rel="noopener noreferrer"&gt;ADK memory docs&lt;/a&gt;). If your agent forgets a user every time you restart or open a new session, this is the layer to fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  What ADK gives you
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;BaseMemoryService&lt;/code&gt; has four operations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;add_session_to_memory(session)&lt;/code&gt;: ingest a completed session.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;add_events_to_memory(...)&lt;/code&gt;: append a delta, such as the latest turn.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;add_memory(...)&lt;/code&gt;: add explicit &lt;code&gt;MemoryEntry&lt;/code&gt; objects.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;search_memory(...)&lt;/code&gt;: query, usually through a tool.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The docs note that &lt;code&gt;add_events_to_memory&lt;/code&gt; and &lt;code&gt;add_memory&lt;/code&gt; are optional and not every service implements them. Python ADK ships three services:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;Persistence (per ADK docs)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;InMemoryMemoryService&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;None: data is lost on restart&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;VertexAiMemoryBankService&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes, managed by Google's Agent Platform&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;VertexAiRagMemoryService&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes, stored in Knowledge Engine&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Agents read memory through ADK's built-in tools. &lt;code&gt;load_memory&lt;/code&gt; lets the model decide when to search, and &lt;code&gt;preload_memory&lt;/code&gt; injects results before the turn.&lt;/p&gt;

&lt;p&gt;So your options are: stay in-memory (fine for dev), adopt Vertex AI's services, or implement &lt;code&gt;BaseMemoryService&lt;/code&gt; against another backend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Plugging in a third-party MemoryService
&lt;/h2&gt;

&lt;p&gt;MemorySync publishes &lt;code&gt;google-adk-memorysync&lt;/code&gt;, which implements all four &lt;code&gt;BaseMemoryService&lt;/code&gt; methods (&lt;a href="https://docs.memorysync.io/guides/google-adk?utm_source=devto&amp;amp;utm_medium=organic_page&amp;amp;utm_campaign=v5_oct25&amp;amp;utm_content=pseo_google-adk-memory-across-sessions" rel="noopener noreferrer"&gt;guide&lt;/a&gt;). It requires &lt;code&gt;google-adk&lt;/code&gt; 2.0+ and Python 3.10+. Set &lt;code&gt;MEMORYSYNC_API_KEY&lt;/code&gt; server-side.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="s2"&gt;"google-adk&amp;gt;=2"&lt;/span&gt; google-adk-memorysync
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 1: Wire the service into the Runner
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google.adk.agents&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LlmAgent&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google.adk.runners&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Runner&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google.adk.sessions&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;InMemorySessionService&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google.adk.tools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;preload_memory&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google_adk_memorysync&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MemorySyncMemoryService&lt;/span&gt;

&lt;span class="n"&gt;memory_service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MemorySyncMemoryService&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# reads MEMORYSYNC_API_KEY
&lt;/span&gt;
&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LlmAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;assistant&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemini-2.5-flash&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;# any model your ADK setup supports
&lt;/span&gt;    &lt;span class="n"&gt;instruction&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You are a helpful assistant.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;preload_memory&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;     &lt;span class="c1"&gt;# ADK's own tool, backed by the service
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;runner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Runner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;app_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;support&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;session_service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;InMemorySessionService&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;memory_service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;memory_service&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This construction ran on the box.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Ingest the session when a conversation ends
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;runner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_session&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;app_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;support&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer-7&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;thread-42&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;memory_service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_session_to_memory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Per the guide, this sends the text messages the user wrote for fact extraction. Agent replies, partial streams and tool traffic are skipped. It raises on failure. Re-ingesting the same session doesn't extract anything twice, because each message carries a seed derived from its ADK event id. The next session for &lt;code&gt;customer-7&lt;/code&gt;, in any process, has &lt;code&gt;preload_memory&lt;/code&gt; inject what's known.&lt;/p&gt;

&lt;p&gt;In my local run (stand-in model, mocked API), the ingest sent only the user's message, the second ingest sent the same event-id-based seed again, and the stored fact appeared in the model's input on a new session in a fresh Runner. In a later run against the live MemorySync API, ingesting the same session a second time returned &lt;code&gt;already_exists: true&lt;/code&gt; and nothing was extracted twice, and a fresh Runner and session got the stored fact injected.&lt;/p&gt;

&lt;p&gt;The ADK &lt;code&gt;user_id&lt;/code&gt; is your isolation key. Use your real, stable user id. By default a user's memory follows them across ADK apps. Pass &lt;code&gt;MemorySyncMemoryService(scope_to_app=True)&lt;/code&gt; to keep each &lt;code&gt;app_name&lt;/code&gt; separate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3 (alternative): Inject and capture every turn
&lt;/h3&gt;

&lt;p&gt;If you don't want to manage the end-of-session ingest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google_adk_memorysync&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MemorySyncContextTool&lt;/span&gt;

&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LlmAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;assistant&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemini-2.5-flash&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;instruction&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You are a helpful assistant.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;MemorySyncContextTool&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;   &lt;span class="c1"&gt;# recall before each turn + save the user's message
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a local run with a stand-in model and a mocked API, a stored fact reached the model's input and the user's message was the only thing written.&lt;/p&gt;

&lt;p&gt;Use one capture path, not both. If you call &lt;code&gt;add_session_to_memory&lt;/code&gt;, set &lt;code&gt;MemorySyncContextTool(persist=False)&lt;/code&gt; and don't use the capture callback, so each message is sent once (the guide explains that the two paths use different seed schemes).&lt;/p&gt;

&lt;h3&gt;
  
  
  Optional: memory tools for the model
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;create_memorysync_tools(user_id="customer-7")&lt;/code&gt; returns five tools: add, search, list, update and delete. On the box it returned 5 tools. Pass &lt;code&gt;read_only=True&lt;/code&gt; for untrusted agents (that returned 2 on the box).&lt;/p&gt;

&lt;h2&gt;
  
  
  Common questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Why does my ADK agent forget everything after a restart?&lt;/strong&gt; You're on &lt;code&gt;InMemoryMemoryService&lt;/code&gt; (or no memory service at all). Its data lives in process memory, so ADK's docs say it is lost on restart.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;load_memory&lt;/code&gt; or &lt;code&gt;preload_memory&lt;/code&gt;?&lt;/strong&gt; &lt;code&gt;load_memory&lt;/code&gt; leaves recall to the model. &lt;code&gt;preload_memory&lt;/code&gt; injects results before the turn. If recall must happen every turn, use injection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is there a TypeScript version?&lt;/strong&gt; ADK has TypeScript, Go, Java and Kotlin SDKs. MemorySync's ADK package is Python only, and its docs point TypeScript users to its Vercel AI SDK or Mastra integrations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I use two memory services?&lt;/strong&gt; ADK's docs include an example of using two memory services. Check the "Can an agent have access to more than one memory service?" section.&lt;/p&gt;

&lt;p&gt;See a fact survive a full page reload in the browser first: &lt;strong&gt;&lt;a href="https://memorysync.io/try?utm_source=devto&amp;amp;utm_medium=organic_page&amp;amp;utm_campaign=v5_oct25&amp;amp;utm_content=pseo_google-adk-memory-across-sessions" rel="noopener noreferrer"&gt;try MemorySync&lt;/a&gt;&lt;/strong&gt;. No signup is needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;ADK docs, Memory: &lt;a href="https://adk.dev/sessions/memory/" rel="noopener noreferrer"&gt;https://adk.dev/sessions/memory/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;MemorySync Google ADK guide: &lt;a href="https://docs.memorysync.io/guides/google-adk" rel="noopener noreferrer"&gt;https://docs.memorysync.io/guides/google-adk&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;google-adk-memorysync on PyPI: &lt;a href="https://pypi.org/project/google-adk-memorysync/" rel="noopener noreferrer"&gt;https://pypi.org/project/google-adk-memorysync/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>agents</category>
      <category>gemini</category>
    </item>
    <item>
      <title>Fix "'ClusterPipeline' object has no attribute 'raise_first_error'" with OpenAI Agents SDK RedisSession on Redis Cluster</title>
      <dc:creator>Mohammed Rafay</dc:creator>
      <pubDate>Sun, 25 Oct 2026 16:38:00 +0000</pubDate>
      <link>https://dev.to/memorysync_rafay/fix-clusterpipeline-object-has-no-attribute-raisefirsterror-with-openai-agents-sdk-3dpa</link>
      <guid>https://dev.to/memorysync_rafay/fix-clusterpipeline-object-has-no-attribute-raisefirsterror-with-openai-agents-sdk-3dpa</guid>
      <description>&lt;p&gt;&lt;em&gt;Disclosure: I'm Mohammed, founder of MemorySync. I drafted this with AI assistance from the public GitHub issue and the SDK source. Code status: on Oct 25, 2026 I installed openai-agents 0.24.0 and redis 8.1.0 (Python 3.13). I started a local 3-node Redis Cluster (Redis 8.0.2), confirmed that &lt;code&gt;ClusterPipeline&lt;/code&gt; lacks the hooked attributes, reproduced the new &lt;code&gt;TypeError&lt;/code&gt; from &lt;code&gt;add_items&lt;/code&gt; and &lt;code&gt;clear_session&lt;/code&gt; with a &lt;code&gt;RedisCluster&lt;/code&gt; client, and ran the custom session below against that cluster, including two &lt;code&gt;Runner.run&lt;/code&gt; turns with a stand-in model (no OpenAI key, so no real LLM call). I checked the hash-slot math with redis-py's &lt;code&gt;key_slot&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The error
&lt;/h2&gt;

&lt;p&gt;With the OpenAI Agents SDK's Redis-backed session and a cluster client (&lt;code&gt;redis.asyncio.cluster.RedisCluster&lt;/code&gt;), storing history fails:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;File "agents/extensions/memory/redis_session.py", line 483, in _write_items_attempt
    raise_first_error = pipe.raise_first_error
AttributeError: 'ClusterPipeline' object has no attribute 'raise_first_error'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is &lt;a href="https://github.com/openai/openai-agents-python/issues/5322" rel="noopener noreferrer"&gt;openai/openai-agents-python#5322&lt;/a&gt;, still open as of Oct 25, 2026. The reporter saw it from 0.20.0 through 0.23.1. Per the issue, &lt;code&gt;add_items&lt;/code&gt; and (on latest) &lt;code&gt;clear_session&lt;/code&gt; fail, &lt;code&gt;get_items&lt;/code&gt; and &lt;code&gt;pop_item&lt;/code&gt; work, and 0.14.5 worked for all four.&lt;/p&gt;

&lt;p&gt;On &lt;strong&gt;0.24.0&lt;/strong&gt; you'll get a clearer error instead. I reproduced this against a local Redis Cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TypeError: RedisSession does not support transactions with RedisCluster clients. Use redis.asyncio.Redis connected to a standalone Redis server, or implement a custom Session backend for Redis Cluster.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In 0.24.0 the check sits in the write path, not in &lt;code&gt;__init__&lt;/code&gt;. Constructing the session succeeds, and the first write raises. That comes from PR #5361 ("scope Redis Cluster rejection to transactional writes"), which the issue links and which was merged on Oct 14, 2026. On the cluster, &lt;code&gt;get_items&lt;/code&gt; and &lt;code&gt;pop_item&lt;/code&gt; still worked on 0.24.0, and &lt;code&gt;add_items&lt;/code&gt; and &lt;code&gt;clear_session&lt;/code&gt; raised the &lt;code&gt;TypeError&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it happens
&lt;/h2&gt;

&lt;p&gt;The issue identifies two causes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pipeline internals.&lt;/strong&gt; The write hooks &lt;code&gt;pipe.raise_first_error&lt;/code&gt; and &lt;code&gt;pipe.parse_response&lt;/code&gt;, which exist on the standalone &lt;code&gt;redis.asyncio.client.Pipeline&lt;/code&gt; but not on &lt;code&gt;ClusterPipeline&lt;/code&gt;. I checked: &lt;code&gt;hasattr(ClusterPipeline, "raise_first_error")&lt;/code&gt; is &lt;code&gt;False&lt;/code&gt;, and it's &lt;code&gt;True&lt;/code&gt; for &lt;code&gt;Pipeline&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-slot transaction.&lt;/strong&gt; The write is a &lt;code&gt;WATCH&lt;/code&gt;/&lt;code&gt;MULTI&lt;/code&gt; transaction over three keys: &lt;code&gt;{prefix}:{id}&lt;/code&gt;, &lt;code&gt;{prefix}:{id}:messages&lt;/code&gt; and &lt;code&gt;{prefix}:{id}:counter&lt;/code&gt;. Those keys hash to different cluster slots, and Redis Cluster only runs a transaction within one slot. So adding the missing hooks wouldn't be enough. With redis-py's &lt;code&gt;key_slot&lt;/code&gt;, &lt;code&gt;repro:s1&lt;/code&gt; and &lt;code&gt;repro:s1:messages&lt;/code&gt; landed in slots 7598 and 11344.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Fix options
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A. Point &lt;code&gt;RedisSession&lt;/code&gt; at a standalone (non-cluster) Redis.&lt;/strong&gt; This is what the SDK's error message suggests. It's simplest if your provider offers a non-cluster mode.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;B. Pin &lt;code&gt;openai-agents==0.14.5&lt;/code&gt;.&lt;/strong&gt; The issue reports that it worked on a cluster, and on my local cluster (with redis 8.1.0) add, get, pop and clear all ran. You'd give up every later fix, though. Use this only as a stopgap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;C. Write a small cluster-safe Session.&lt;/strong&gt; The SDK's sessions are a four-method interface (&lt;code&gt;get_items&lt;/code&gt;, &lt;code&gt;add_items&lt;/code&gt;, &lt;code&gt;pop_item&lt;/code&gt;, &lt;code&gt;clear_session&lt;/code&gt;). Use one list key with a Redis Cluster &lt;strong&gt;hash tag&lt;/strong&gt; (&lt;code&gt;{...}&lt;/code&gt;), so everything for a session lives in one slot and needs no transaction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;agents.memory&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SessionABC&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ClusterRedisSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SessionABC&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Session history on Redis Cluster: one list key, hash-tagged by session id.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;redis_client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key_prefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agents&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session_id&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_redis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redis_client&lt;/span&gt;
        &lt;span class="c1"&gt;# {...} is a hash tag: every key for this session maps to one slot.
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;key_prefix&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:{{&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;}}:messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_items&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_items&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rpush&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;pop_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rpop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;clear_session&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use it like any other session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;redis.asyncio.cluster&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RedisCluster&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;agents&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Runner&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RedisCluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your-cluster-host&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;6379&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ClusterRedisSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user-123:chat-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key_prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agents&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Runner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hi again&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What I verified on the local 3-node cluster: add/get (including &lt;code&gt;limit=1&lt;/code&gt; returning the newest item), pop and clear all behaved as expected. Two &lt;code&gt;Runner.run&lt;/code&gt; turns with a stand-in model stored both user and assistant items, and the second turn's model input included the first turn's history. &lt;code&gt;repro:{s1}:messages&lt;/code&gt; and &lt;code&gt;repro:{s1}:counter&lt;/code&gt; both hash to slot 15224. I didn't make a real LLM call or test with concurrent writers.&lt;/p&gt;

&lt;p&gt;Trade-offs: &lt;code&gt;RPUSH&lt;/code&gt; is atomic for a single call, but this skips the SDK's WATCH-based protections and its &lt;code&gt;session_settings&lt;/code&gt; handling. Concurrent writers to the &lt;em&gt;same&lt;/em&gt; session interleave in arrival order. The SDK docstring says third-party libraries should implement the &lt;code&gt;Session&lt;/code&gt; protocol rather than subclass &lt;code&gt;SessionABC&lt;/code&gt;. Subclassing is fine in your own app code. &lt;code&gt;SessionABC&lt;/code&gt; declares &lt;code&gt;session_settings&lt;/code&gt; with a default of &lt;code&gt;None&lt;/code&gt;, so this class doesn't need to set it.&lt;/p&gt;

&lt;h2&gt;
  
  
  A note on what a session is (and isn't)
&lt;/h2&gt;

&lt;p&gt;A session replays one conversation's history. It doesn't give a user memory across different sessions. That's a separate layer, whether you use the SDK's other memory features or a hosted store. MemorySync has an &lt;a href="https://docs.memorysync.io/guides/openai-agents?utm_source=devto&amp;amp;utm_medium=organic_page&amp;amp;utm_campaign=v5_oct25&amp;amp;utm_content=pseo_openai-agents-redissession-redis-cluster-error" rel="noopener noreferrer"&gt;OpenAI Agents SDK guide&lt;/a&gt; for that cross-session layer. It doesn't replace your Redis session store.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Will upgrading fix it?&lt;/strong&gt; As of 0.24.0, no. You get a clear &lt;code&gt;TypeError&lt;/code&gt; instead of the &lt;code&gt;AttributeError&lt;/code&gt;. Watch #5322 for real cluster support. The reporter proposed hash-tagged keys plus a single-node WATCH/MULTI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why do &lt;code&gt;get_items&lt;/code&gt; and &lt;code&gt;pop_item&lt;/code&gt; still work?&lt;/strong&gt; According to the issue, only the transactional write paths use the hooked pipeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I just hash-tag the prefix I pass to &lt;code&gt;RedisSession&lt;/code&gt;?&lt;/strong&gt; The key layout is &lt;code&gt;{prefix}:{id}...&lt;/code&gt;. A tag in the prefix would put all sessions in one slot, which concentrates load on one node. It also wouldn't fix the missing pipeline attributes, because 0.24.0 rejects any &lt;code&gt;RedisCluster&lt;/code&gt; client on write.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Managed Redis only offers cluster mode. Is option C safe for production?&lt;/strong&gt; It's a small, readable store, but test it under your real concurrency on a staging cluster first.&lt;/p&gt;

&lt;p&gt;Curious what cross-session memory looks like next to session history? &lt;strong&gt;&lt;a href="https://memorysync.io/try?utm_source=devto&amp;amp;utm_medium=organic_page&amp;amp;utm_campaign=v5_oct25&amp;amp;utm_content=pseo_openai-agents-redissession-redis-cluster-error" rel="noopener noreferrer"&gt;Try MemorySync in the browser&lt;/a&gt;&lt;/strong&gt;. No signup is needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Issue #5322: &lt;a href="https://github.com/openai/openai-agents-python/issues/5322" rel="noopener noreferrer"&gt;https://github.com/openai/openai-agents-python/issues/5322&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;OpenAI Agents SDK, Sessions: &lt;a href="https://openai.github.io/openai-agents-python/sessions/" rel="noopener noreferrer"&gt;https://openai.github.io/openai-agents-python/sessions/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Redis Cluster spec, hash tags: &lt;a href="https://redis.io/docs/latest/operate/oss_and_stack/reference/cluster-spec/" rel="noopener noreferrer"&gt;https://redis.io/docs/latest/operate/oss_and_stack/reference/cluster-spec/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;MemorySync OpenAI Agents SDK guide: &lt;a href="https://docs.memorysync.io/guides/openai-agents" rel="noopener noreferrer"&gt;https://docs.memorysync.io/guides/openai-agents&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>redis</category>
      <category>ai</category>
      <category>openai</category>
    </item>
    <item>
      <title>Add long-term memory to a Microsoft Agent Framework agent (one context provider, per-user recall across sessions)</title>
      <dc:creator>Mohammed Rafay</dc:creator>
      <pubDate>Sun, 25 Oct 2026 16:37:51 +0000</pubDate>
      <link>https://dev.to/memorysync_rafay/add-long-term-memory-to-a-microsoft-agent-framework-agent-one-context-provider-per-user-recall-40h3</link>
      <guid>https://dev.to/memorysync_rafay/add-long-term-memory-to-a-microsoft-agent-framework-agent-one-context-provider-per-user-recall-40h3</guid>
      <description>&lt;p&gt;&lt;em&gt;Disclosure: I'm Mohammed, founder of MemorySync. I drafted this article with AI assistance and reviewed it for accuracy against the official MemorySync and Microsoft Agent Framework docs. Install, imports and object construction were run; calls needing an API key were checked against the docs and run only against a local mock, not the live services.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Microsoft Agent Framework handles memory through &lt;strong&gt;context providers&lt;/strong&gt;: objects you pass in &lt;code&gt;context_providers=[...]&lt;/code&gt; that add context before the model runs and process the result afterwards (&lt;a href="https://learn.microsoft.com/en-us/agent-framework/concepts/agents/conversations/context-providers" rel="noopener noreferrer"&gt;Agent Framework context providers docs&lt;/a&gt;). The built-in &lt;code&gt;InMemoryHistoryProvider&lt;/code&gt; gives you local conversational memory for a session. Provider state lives in the session's state. That's the right home for the current thread. It isn't the right home for "this user is vegetarian and prefers short answers" when the same person opens a new session next week.&lt;/p&gt;

&lt;p&gt;This post adds per-user long-term memory with MemorySync's Agent Framework integration, &lt;code&gt;agent-framework-memorysync&lt;/code&gt;. MemorySync is a hosted memory layer, and I'm building it. Agent Framework behaviour comes from Microsoft's docs and the framework's source; everything about MemorySync comes from its &lt;a href="https://docs.memorysync.io/guides/agent-framework?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=pivot_oct19&amp;amp;utm_content=microsoft-agent-framework-long-term-memory" rel="noopener noreferrer"&gt;Microsoft Agent Framework Memory guide&lt;/a&gt;. I installed the packages and ran the snippets with a stub chat client and a mocked MemorySync endpoint (agent-framework-core 1.23.0, agent-framework-memorysync 1.1.0), but not against a real model or a real MemorySync key, so check it against your versions.&lt;/p&gt;

&lt;p&gt;If you're here from AutoGen: the AutoGen repository is now archived, and its README points new users to Agent Framework and links a migration guide. MemorySync has an &lt;a href="https://docs.memorysync.io/guides/autogen?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=pivot_oct19&amp;amp;utm_content=microsoft-agent-framework-long-term-memory" rel="noopener noreferrer"&gt;AutoGen guide&lt;/a&gt; too if you can't move yet, but I'd build new agents on Agent Framework.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you'll set up
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;One &lt;code&gt;MemorySyncContextProvider&lt;/code&gt; that recalls relevant facts about the user before every run and sends the user's messages for fact extraction after it.&lt;/li&gt;
&lt;li&gt;Per-run user identity, so one agent can serve many users.&lt;/li&gt;
&lt;li&gt;Optional: memory tools the model can call itself.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How Agent Framework thinks about memory
&lt;/h2&gt;

&lt;p&gt;A context provider subclasses &lt;code&gt;ContextProvider&lt;/code&gt; and overrides two hooks (&lt;a href="https://learn.microsoft.com/en-us/agent-framework/concepts/agents/conversations/context-providers" rel="noopener noreferrer"&gt;docs&lt;/a&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;before_run&lt;/code&gt; adds instructions, messages or tools to the invocation, e.g. &lt;code&gt;context.extend_instructions(self.source_id, "...")&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;after_run&lt;/code&gt; sees the input messages and the response, so it can store or extract things.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Microsoft's own memory sample stores the user's name in provider state, so it carries across turns &lt;em&gt;of that session&lt;/em&gt;. You can stack several providers on one agent (history, memory, RAG). The MemorySync provider is one of these, so you're not replacing anything the framework does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;agent-framework-memorysync
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Per the guide it needs Python 3.10+ and &lt;code&gt;agent-framework-core&lt;/code&gt; 1.8 or newer. PyPI currently lists &lt;code&gt;agent-framework-memorysync&lt;/code&gt; 1.1.0 (requiring &lt;code&gt;agent-framework-core&amp;gt;=1.8,&amp;lt;2&lt;/code&gt;), and &lt;code&gt;agent-framework-core&lt;/code&gt; is at 1.23.0. You'll need a MemorySync API key from the dashboard (Settings → API Keys, inside a project). Set it as &lt;code&gt;MEMORYSYNC_API_KEY&lt;/code&gt; and keep it server-side.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Create the provider
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;agent_framework_memorysync&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MemorySyncContextProvider&lt;/span&gt;

&lt;span class="n"&gt;provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MemorySyncContextProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer-42&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;# required: the end user these memories belong to
&lt;/span&gt;    &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;support&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;# optional: tags this conversation's facts
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# api_key="ms_..." also works; otherwise it reads MEMORYSYNC_API_KEY
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;user_id&lt;/code&gt; is a stable string you choose: your internal user ID, an email or a UUID. Use the same value on every run, or recall comes back empty. One &lt;code&gt;user_id&lt;/code&gt; drives both storage and retrieval.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Attach it to the agent
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;agent_framework&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;

&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;...,&lt;/span&gt; &lt;span class="n"&gt;context_providers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Which seat should I book?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What happens, per the guide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Before every run&lt;/strong&gt;, relevant memories about this user are recalled into the &lt;strong&gt;instructions layer&lt;/strong&gt;. They aren't added as a fake user message.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;After the run&lt;/strong&gt;, the user's messages are sent to MemorySync. The server extracts the durable facts and stores only those. Filler like "ok" yields none, and the agent's replies aren't sent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tool loops capture once&lt;/strong&gt;: one send per user turn, not per model round-trip. Retries aren't extracted twice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failures don't break the run.&lt;/strong&gt; If the memory lookup fails or times out, the agent runs without memories. If the send after the run fails, it's logged, and the run's result still stands.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Extraction is asynchronous, so a fact you just stated may take a moment to become recallable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Keep the thread and the user separate
&lt;/h2&gt;

&lt;p&gt;The provider covers facts about the user. For the current conversation, keep using a session and a history provider. Here's how I'd combine them, using &lt;code&gt;InMemoryHistoryProvider&lt;/code&gt; and &lt;code&gt;create_session()&lt;/code&gt; from Microsoft's docs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;agent_framework&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;InMemoryHistoryProvider&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;agent_framework.openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAIChatClient&lt;/span&gt;

&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;OpenAIChatClient&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;instructions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You are a helpful assistant.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;context_providers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nc"&gt;InMemoryHistoryProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;memory&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;load_messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;# this thread
&lt;/span&gt;        &lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                                               &lt;span class="c1"&gt;# this user, across threads
&lt;/span&gt;    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_session&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;I&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;m vegetarian and I always pick the aisle seat.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;OpenAIChatClient&lt;/code&gt; lives in a separate package (&lt;code&gt;pip install agent-framework-openai&lt;/code&gt;, per Microsoft's OpenAI provider docs), and with no arguments it reads &lt;code&gt;OPENAI_API_KEY&lt;/code&gt; and &lt;code&gt;OPENAI_CHAT_MODEL&lt;/code&gt; (or &lt;code&gt;OPENAI_MODEL&lt;/code&gt;) from the environment. That's my own combination of the two docs, so test it. &lt;code&gt;InMemoryHistoryProvider&lt;/code&gt; is local, as its name says. Next week's new session starts with an empty history, and that's where the MemorySync provider comes in: the user's facts are still recalled.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: One agent, many users
&lt;/h2&gt;

&lt;p&gt;The guide calls out a common gap: some memory adapters fix identity when you construct them, so a multi-tenant server ends up building one provider per user. This one resolves identity fresh on every run, in this order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Per run: &lt;code&gt;await agent.run(..., options={"memorysync_user_id": user_id})&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;A resolver: &lt;code&gt;MemorySyncContextProvider(user_id_resolver=lambda session: ...)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The constructor's &lt;code&gt;user_id&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What should I order?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;memorysync_user_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My advice: take that ID from your authenticated user record, never from the request body.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5 (optional): Memory tools and read-only runs
&lt;/h2&gt;

&lt;p&gt;Two settings from the guide's configuration table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MemorySyncContextProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer-42&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;expose_search_tool&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# registers search_memory + save_memory each run
&lt;/span&gt;    &lt;span class="n"&gt;capture&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;            &lt;span class="c1"&gt;# recall only; don't send this run's user messages
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;capture=False&lt;/code&gt; is handy for eval runs that shouldn't write memory. &lt;code&gt;top_k&lt;/code&gt; (default 5) controls how many memories are considered per run, and &lt;code&gt;min_prompt_chars&lt;/code&gt; skips recall for trivial prompts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coming from AutoGen: what changes
&lt;/h2&gt;

&lt;p&gt;AutoGen's AgentChat had a &lt;code&gt;Memory&lt;/code&gt; protocol with &lt;code&gt;query&lt;/code&gt;, &lt;code&gt;update_context&lt;/code&gt;, &lt;code&gt;add&lt;/code&gt;, &lt;code&gt;clear&lt;/code&gt; and &lt;code&gt;close&lt;/code&gt;, and you passed memories to &lt;code&gt;AssistantAgent(memory=[...])&lt;/code&gt; (&lt;a href="https://microsoft.github.io/autogen/stable/user-guide/agentchat-user-guide/memory.html" rel="noopener noreferrer"&gt;AutoGen Memory and RAG docs&lt;/a&gt;). &lt;code&gt;update_context&lt;/code&gt; added retrieved entries to the agent's model context before a step. Nothing was saved unless your code called &lt;code&gt;add&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In Agent Framework, the same job is split across a provider's two hooks: &lt;code&gt;before_run&lt;/code&gt; for recall and &lt;code&gt;after_run&lt;/code&gt; for capture. In MemorySync's two adapters, that's the practical difference:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AutoGen adapter:&lt;/strong&gt; recall is automatic, but capture is your job. After each exchange you call &lt;code&gt;await memory.add_turn_pair(user_text, reply)&lt;/code&gt;, and only the user text is sent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent Framework adapter:&lt;/strong&gt; both are automatic. There's no per-turn code, and &lt;code&gt;capture=False&lt;/code&gt; turns the write side off.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So when you port an AutoGen agent, delete the &lt;code&gt;add_turn_pair&lt;/code&gt; call. If you keep it next to the provider, you'd be sending the same user text from two places. Keep the same &lt;code&gt;user_id&lt;/code&gt; values as before. I haven't checked whether facts stored through the AutoGen adapter come back through the new one (the two tag sessions differently), so test with one user first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;p&gt;From the guide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ModuleNotFoundError: agent_framework&lt;/code&gt;.&lt;/strong&gt; The framework itself isn't installed: &lt;code&gt;pip install agent-framework&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recall returns nothing right after storing.&lt;/strong&gt; Extraction is asynchronous; give it a moment. Also confirm store and recall use the same &lt;code&gt;user_id&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The agent ran without memories once.&lt;/strong&gt; A slow or failed lookup is skipped rather than holding up the run. The next run recalls normally.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-tenant identity.&lt;/strong&gt; Don't build one provider per user. Pass &lt;code&gt;options={"memorysync_user_id": ...}&lt;/code&gt; per run.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One more, from the guide's quota section: on a monthly plan limit, over-limit writes are accepted without storing and reads return empty, so the agent keeps working. Evaluation keys return a &lt;code&gt;429&lt;/code&gt; instead, so you notice during testing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;MemorySync Microsoft Agent Framework Memory guide: docs.memorysync.io/guides/agent-framework (and /guides/autogen)&lt;/li&gt;
&lt;li&gt;Microsoft Learn: Agent Framework context providers (learn.microsoft.com/en-us/agent-framework/concepts/agents/conversations/context-providers)&lt;/li&gt;
&lt;li&gt;microsoft/agent-framework: &lt;code&gt;python/packages/core/agent_framework/_sessions.py&lt;/code&gt; (ContextProvider, provider state in AgentSession.state) and &lt;code&gt;python/samples/01-get-started/04_memory.py&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;microsoft/autogen README (archived; points to Agent Framework) and AutoGen AgentChat Memory and RAG docs&lt;/li&gt;
&lt;li&gt;PyPI: agent-framework-memorysync 1.1.0, agent-framework-core 1.23.0&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;If you want to see the save, reload and recall loop before wiring up an agent, the MemorySync playground runs it in your browser: &lt;a href="https://memorysync.io/try?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=pivot_oct19&amp;amp;utm_content=microsoft-agent-framework-long-term-memory" rel="noopener noreferrer"&gt;memorysync.io/try&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>agents</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>MCP memory server lost your memories? Fixing MEMORY_FILE_PATH and the npx cache</title>
      <dc:creator>Mohammed Rafay</dc:creator>
      <pubDate>Sun, 25 Oct 2026 16:37:06 +0000</pubDate>
      <link>https://dev.to/memorysync_rafay/mcp-memory-server-lost-your-memories-fixing-memoryfilepath-and-the-npx-cache-21le</link>
      <guid>https://dev.to/memorysync_rafay/mcp-memory-server-lost-your-memories-fixing-memoryfilepath-and-the-npx-cache-21le</guid>
      <description>&lt;p&gt;&lt;em&gt;Disclosure: I'm Mohammed, founder of MemorySync. I drafted this article with AI assistance and reviewed it for accuracy against the official MemorySync and MCP server-memory, Claude Code docs. Install and setup steps were run on a Linux box (server-memory 2.0.0, Node 24, Claude Code CLI, the MemorySync installer in list/dry-run mode); the Windows, VS Code and Docker configs and the hosted server sign-in weren't run.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you run the reference MCP memory server (&lt;code&gt;@modelcontextprotocol/server-memory&lt;/code&gt;) through &lt;code&gt;npx&lt;/code&gt;, you may have set &lt;code&gt;MEMORY_FILE_PATH&lt;/code&gt;, told Claude a few things, and later found the file you pointed at empty. The memories were real. They were written somewhere else: inside npm's npx cache.&lt;/p&gt;

&lt;p&gt;That's the long-running GitHub issue &lt;a href="https://github.com/modelcontextprotocol/servers/issues/692" rel="noopener noreferrer"&gt;modelcontextprotocol/servers#692&lt;/a&gt;, "Memory MCP ignores custom storage path setting". The reporter's file ended up at a path like &lt;code&gt;~/.npm/_npx/&amp;lt;hash&amp;gt;/node_modules/@modelcontextprotocol/server-memory/dist/memory.json&lt;/code&gt;. Some people in the thread reported losing data when that cache was cleared or the package was updated. The issue was closed on Oct 19, 2026, when the reference servers were released as v2.&lt;/p&gt;

&lt;p&gt;Below is how I'd find where your memory actually lives, pin it to a file you control, get the old data back, and decide whether you want a local file at all. The server behaviour comes from the &lt;a href="https://github.com/modelcontextprotocol/servers/tree/main/src/memory" rel="noopener noreferrer"&gt;server-memory README&lt;/a&gt;, its source and the issue thread. Client config comes from the &lt;a href="https://code.claude.com/docs/en/mcp" rel="noopener noreferrer"&gt;Claude Code MCP docs&lt;/a&gt;. The MemorySync part comes from the &lt;a href="https://docs.memorysync.io/mcp/overview?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=pivot_oct19&amp;amp;utm_content=mcp-memory-server-memory-file-path-npx-cache" rel="noopener noreferrer"&gt;MemorySync MCP docs&lt;/a&gt;. I'm building MemorySync, a hosted memory layer, so weigh that section with that in mind. I ran the Linux commands below against server-memory 2.0.0 on Node 24 before publishing; the Windows, VS Code and Docker variants are from the README and weren't run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it happens
&lt;/h2&gt;

&lt;p&gt;The server stores its knowledge graph (entities, relations, observations) as JSONL. In the current source the default path is &lt;code&gt;memory.jsonl&lt;/code&gt; &lt;strong&gt;in the package directory&lt;/strong&gt;, the folder the server's code was installed into (in 2.0.0 that's the package's &lt;code&gt;dist&lt;/code&gt; folder). With &lt;code&gt;npx -y @modelcontextprotocol/server-memory&lt;/code&gt;, that folder is inside the npx cache. So when the variable doesn't take effect, your memory sits in a cache directory that npm can replace at any time.&lt;/p&gt;

&lt;p&gt;There are three ways to end up there:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The variable never reaches the server.&lt;/strong&gt; In the issue, commenters suspected the client or &lt;code&gt;npx&lt;/code&gt; wasn't passing it through. When the maintainer closed it, he said v2 uses an absolute path as given "once the client passes the variable through". So the client config still matters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The path is relative.&lt;/strong&gt; In v2, a relative &lt;code&gt;MEMORY_FILE_PATH&lt;/code&gt; is joined onto the package directory. &lt;code&gt;memory.jsonl&lt;/code&gt; or &lt;code&gt;./memory.jsonl&lt;/code&gt; therefore lands back in the npx cache.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An older version.&lt;/strong&gt; Older releases used &lt;code&gt;memory.json&lt;/code&gt;. When there's no custom path, the server renames a legacy &lt;code&gt;memory.json&lt;/code&gt; in its default folder to &lt;code&gt;memory.jsonl&lt;/code&gt; on startup.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;v2 also expands a leading &lt;code&gt;~&lt;/code&gt; itself, because MCP clients pass env values from JSON and no shell does it for them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Find where your memory actually is
&lt;/h2&gt;

&lt;p&gt;Before changing anything, look for the file the server has really been writing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# macOS / Linux: find graph files inside the npx cache&lt;/span&gt;
find ~/.npm/_npx &lt;span class="nt"&gt;-path&lt;/span&gt; &lt;span class="s2"&gt;"*server-memory*"&lt;/span&gt; &lt;span class="se"&gt;\(&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;"memory.json"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;"memory.jsonl"&lt;/span&gt; &lt;span class="se"&gt;\)&lt;/span&gt; 2&amp;gt;/dev/null
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Windows the npm cache is usually under &lt;code&gt;%LocalAppData%\npm-cache\_npx&lt;/code&gt;. That's npm's default location, not something from the server docs, so check &lt;code&gt;npm config get cache&lt;/code&gt; if it isn't there.&lt;/p&gt;

&lt;p&gt;If you find a file with your entities in it, &lt;strong&gt;copy it somewhere safe now&lt;/strong&gt;, before you touch the config or the cache.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Use an absolute path and the current version
&lt;/h2&gt;

&lt;p&gt;The server requires Node.js 24 or later when you run it through &lt;code&gt;npx&lt;/code&gt; (per its README; npm lists 2.0.0 as the latest version). For Claude Desktop, the README's config looks like this, with an absolute path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"memory"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@modelcontextprotocol/server-memory"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"MEMORY_FILE_PATH"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/Users/you/mcp-data/memory.jsonl"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Windows the README launches &lt;code&gt;npx&lt;/code&gt; through &lt;code&gt;cmd /c&lt;/code&gt; (&lt;code&gt;"command": "cmd"&lt;/code&gt;, &lt;code&gt;"args": ["/c", "npx", "-y", "@modelcontextprotocol/server-memory"]&lt;/code&gt;) with the same &lt;code&gt;env&lt;/code&gt; block.&lt;/p&gt;

&lt;p&gt;My own additions: create the directory first, because the server saves atomically through a temp file next to the memory file and also puts a lock file there, so the folder must be writable. If you want to be sure you're on v2, pin it with standard npm syntax: &lt;code&gt;@modelcontextprotocol/server-memory@2.0.0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;Claude Code&lt;/strong&gt;, pass the variable with &lt;code&gt;--env&lt;/code&gt;, after the server name and before &lt;code&gt;--&lt;/code&gt; (that's the documented pattern for stdio servers):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add memory &lt;span class="nt"&gt;--env&lt;/span&gt; &lt;span class="nv"&gt;MEMORY_FILE_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/Users/you/mcp-data/memory.jsonl &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--&lt;/span&gt; npx &lt;span class="nt"&gt;-y&lt;/span&gt; @modelcontextprotocol/server-memory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For &lt;strong&gt;VS Code&lt;/strong&gt;, the README uses the same &lt;code&gt;command&lt;/code&gt;/&lt;code&gt;args&lt;/code&gt; shape under a top-level &lt;code&gt;servers&lt;/code&gt; key in &lt;code&gt;mcp.json&lt;/code&gt;. Add the same &lt;code&gt;env&lt;/code&gt; block.&lt;/p&gt;

&lt;p&gt;Restart the client fully after editing, since MCP config is read at startup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Check that it worked
&lt;/h2&gt;

&lt;p&gt;Ask the assistant to remember one throwaway fact, then check that your file changed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; /Users/you/mcp-data/memory.jsonl &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 3 /Users/you/mcp-data/memory.jsonl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the file is still missing but the fact was "remembered", the variable isn't reaching the server. Run the &lt;code&gt;find&lt;/code&gt; command from Step 1 again to see where the write went.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Bring the old memory back
&lt;/h2&gt;

&lt;p&gt;Put the file you rescued in Step 1 at your new &lt;code&gt;MEMORY_FILE_PATH&lt;/code&gt;. If it's a legacy &lt;code&gt;memory.json&lt;/code&gt;, rename it to &lt;code&gt;memory.jsonl&lt;/code&gt;. That's the same rename the server does itself during its own migration. Then restart and ask the assistant to read the graph (the &lt;code&gt;read_graph&lt;/code&gt; tool) to confirm your entities came back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Two more fixes from the docs and the thread
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Docker instead of npx.&lt;/strong&gt; The README's Docker config mounts a named volume (&lt;code&gt;-v claude-memory:/app/dist&lt;/code&gt;), so the default file lives in a Docker volume rather than the npx cache. The README also warns that an old volume can contain an &lt;code&gt;index.js&lt;/code&gt; the new container will overwrite.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run it with &lt;code&gt;node&lt;/code&gt; directly.&lt;/strong&gt; One commenter in the issue worked around it by installing the package globally and pointing the client at the installed &lt;code&gt;index.js&lt;/code&gt; with &lt;code&gt;node&lt;/code&gt;, so no &lt;code&gt;npx&lt;/code&gt; layer sits in between. That's a community workaround, not official guidance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If two clients (say Claude Desktop and Claude Code) point at the same file, each starts its own server process. v2 takes a lock file during every write so they take turns. The README notes this relies on a local filesystem, so don't share the file over a network drive.&lt;/p&gt;

&lt;h2&gt;
  
  
  When a hosted memory server is the simpler answer
&lt;/h2&gt;

&lt;p&gt;A local file is fine if one machine and a couple of clients are all you need. The trouble starts when you want the same memory on a laptop and a desktop, or in a CI job, and you end up syncing a JSONL file by hand.&lt;/p&gt;

&lt;p&gt;That's the case MemorySync covers: its memory server is hosted at &lt;code&gt;https://mcp.memorysync.io/mcp&lt;/code&gt;, so there's no local process and no file path to configure. Per the &lt;a href="https://docs.memorysync.io/mcp/install?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=pivot_oct19&amp;amp;utm_content=mcp-memory-server-memory-file-path-npx-cache" rel="noopener noreferrer"&gt;Connect a Client docs&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx &lt;span class="nt"&gt;-y&lt;/span&gt; memorysync-mcp-install &lt;span class="nt"&gt;--list&lt;/span&gt;     &lt;span class="c"&gt;# show detected clients, change nothing&lt;/span&gt;
npx &lt;span class="nt"&gt;-y&lt;/span&gt; memorysync-mcp-install &lt;span class="nt"&gt;--dry-run&lt;/span&gt;  &lt;span class="c"&gt;# print what it would write&lt;/span&gt;
npx &lt;span class="nt"&gt;-y&lt;/span&gt; memorysync-mcp-install            &lt;span class="c"&gt;# configure every detected client&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The installer backs up each config file before changing it. You sign in through the browser on first use. For headless setups the server also accepts a MemorySync API key in the &lt;code&gt;X-API-Key&lt;/code&gt; header. Keep that key in an environment variable or CI secret, never in a committed config file. Restart the client afterwards.&lt;/p&gt;

&lt;p&gt;It's a different model from the reference server, not a drop-in replacement. Instead of an entity/relation graph that the model edits directly, the assistant calls tools like &lt;code&gt;search_memory&lt;/code&gt; and &lt;code&gt;add_memory&lt;/code&gt;. &lt;code&gt;add_memory&lt;/code&gt; runs server-side extraction, so filler is discarded and &lt;code&gt;skipped&lt;/code&gt; is a normal result. The &lt;a href="https://docs.memorysync.io/mcp/tools?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=pivot_oct19&amp;amp;utm_content=mcp-memory-server-memory-file-path-npx-cache" rel="noopener noreferrer"&gt;Memory Tools page&lt;/a&gt; lists all 15 tools. I'm not covering moving an existing &lt;code&gt;memory.jsonl&lt;/code&gt; into it here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Memory "works" but your file stays empty.&lt;/strong&gt; The variable isn't reaching the server, or the path is relative. Use an absolute path and re-check the client's &lt;code&gt;env&lt;/code&gt; / &lt;code&gt;--env&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Everything vanished after an update.&lt;/strong&gt; The data was probably in the npx cache. Search it (Step 1) before anything else clears it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A write fails with a lock error.&lt;/strong&gt; v2 gives up after 60 seconds rather than overwriting. Check for a stuck process, and make sure the folder is writable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The hosted server shows no tools.&lt;/strong&gt; Restart the client. A &lt;code&gt;405&lt;/code&gt; on a GET request is expected, per the MemorySync docs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;GitHub issue modelcontextprotocol/servers#692 and its closing comment (Oct 19, 2026)&lt;/li&gt;
&lt;li&gt;server-memory README and &lt;code&gt;src/memory/index.ts&lt;/code&gt; (default path, relative-path and &lt;code&gt;~&lt;/code&gt; handling, legacy rename, lock file); npm @modelcontextprotocol/server-memory 2.0.0 (Node &amp;gt;= 24)&lt;/li&gt;
&lt;li&gt;Claude Code MCP docs: code.claude.com/docs/en/mcp (&lt;code&gt;--env&lt;/code&gt; for stdio servers)&lt;/li&gt;
&lt;li&gt;MemorySync docs: docs.memorysync.io/mcp/overview, /mcp/install, /mcp/tools&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;If you want to see a memory survive a full page reload before wiring up any client, the MemorySync playground runs the save, reload and recall loop in your browser: &lt;a href="https://memorysync.io/try?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=pivot_oct19&amp;amp;utm_content=mcp-memory-server-memory-file-path-npx-cache" rel="noopener noreferrer"&gt;memorysync.io/try&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>claude</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Add long-term memory to a Mastra agent (input/output processors, per-user recall)</title>
      <dc:creator>Mohammed Rafay</dc:creator>
      <pubDate>Sat, 24 Oct 2026 16:09:52 +0000</pubDate>
      <link>https://dev.to/memorysync_rafay/add-long-term-memory-to-a-mastra-agent-inputoutput-processors-per-user-recall-74e</link>
      <guid>https://dev.to/memorysync_rafay/add-long-term-memory-to-a-mastra-agent-inputoutput-processors-per-user-recall-74e</guid>
      <description>&lt;p&gt;&lt;em&gt;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.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Mastra agents already have memory. Give an agent a &lt;code&gt;Memory&lt;/code&gt; from &lt;code&gt;@mastra/memory&lt;/code&gt; and pass &lt;code&gt;memory: { resource, thread }&lt;/code&gt; on each call: &lt;code&gt;resource&lt;/code&gt; is a stable id for the user and &lt;code&gt;thread&lt;/code&gt; isolates one conversation (&lt;a href="https://mastra.ai/docs/memory/overview" rel="noopener noreferrer"&gt;Mastra memory docs&lt;/a&gt;). Mastra also has working memory and semantic recall, stored in whichever storage provider you configure.&lt;/p&gt;

&lt;p&gt;If 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, &lt;code&gt;memorysync-mastra&lt;/code&gt;. MemorySync is a hosted memory layer, and I'm building it. Mastra behaviour comes from its docs on &lt;a href="https://mastra.ai/docs/memory/overview" rel="noopener noreferrer"&gt;memory&lt;/a&gt; and &lt;a href="https://mastra.ai/docs/agents/processors" rel="noopener noreferrer"&gt;processors&lt;/a&gt;. Everything about MemorySync comes from its &lt;a href="https://docs.memorysync.io/guides/mastra?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=pivot_oct19&amp;amp;utm_content=mastra-agent-long-term-memory-processors" rel="noopener noreferrer"&gt;Mastra Memory guide&lt;/a&gt;. The code is adapted from those docs. I haven't run it for this post, so check it against your versions.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you'll set up
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;An input processor that injects recalled facts before the model call, and an output processor that sends the user's message for fact extraction after it.&lt;/li&gt;
&lt;li&gt;Per-call user identity, so one agent can serve many users.&lt;/li&gt;
&lt;li&gt;Optional: recall modes, five memory tools and standalone helpers.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;memorysync-mastra
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Per the guide it supports &lt;code&gt;@mastra/core&lt;/code&gt; 1.42+ (Node 20+ for the package; current &lt;code&gt;@mastra/core&lt;/code&gt; itself requires Node 22+). npm currently lists 1.1.0 with peer &lt;code&gt;@mastra/core &amp;gt;=1.42.0 &amp;lt;2.0.0&lt;/code&gt;. Set &lt;code&gt;MEMORYSYNC_API_KEY&lt;/code&gt; in the environment, or pass &lt;code&gt;apiKey&lt;/code&gt;, and keep it server-side.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Add the processors
&lt;/h2&gt;

&lt;p&gt;Mastra processors are hooks in the agent pipeline: &lt;code&gt;inputProcessors&lt;/code&gt; run before messages reach the model, &lt;code&gt;outputProcessors&lt;/code&gt; run after the model responds and before the result is returned (&lt;a href="https://mastra.ai/docs/agents/processors" rel="noopener noreferrer"&gt;processors docs&lt;/a&gt;). MemorySync uses one of each:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Agent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@mastra/core/agent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;openai&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@ai-sdk/openai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createMemorySyncProcessors&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;memorysync-mastra&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;inputProcessor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;outputProcessor&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createMemorySyncProcessors&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;customer-7&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;// scopes memory to one end user&lt;/span&gt;
  &lt;span class="na"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;thread-42&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// groups the stored facts by thread&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;assistant&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Assistant&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;You are a helpful assistant.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;your-model&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;inputProcessors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;inputProcessor&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;outputProcessors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;outputProcessor&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;I'm vegetarian and I fly aisle.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Later, same user, any thread&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Book my trip: flight plus a dinner spot.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Per the guide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Recall is injected once per &lt;code&gt;generate&lt;/code&gt;/&lt;code&gt;stream&lt;/code&gt; call (it uses &lt;code&gt;processInput&lt;/code&gt;, 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.&lt;/li&gt;
&lt;li&gt;Only the user's message is sent; MemorySync stores just the durable facts in it. Assistant replies aren't stored.&lt;/li&gt;
&lt;li&gt;If recall fails, the call proceeds without context. If persisting fails, it's reported through &lt;code&gt;onError&lt;/code&gt; (a console warning by default) and never thrown.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Prefer less wiring? &lt;code&gt;withMemorySync(config, { userId })&lt;/code&gt; wraps your agent config and merges both processors in, keeping any processors you already have (MemorySync's run after yours).&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Resolve the user per call
&lt;/h2&gt;

&lt;p&gt;A Mastra server agent serves many users, so identity is resolved per call, in this priority order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RequestContext&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@mastra/core/request-context&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;createMemorySyncProcessors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;MASTRA_RESOURCE_ID_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;MASTRA_THREAD_ID_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;memorysync-mastra&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// 1. Your own resolver wins over everything&lt;/span&gt;
&lt;span class="nf"&gt;createMemorySyncProcessors&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;resolveIdentity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;requestContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;currentSession&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;currentSession&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;threadId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// 2. Static ids (scripts, workers)&lt;/span&gt;
&lt;span class="nf"&gt;createMemorySyncProcessors&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;customer-7&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;thread-42&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// 3. Mastra's RequestContext, set by your server middleware&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RequestContext&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;MASTRA_RESOURCE_ID_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;customer-7&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;MASTRA_THREAD_ID_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;thread-42&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;requestContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// 4. Mastra's own memory ids on the call&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;I moved to Lisbon.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;customer-7&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;thread-42&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Option 4 is handy if you already use Mastra memory: the same &lt;code&gt;resource&lt;/code&gt; id becomes the MemorySync user. If no source yields a user id, recall is skipped and the write is refused (reported through &lt;code&gt;onError&lt;/code&gt;), never sent to a default scope. Take the id from your auth session, not from the request body.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Streaming
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Plan a dinner for my team.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;customer-7&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;thread-42&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;await &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textStream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The user's message is sent once, after the full stream. Per the guide, a stream that errors mid-flight (&lt;code&gt;finishReason: "error"&lt;/code&gt;) sends nothing, so the retry sends the message once.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4 (optional): Recall modes and switches
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;createMemorySyncProcessors&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;query&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;   &lt;span class="c1"&gt;// relevant to the latest message (default)&lt;/span&gt;
&lt;span class="nf"&gt;createMemorySyncProcessors&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;profile&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// stable overview of the user&lt;/span&gt;
&lt;span class="nf"&gt;createMemorySyncProcessors&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;full&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;    &lt;span class="c1"&gt;// both&lt;/span&gt;

&lt;span class="nf"&gt;createMemorySyncProcessors&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;persist&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;  &lt;span class="c1"&gt;// read-only&lt;/span&gt;
&lt;span class="nf"&gt;createMemorySyncProcessors&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;recall&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;   &lt;span class="c1"&gt;// write-only&lt;/span&gt;

&lt;span class="nf"&gt;createMemorySyncProcessors&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;What you know about this user:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;{context}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 5 (optional): Tools and helpers
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createMemorySyncTools&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;memorysync-mastra&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;assistant&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Assistant&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Use the memory tools to remember durable facts.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;your-model&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;createMemorySyncTools&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;customer-7&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// For agents you don't fully trust: search and list only&lt;/span&gt;
&lt;span class="nf"&gt;createMemorySyncTools&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;customer-7&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;readOnly&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The five tools are &lt;code&gt;add_memory&lt;/code&gt;, &lt;code&gt;search_memory&lt;/code&gt;, &lt;code&gt;list_memories&lt;/code&gt;, &lt;code&gt;update_memory&lt;/code&gt; (tags and importance only) and &lt;code&gt;delete_memory&lt;/code&gt;; they return readable error strings instead of throwing. For workflows there are &lt;code&gt;getMemoryContext&lt;/code&gt;, &lt;code&gt;searchMemories&lt;/code&gt; and &lt;code&gt;saveTurn&lt;/code&gt;. &lt;code&gt;saveTurn&lt;/code&gt; sends only the user's message and throws on failure. The guide says a turn sent by both the processors and &lt;code&gt;saveTurn&lt;/code&gt; is recognised server-side and extracted once.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nothing is remembered.&lt;/strong&gt; Check &lt;code&gt;onError&lt;/code&gt; output; a call with no resolvable user writes nothing by design.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A new thread knows nothing.&lt;/strong&gt; The user id (or &lt;code&gt;resource&lt;/code&gt;) must match across calls. The thread can differ.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Version errors.&lt;/strong&gt; The package targets &lt;code&gt;@mastra/core&lt;/code&gt; 1.x from 1.42, and current core needs Node 22+.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Common questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Does a Mastra agent remember previous conversations?&lt;/strong&gt;&lt;br&gt;
Yes, if you give it a &lt;code&gt;Memory&lt;/code&gt; from &lt;code&gt;@mastra/memory&lt;/code&gt; and pass &lt;code&gt;memory: { resource, thread }&lt;/code&gt; on each call. To recall earlier messages, call the agent with the same &lt;code&gt;resource&lt;/code&gt; and &lt;code&gt;thread&lt;/code&gt; values. &lt;code&gt;lastMessages&lt;/code&gt; controls how many recent messages are included (&lt;a href="https://mastra.ai/docs/memory/overview" rel="noopener noreferrer"&gt;Mastra memory docs&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where does Mastra store memory?&lt;/strong&gt;&lt;br&gt;
In the storage provider you configure. Memory needs a storage provider to persist message history; the quickstart uses &lt;code&gt;@mastra/libsql&lt;/code&gt; (&lt;a href="https://mastra.ai/docs/memory/overview" rel="noopener noreferrer"&gt;Mastra memory docs&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's the difference between resource and thread in Mastra memory?&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;resource&lt;/code&gt; is a stable id for the user or entity, and &lt;code&gt;thread&lt;/code&gt; 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 (&lt;a href="https://mastra.ai/docs/memory/overview" rel="noopener noreferrer"&gt;Mastra memory docs&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I use Mastra's Memory and the MemorySync processors together?&lt;/strong&gt;&lt;br&gt;
Yes. MemorySync's guide recommends running both: Mastra's &lt;code&gt;Memory&lt;/code&gt; 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 &lt;code&gt;memory: { resource, thread }&lt;/code&gt; option (&lt;a href="https://docs.memorysync.io/guides/mastra?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=pivot_oct19&amp;amp;utm_content=mastra-agent-long-term-memory-processors" rel="noopener noreferrer"&gt;MemorySync Mastra guide&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;MemorySync Mastra Memory guide: docs.memorysync.io/guides/mastra&lt;/li&gt;
&lt;li&gt;Mastra docs: mastra.ai/docs/memory/overview and mastra.ai/docs/agents/processors&lt;/li&gt;
&lt;li&gt;npm: memorysync-mastra 1.1.0&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;If you want to see the save, reload and recall loop before wiring up an agent, the MemorySync playground runs it in your browser: &lt;a href="https://memorysync.io/try?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=pivot_oct19&amp;amp;utm_content=mastra-agent-long-term-memory-processors" rel="noopener noreferrer"&gt;memorysync.io/try&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>typescript</category>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Add long-term memory to a Pydantic AI agent (one capability, per-user recall across runs)</title>
      <dc:creator>Mohammed Rafay</dc:creator>
      <pubDate>Sat, 24 Oct 2026 16:09:40 +0000</pubDate>
      <link>https://dev.to/memorysync_rafay/add-long-term-memory-to-a-pydantic-ai-agent-one-capability-per-user-recall-across-runs-27d3</link>
      <guid>https://dev.to/memorysync_rafay/add-long-term-memory-to-a-pydantic-ai-agent-one-capability-per-user-recall-across-runs-27d3</guid>
      <description>&lt;p&gt;&lt;em&gt;Disclosure: I'm Mohammed, founder of MemorySync. I drafted this article with AI assistance and reviewed it for accuracy against the official MemorySync and Pydantic AI docs. The code is adapted from the docs and I haven't run it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Pydantic AI keeps context across runs through message history: you pass &lt;code&gt;message_history=&lt;/code&gt; (or the previous result's conversation) into the next &lt;code&gt;Agent.run&lt;/code&gt;, and &lt;code&gt;result.all_messages()&lt;/code&gt; / &lt;code&gt;new_messages()&lt;/code&gt; give you the messages back (&lt;a href="https://ai.pydantic.dev/message-history/" rel="noopener noreferrer"&gt;Pydantic AI message history docs&lt;/a&gt;). Storing and reloading that list is up to you, and it only covers one conversation. Start a new thread and the agent knows nothing about the user it talked to yesterday.&lt;/p&gt;

&lt;p&gt;This post adds per-user long-term memory with MemorySync's Pydantic AI integration, &lt;code&gt;pydantic-ai-memorysync&lt;/code&gt;. MemorySync is a hosted memory layer, and I'm building it. Pydantic AI behaviour comes from its official docs on &lt;a href="https://ai.pydantic.dev/message-history/" rel="noopener noreferrer"&gt;message history&lt;/a&gt; and &lt;a href="https://ai.pydantic.dev/capabilities/" rel="noopener noreferrer"&gt;capabilities&lt;/a&gt;. Everything about MemorySync comes from its &lt;a href="https://docs.memorysync.io/guides/pydantic-ai?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=pivot_oct19&amp;amp;utm_content=pydantic-ai-agent-memory-across-runs" rel="noopener noreferrer"&gt;Pydantic AI Memory guide&lt;/a&gt;. The code is adapted from those docs. I haven't run it for this post, so check it against your versions.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you'll set up
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;One &lt;code&gt;MemorySyncCapability&lt;/code&gt; in the &lt;code&gt;Agent&lt;/code&gt; constructor that recalls relevant facts before every run and sends the user's prompt for fact extraction after a successful run.&lt;/li&gt;
&lt;li&gt;User identity taken from your &lt;code&gt;deps&lt;/code&gt;, so one agent can serve many users.&lt;/li&gt;
&lt;li&gt;Optional: five memory tools, a typed search tool and async helpers.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;pydantic-ai-memorysync
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Per the guide it needs &lt;code&gt;pydantic-ai&lt;/code&gt; (or &lt;code&gt;pydantic-ai-slim&lt;/code&gt;) 2.0+ and Python 3.10+. PyPI currently lists 1.1.0 with &lt;code&gt;pydantic-ai-slim&amp;gt;=2,&amp;lt;3&lt;/code&gt;. Set &lt;code&gt;MEMORYSYNC_API_KEY&lt;/code&gt; in the environment, or pass &lt;code&gt;api_key&lt;/code&gt;, and keep it server-side. This is Python only; the guide points TypeScript users to the Vercel AI SDK or Mastra integrations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Add the capability
&lt;/h2&gt;

&lt;p&gt;In Pydantic AI, a capability is a reusable unit of agent behaviour that you pass to the &lt;code&gt;Agent&lt;/code&gt; constructor instead of wiring instructions, tools and hooks separately (&lt;a href="https://ai.pydantic.dev/capabilities/" rel="noopener noreferrer"&gt;capabilities docs&lt;/a&gt;). MemorySync ships memory as one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic_ai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic_ai_memorysync&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MemorySyncCapability&lt;/span&gt;

&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;openai:your-model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;instructions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You are a helpful assistant.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;capabilities&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;MemorySyncCapability&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer-7&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# First run
&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;I&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;m vegetarian and I fly aisle.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# A later run, new conversation, same user
&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What should I cook tonight?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What happens, per the guide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Recall goes through the instructions channel&lt;/strong&gt;, not into the message list. The model sees a "Relevant memories about this user" block after your instructions, but it is never a conversation part. So if you replay &lt;code&gt;result.all_messages()&lt;/code&gt; into the next run, old memory blocks don't pile up in the transcript.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Capture happens in &lt;code&gt;after_run&lt;/code&gt;&lt;/strong&gt;, which Pydantic AI fires only when a run succeeds. A failed run leaves no half-remembered turn.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Only the user's prompt is sent.&lt;/strong&gt; MemorySync extracts the durable facts in it and stores those; filler like "ok" or "thanks" yields none. The model's reply, tool calls, retry prompts and thinking parts aren't sent. Streaming runs send the prompt once the stream completes.&lt;/li&gt;
&lt;li&gt;Prompts are grouped by the run's &lt;code&gt;conversation_id&lt;/code&gt; (sent as &lt;code&gt;session_id&lt;/code&gt;). Pass &lt;code&gt;session_id="..."&lt;/code&gt; to pin it yourself. &lt;code&gt;persist=False&lt;/code&gt; gives read-only memory.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Failure handling: if recall fails, the agent runs with its base instructions; if the send fails, the run result is untouched. Both report through &lt;code&gt;on_error&lt;/code&gt; (a logging warning by default).&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Take the user from your deps
&lt;/h2&gt;

&lt;p&gt;One agent usually serves many users, so don't hard-code &lt;code&gt;user_id&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic_ai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic_ai_memorysync&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MemorySyncCapability&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyDeps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;

&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;openai:your-model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;deps_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;MyDeps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;capabilities&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;MemorySyncCapability&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;  &lt;span class="c1"&gt;# reads ctx.deps.user_id
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hi&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;deps&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;MyDeps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer-7&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;# Or resolve it from anything on your deps:
&lt;/span&gt;&lt;span class="nc"&gt;MemorySyncCapability&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id_resolver&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The resolution order is &lt;code&gt;user_id_resolver(ctx)&lt;/code&gt;, then a static &lt;code&gt;user_id&lt;/code&gt;, then &lt;code&gt;ctx.deps.user_id&lt;/code&gt;. If none is found, the run goes ahead without memory and the miss is reported through &lt;code&gt;on_error&lt;/code&gt;; it never falls back to a shared namespace. My advice: fill &lt;code&gt;user_id&lt;/code&gt; from your authenticated user record, not from the request body.&lt;/p&gt;

&lt;p&gt;Keep using &lt;code&gt;message_history&lt;/code&gt; for the current conversation. The capability adds what Pydantic AI doesn't do on its own: facts about the user that carry over to new threads.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 (optional): Memory tools
&lt;/h2&gt;

&lt;p&gt;If you want the model to decide what to save:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic_ai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic_ai_memorysync&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;create_memorysync_tools&lt;/span&gt;

&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;openai:your-model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;instructions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Use the memory tools to remember durable facts.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;create_memorysync_tools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer-7&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# For agents you don't fully trust: search and list only
&lt;/span&gt;&lt;span class="nf"&gt;create_memorysync_tools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer-7&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;read_only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tools are &lt;code&gt;add_memory&lt;/code&gt;, &lt;code&gt;search_memory&lt;/code&gt;, &lt;code&gt;list_memories&lt;/code&gt;, &lt;code&gt;update_memory&lt;/code&gt; (tags and importance only; memory text can't be changed) and &lt;code&gt;delete_memory&lt;/code&gt;. In Pydantic AI a tool exception fails the whole run, so these return short readable error strings instead of raising.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4 (optional): Typed search and helpers
&lt;/h2&gt;

&lt;p&gt;When downstream code consumes tool output structurally, use the typed search tool. It returns &lt;code&gt;list[MemoryResult]&lt;/code&gt; (Pydantic models with &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;text&lt;/code&gt;, &lt;code&gt;score&lt;/code&gt;), and on failure returns &lt;code&gt;[]&lt;/code&gt; and reports through &lt;code&gt;on_error&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic_ai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic_ai_memorysync&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;create_memory_search_tool&lt;/span&gt;

&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;openai:your-model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;create_memory_search_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer-7&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For hand-wired setups there are async helpers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic_ai_memorysync&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;get_memory_context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;save_turn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;search_memories&lt;/span&gt;

&lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;get_memory_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;what should I cook?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer-7&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# "" for a new user
&lt;/span&gt;&lt;span class="n"&gt;hits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;search_memories&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dietary preferences&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer-7&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Explicit persistence. This one raises on failure.
&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;save_turn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer-7&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;I&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;m vegetarian&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;assistant&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Noted!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;thread-42&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;save_turn&lt;/code&gt; sends only the &lt;code&gt;user&lt;/code&gt; message. API errors raise &lt;code&gt;MemorySyncAPIError&lt;/code&gt;, which carries the HTTP status and server detail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nothing is remembered.&lt;/strong&gt; Check &lt;code&gt;on_error&lt;/code&gt; output. A run with no resolvable user proceeds memoryless; so does a failed run, because capture only happens after success.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A new thread knows nothing.&lt;/strong&gt; The &lt;code&gt;user_id&lt;/code&gt; must be identical across runs. &lt;code&gt;session_id&lt;/code&gt; / &lt;code&gt;conversation_id&lt;/code&gt; can differ.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duplicate memory text in the transcript.&lt;/strong&gt; It shouldn't happen with the capability, since recall rides the instructions channel. If you see it, check you're not also pasting &lt;code&gt;get_memory_context&lt;/code&gt; output into your prompt.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Version errors.&lt;/strong&gt; The package targets &lt;code&gt;pydantic-ai&lt;/code&gt; 2.x.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Common questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Does Pydantic AI keep memory between runs?&lt;/strong&gt;&lt;br&gt;
Not on its own. Each &lt;code&gt;Agent.run&lt;/code&gt; only sees the messages you pass in. To continue a conversation, pass the earlier messages back with &lt;code&gt;message_history=&lt;/code&gt;, using &lt;code&gt;result.all_messages()&lt;/code&gt; (everything, including prior runs) or &lt;code&gt;result.new_messages()&lt;/code&gt; (only the current run) (&lt;a href="https://ai.pydantic.dev/message-history/" rel="noopener noreferrer"&gt;Pydantic AI message history docs&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I persist message_history to a database?&lt;/strong&gt;&lt;br&gt;
The docs' intended route is a &lt;code&gt;TypeAdapter&lt;/code&gt;: &lt;code&gt;ModelMessagesTypeAdapter&lt;/code&gt; serializes messages to JSON and back. For a conversation you plan to continue, they suggest storing its &lt;code&gt;Conversation&lt;/code&gt; (via &lt;code&gt;ConversationTypeAdapter&lt;/code&gt;) rather than the messages alone. Where those bytes live and when to reload them is left to your application; &lt;code&gt;conversation_id&lt;/code&gt; is the key to store them under, and the resolved value is on the result as &lt;code&gt;AgentRunResult.conversation_id&lt;/code&gt; (&lt;a href="https://ai.pydantic.dev/message-history/" rel="noopener noreferrer"&gt;message history docs&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I stop message history from growing too large?&lt;/strong&gt;&lt;br&gt;
Use the &lt;code&gt;ProcessHistory&lt;/code&gt; capability. It lets you change the history before each model request, for example to keep only recent messages or summarize old ones (&lt;a href="https://ai.pydantic.dev/message-history/" rel="noopener noreferrer"&gt;message history docs&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I make an agent remember a user across new conversations?&lt;/strong&gt;&lt;br&gt;
A stored history only covers the conversation it belongs to. For facts that should follow a user into new threads, you need a separate memory store. The setup in this post uses MemorySync's &lt;code&gt;MemorySyncCapability&lt;/code&gt;: it adds recalled facts through the instructions channel, so nothing is added to your message history, and it sends the user's prompt for fact extraction only after a run succeeds (&lt;a href="https://docs.memorysync.io/guides/pydantic-ai?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=pivot_oct19&amp;amp;utm_content=pydantic-ai-agent-memory-across-runs" rel="noopener noreferrer"&gt;MemorySync Pydantic AI guide&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;MemorySync Pydantic AI Memory guide: docs.memorysync.io/guides/pydantic-ai&lt;/li&gt;
&lt;li&gt;Pydantic AI docs: ai.pydantic.dev/message-history/ and ai.pydantic.dev/capabilities/&lt;/li&gt;
&lt;li&gt;PyPI: pydantic-ai-memorysync 1.1.0&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;If you want to see the save, reload and recall loop before wiring up an agent, the MemorySync playground runs it in your browser: &lt;a href="https://memorysync.io/try?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=pivot_oct19&amp;amp;utm_content=pydantic-ai-agent-memory-across-runs" rel="noopener noreferrer"&gt;memorysync.io/try&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>pydantic</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Add persistent memory to a LlamaIndex agent (Memory, memory blocks and a retriever)</title>
      <dc:creator>Mohammed Rafay</dc:creator>
      <pubDate>Fri, 23 Oct 2026 16:13:12 +0000</pubDate>
      <link>https://dev.to/memorysync_rafay/add-persistent-memory-to-a-llamaindex-agent-memory-memory-blocks-and-a-retriever-5h8m</link>
      <guid>https://dev.to/memorysync_rafay/add-persistent-memory-to-a-llamaindex-agent-memory-memory-blocks-and-a-retriever-5h8m</guid>
      <description>&lt;p&gt;&lt;em&gt;Disclosure: I'm Mohammed, founder of MemorySync. I drafted this article with AI assistance and reviewed it for accuracy against the official MemorySync and LlamaIndex docs. The code is adapted from the docs and I haven't run it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;LlamaIndex agents already have memory. Pass a &lt;code&gt;Memory&lt;/code&gt; object to &lt;code&gt;agent.run(..., memory=memory)&lt;/code&gt; and the agent gets a short-term chat buffer plus optional long-term "memory blocks". Two things surprise people when they try to make it remember a user across sessions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;By default the &lt;code&gt;Memory&lt;/code&gt; class uses an in-memory SQLite database, so the buffer is gone when the process restarts (&lt;a href="https://developers.llamaindex.ai/python/framework/module_guides/deploying/agents/memory/" rel="noopener noreferrer"&gt;LlamaIndex memory docs&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;Memory blocks only receive messages when the short-term buffer overflows. The defaults are &lt;code&gt;token_limit=30000&lt;/code&gt; and &lt;code&gt;chat_history_token_ratio=0.7&lt;/code&gt;, so nothing is flushed to long-term memory until the chat history passes about 21,000 tokens. Most real conversations never get that long.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This post fixes both with MemorySync's LlamaIndex integration, &lt;code&gt;llamaindex-memorysync&lt;/code&gt;. MemorySync is a hosted memory layer, and I'm building it. LlamaIndex behaviour comes from the official memory guide linked above. Everything about MemorySync comes from its &lt;a href="https://docs.memorysync.io/guides/llamaindex?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=pivot_oct19&amp;amp;utm_content=llamaindex-agent-persistent-memory" rel="noopener noreferrer"&gt;LlamaIndex Memory guide&lt;/a&gt;. The code is adapted from those docs. I haven't run it for this post, so check it against your versions.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you'll set up
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;A drop-in &lt;code&gt;Memory&lt;/code&gt; that sends each user message for fact extraction as it arrives and recalls relevant facts on later runs, for the same user, in any thread.&lt;/li&gt;
&lt;li&gt;Optional: the recall block on its own, composed with blocks you already use.&lt;/li&gt;
&lt;li&gt;Optional: memories as a LlamaIndex retriever, a query engine or an agent tool.&lt;/li&gt;
&lt;li&gt;Optional: five memory tools and three helpers.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;llamaindex-memorysync
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Per the guide it needs &lt;code&gt;llama-index-core&lt;/code&gt; 0.13+ and Python 3.10+. PyPI currently lists 1.1.0 with &lt;code&gt;llama-index-core&amp;gt;=0.13,&amp;lt;0.15&lt;/code&gt;. Set &lt;code&gt;MEMORYSYNC_API_KEY&lt;/code&gt; in the environment, or pass &lt;code&gt;api_key&lt;/code&gt;, and keep it server-side. This is Python only. The guide points TypeScript users to the Vercel AI SDK or Mastra integrations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Swap in MemorySyncMemory
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;llama_index.core.agent.workflow&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FunctionAgent&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;llama_index.llms.openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;llamaindex_memorysync&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MemorySyncMemory&lt;/span&gt;

&lt;span class="n"&gt;memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MemorySyncMemory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_defaults&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer-7&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;# required: scopes memory to one end user
&lt;/span&gt;    &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;thread-42&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# groups the stored facts by thread
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FunctionAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[...],&lt;/span&gt; &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your-model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;# First conversation
&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;I&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;m vegetarian and I fly aisle.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# A later run for the same user
&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Book my trip: flight plus a dinner spot.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;MemorySyncMemory&lt;/code&gt; is a subclass of LlamaIndex's &lt;code&gt;Memory&lt;/code&gt;, not a wrapper, so &lt;code&gt;token_limit&lt;/code&gt;, &lt;code&gt;insert_method&lt;/code&gt; and any extra blocks still work. What changes, per the guide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each user message is sent to MemorySync when it's added to memory (on &lt;code&gt;aput&lt;/code&gt;), not when the buffer overflows. Short conversations persist too.&lt;/li&gt;
&lt;li&gt;Only user messages are sent. Assistant replies, system prompts and tool traffic stay in the short-term buffer.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;recall=False&lt;/code&gt; or &lt;code&gt;persist=False&lt;/code&gt; turns either half off.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The guide also spells out failure handling. The short-term buffer write happens first and its errors propagate. The MemorySync write comes after and reports through &lt;code&gt;on_error&lt;/code&gt; instead of failing the turn. If recall fails, no memory block is rendered and the chat history still flows.&lt;/p&gt;

&lt;p&gt;Note that the short-term buffer is still LlamaIndex's own. If you also want the raw transcript to survive restarts, LlamaIndex's docs show passing &lt;code&gt;async_database_uri&lt;/code&gt; (for example a Postgres URI) or your own &lt;code&gt;chat_store&lt;/code&gt; to &lt;code&gt;Memory.from_defaults&lt;/code&gt;. I'm assuming those pass through, since the guide says every standard &lt;code&gt;Memory&lt;/code&gt; kwarg does, but I haven't tested it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Who owns the memory
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;user_id&lt;/code&gt; is the memory owner. Take it from your authenticated user record, not from the request body and not from a session id. A new &lt;code&gt;user_id&lt;/code&gt; is a new, empty memory. &lt;code&gt;session_id&lt;/code&gt; only groups facts by thread, so the same user gets the same memories in a new thread.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 (optional): Compose the block yourself
&lt;/h2&gt;

&lt;p&gt;If you already run your own &lt;code&gt;Memory&lt;/code&gt; with blocks like &lt;code&gt;FactExtractionMemoryBlock&lt;/code&gt; or &lt;code&gt;VectorMemoryBlock&lt;/code&gt;, add the recall block next to them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;llama_index.core.memory&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Memory&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;llamaindex_memorysync&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MemorySyncMemoryBlock&lt;/span&gt;

&lt;span class="n"&gt;memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Memory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_defaults&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;thread-42&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;memory_blocks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nc"&gt;MemorySyncMemoryBlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer-7&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="c1"&gt;# ...alongside any other blocks you already run
&lt;/span&gt;    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Recall renders into LlamaIndex's own memory template. Keep the flush rule in mind, though: in this setup the block only receives the user messages the buffer flushes into it. If you want every user message stored right away, use &lt;code&gt;MemorySyncMemory&lt;/code&gt; from Step 1. The guide says a message sent on both paths is recognised server-side and extracted once.&lt;/p&gt;

&lt;p&gt;Truncation is the other difference. LlamaIndex's docs say that when blocks plus chat history exceed &lt;code&gt;token_limit&lt;/code&gt;, blocks are truncated in priority order, and by default a truncated block is removed entirely. The guide says &lt;code&gt;MemorySyncMemoryBlock&lt;/code&gt; drops its lowest-value lines instead, so the top memories stay in the prompt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4 (optional): Memories as a retriever
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;llama_index.core.query_engine&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RetrieverQueryEngine&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;llama_index.core.tools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RetrieverTool&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;llamaindex_memorysync&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MemorySyncRetriever&lt;/span&gt;

&lt;span class="n"&gt;retriever&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MemorySyncRetriever&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer-7&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;similarity_top_k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;nodes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;retriever&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;retrieve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dietary preferences&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# scored nodes
&lt;/span&gt;
&lt;span class="n"&gt;engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RetrieverQueryEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_args&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;retriever&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;retriever&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What do we know about this customer&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s diet?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;tool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RetrieverTool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_defaults&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;retriever&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;retriever&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;memory_search&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Search everything known about this user.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's a standard &lt;code&gt;BaseRetriever&lt;/code&gt; (sync and async), with the memory id and user in node metadata. &lt;code&gt;similarity_top_k&lt;/code&gt; accepts 1 to 50. Unlike the memory classes, it raises on API failure instead of returning an empty list, so a RAG pipeline can tell "no memories" from "the service errored".&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5 (optional): Tools and helpers
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;llama_index.core.agent.workflow&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FunctionAgent&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;llamaindex_memorysync&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;create_memorysync_tools&lt;/span&gt;

&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FunctionAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;create_memorysync_tools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer-7&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;system_prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Use the memory tools to remember durable facts.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# For agents you don't fully trust: search and list only
&lt;/span&gt;&lt;span class="nf"&gt;create_memorysync_tools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer-7&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;read_only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tools are &lt;code&gt;add_memory&lt;/code&gt;, &lt;code&gt;search_memory&lt;/code&gt;, &lt;code&gt;list_memories&lt;/code&gt;, &lt;code&gt;update_memory&lt;/code&gt; (tags and importance only; memory text can't be changed) and &lt;code&gt;delete_memory&lt;/code&gt;. They return readable error strings instead of raising.&lt;/p&gt;

&lt;p&gt;For code outside an agent, there are sync helpers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;llamaindex_memorysync&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;get_memory_context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;save_turn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;search_memories&lt;/span&gt;

&lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_memory_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;what should I cook?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer-7&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# "" for a new user
&lt;/span&gt;&lt;span class="n"&gt;hits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;search_memories&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dietary preferences&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer-7&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Explicit persistence. This one raises on failure.
&lt;/span&gt;&lt;span class="nf"&gt;save_turn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer-7&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;I&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;m vegetarian&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="n"&gt;assistant&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Noted!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;thread-42&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;save_turn&lt;/code&gt; sends only the user message. API errors raise &lt;code&gt;MemorySyncAPIError&lt;/code&gt;, which carries the HTTP status and server detail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nothing is remembered after a short chat.&lt;/strong&gt; You're probably using a plain block inside &lt;code&gt;Memory&lt;/code&gt;, which only sees flushed messages. Switch to &lt;code&gt;MemorySyncMemory&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A new thread knows nothing.&lt;/strong&gt; Check that &lt;code&gt;user_id&lt;/code&gt; is identical in both runs. &lt;code&gt;session_id&lt;/code&gt; can differ.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The memory block disappears in long chats.&lt;/strong&gt; That's LlamaIndex's default truncation for other blocks. Check their &lt;code&gt;priority&lt;/code&gt; values; &lt;code&gt;priority=0&lt;/code&gt; blocks are always kept.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Version errors.&lt;/strong&gt; The package targets &lt;code&gt;llama-index-core&lt;/code&gt; 0.13 to 0.14.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;MemorySync LlamaIndex Memory guide: docs.memorysync.io/guides/llamaindex&lt;/li&gt;
&lt;li&gt;LlamaIndex memory guide: developers.llamaindex.ai/python/framework/module_guides/deploying/agents/memory/&lt;/li&gt;
&lt;li&gt;PyPI: llamaindex-memorysync 1.1.0&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;If you want to see the save, reload and recall loop before wiring up an agent, the MemorySync playground runs it in your browser: &lt;a href="https://memorysync.io/try?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=pivot_oct19&amp;amp;utm_content=llamaindex-agent-persistent-memory" rel="noopener noreferrer"&gt;memorysync.io/try&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>llamaindex</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Persistent memory for CrewAI 1.x: keep crew memory across runs and redeploys</title>
      <dc:creator>Mohammed Rafay</dc:creator>
      <pubDate>Fri, 23 Oct 2026 16:12:53 +0000</pubDate>
      <link>https://dev.to/memorysync_rafay/persistent-memory-for-crewai-1x-keep-crew-memory-across-runs-and-redeploys-e7i</link>
      <guid>https://dev.to/memorysync_rafay/persistent-memory-for-crewai-1x-keep-crew-memory-across-runs-and-redeploys-e7i</guid>
      <description>&lt;p&gt;&lt;em&gt;Disclosure: I'm Mohammed, founder of MemorySync. I drafted this article with AI assistance and reviewed it for accuracy against the official MemorySync and CrewAI docs. The code is adapted from the docs and I haven't run it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;CrewAI 1.x replaced its old short-term, long-term and entity memory classes with one &lt;code&gt;Memory&lt;/code&gt; class. Turn it on with &lt;code&gt;memory=True&lt;/code&gt; and the crew does the work for you: after each task it extracts discrete facts from the output and stores them, and before each task the agent recalls relevant context and injects it into the prompt (&lt;a href="https://docs.crewai.com/en/concepts/memory" rel="noopener noreferrer"&gt;CrewAI memory docs&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The part that catches people is where that memory lives. By default it's LanceDB, stored under &lt;code&gt;./.crewai/memory&lt;/code&gt; (or &lt;code&gt;$CREWAI_STORAGE_DIR/memory&lt;/code&gt;). That's fine on a laptop. It's a problem when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;your crew runs in a container whose filesystem is thrown away on every deploy,&lt;/li&gt;
&lt;li&gt;two services run the same crew and should learn from each other, or&lt;/li&gt;
&lt;li&gt;you want each of your customers to have their own memory, not one shared folder.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CrewAI's answer is a pluggable storage layer: implement the &lt;code&gt;StorageBackend&lt;/code&gt; protocol and pass it to &lt;code&gt;Memory(storage=...)&lt;/code&gt;. This tutorial uses MemorySync's backend for that, plus the two extras the package ships: agent tools and a context helper for task descriptions.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you'll set up
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;A crew whose automatic save and recall loop runs against a hosted store instead of local files.&lt;/li&gt;
&lt;li&gt;A clear choice of who owns the memory: one crew, or one end user.&lt;/li&gt;
&lt;li&gt;Optional: tools so an agent can decide to store or look something up itself.&lt;/li&gt;
&lt;li&gt;Optional: a recall block you paste into a task description.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;crewai-memorysync
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Per the &lt;a href="https://docs.memorysync.io/guides/crewai?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=pivot_oct19&amp;amp;utm_content=crewai-persistent-memory-memorysync" rel="noopener noreferrer"&gt;MemorySync CrewAI guide&lt;/a&gt;, it needs CrewAI 1.0+ (the unified Memory system) and Python 3.10+. On an older CrewAI the import fails with a message naming the fix. Set &lt;code&gt;MEMORYSYNC_API_KEY&lt;/code&gt; in your environment, or pass &lt;code&gt;api_key&lt;/code&gt; explicitly, and keep it in server-side code.&lt;/p&gt;

&lt;p&gt;One thing that doesn't change: CrewAI still runs its own LLM analysis and its own embedder. The backend receives a query embedding from CrewAI, so whatever embedder you've configured (OpenAI's &lt;code&gt;text-embedding-3-large&lt;/code&gt; by default, per CrewAI's docs) still needs its credentials.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: point the crew's memory at the backend
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;crewai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Crew&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;crewai.memory&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Memory&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;crewai_memorysync&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MemorySyncStorage&lt;/span&gt;

&lt;span class="n"&gt;crew&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Crew&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;agents&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;researcher&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;research_task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;write_task&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Memory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;MemorySyncStorage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;support-crew&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;crew&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;kickoff&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole change. You don't add search-and-paste steps to your tasks: CrewAI keeps driving saves and recalls, and the records now go to MemorySync. Per the guide, they survive redeploys and are shared by every service that uses the same &lt;code&gt;user_id&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you'd rather not pass the backend around, register it once at startup and refer to it by name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;crewai.memory&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Memory&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;crewai_memorysync&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;register_memorysync&lt;/span&gt;

&lt;span class="nf"&gt;register_memorysync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;support-crew&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# once, at startup
&lt;/span&gt;
&lt;span class="n"&gt;memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Memory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;memorysync&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;# anywhere in the process
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The guide notes that unknown storage names still go to CrewAI's built-ins, so &lt;code&gt;storage="lancedb"&lt;/code&gt; elsewhere in the same process keeps working.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: decide who owns the memory
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;user_id&lt;/code&gt; is the memory owner, and it's the most important choice in this setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One id per crew&lt;/strong&gt; (like &lt;code&gt;"support-crew"&lt;/code&gt; above) gives the crew a shared knowledge base. Everything any run learns is available to the next run, on any machine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One id per end user&lt;/strong&gt; gives each customer their own memory. A crew working for customer A doesn't see what it learned for customer B.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My rule of thumb: if the crew learns facts about &lt;em&gt;a person&lt;/em&gt;, use that person's id, taken from your own authenticated user record. If it learns facts about &lt;em&gt;the work&lt;/em&gt; (your stack, your product, your process), a crew-level id is the right shape. Don't use a session or run id here. A new id is a new, empty memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: check the loop end to end
&lt;/h2&gt;

&lt;p&gt;You normally never call recall yourself, because agents recall as they work. But after a first run it's worth checking that something was actually stored:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;matches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;recall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;what stack does the team use?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;depth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shallow&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;According to the guide, scope prefixes, category and metadata filters and scores behave like the built-in LanceDB backend, and records keep their scopes (for example &lt;code&gt;/agent/researcher&lt;/code&gt;), categories, importance and sources through a round trip. If you're debugging, &lt;code&gt;attach_memory_logging&lt;/code&gt; logs CrewAI's memory save and query events as they happen.&lt;/p&gt;

&lt;p&gt;Two behaviours worth knowing before production:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Replays don't double-store.&lt;/strong&gt; CrewAI runs saves on a background executor, so replays happen. The guide says saving an identical record again stores nothing twice, and an update replaces the previous version in place.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failures are loud.&lt;/strong&gt; CrewAI's executor silently drops &lt;code&gt;RuntimeError&lt;/code&gt;, so the package never raises one. Failures surface as typed errors instead of saves that quietly vanish.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 4 (optional): let an agent manage memory itself
&lt;/h2&gt;

&lt;p&gt;The automatic loop covers most cases. When you want an agent to explicitly decide "remember this" or "look that up", give it the five tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;crewai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;crewai_memorysync&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;create_memorysync_tools&lt;/span&gt;

&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Personal assistant&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;goal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Help using what you know about the user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;backstory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You remember the user&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s preferences.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;create_memorysync_tools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer-7&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# For agents you don't fully trust: search and list only
&lt;/span&gt;&lt;span class="n"&gt;read_only_tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create_memorysync_tools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer-7&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;read_only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tools are &lt;code&gt;add_memory&lt;/code&gt;, &lt;code&gt;search_memory&lt;/code&gt;, &lt;code&gt;list_memories&lt;/code&gt;, &lt;code&gt;update_memory&lt;/code&gt; and &lt;code&gt;delete_memory&lt;/code&gt;. They never raise: errors come back as readable strings, so a tool failure can't abort a task. Memory text can't be edited, so &lt;code&gt;update_memory&lt;/code&gt; changes tags and importance only. They're the same names and behaviour as the MemorySync LangChain tools, which helps if an agent moves between frameworks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5 (optional): put known context in a task description
&lt;/h2&gt;

&lt;p&gt;For a task that should start from what you already know about a user, there's a one-line helper:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;crewai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;crewai_memorysync&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;get_crew_context&lt;/span&gt;

&lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_crew_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;travel preferences&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer-7&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Plan the trip.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Known preferences:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;None yet.&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;expected_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;An itinerary honouring every stated preference.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;planner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It returns an empty string for a new user. That's a normal state, not an error, so give it a fallback like the &lt;code&gt;or 'None yet.'&lt;/code&gt; above (my addition to the guide's example).&lt;/p&gt;

&lt;h2&gt;
  
  
  Resetting memory without wiping everything
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;crew.reset_memories()&lt;/code&gt; can end up calling &lt;code&gt;reset()&lt;/code&gt; with no scope, which would erase everything that &lt;code&gt;user_id&lt;/code&gt; owns. The backend refuses that by default:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;storage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MemorySyncStorage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;support-crew&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;                      &lt;span class="c1"&gt;# refused: raises ValueError mentioning allow_full_reset
&lt;/span&gt;&lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/agent/researcher&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# scoped resets always work
&lt;/span&gt;
&lt;span class="c1"&gt;# Opt in only when a full wipe is really intended:
&lt;/span&gt;&lt;span class="nc"&gt;MemorySyncStorage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;support-crew&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;allow_full_reset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And if a delete removes nothing while matching records exist, it raises &lt;code&gt;MemorySyncDeleteRefusedError&lt;/code&gt; instead of pretending it worked. For a crew that handles personal data, I'd want that to be loud too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recap
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;pip install crewai-memorysync&lt;/code&gt; and set &lt;code&gt;MEMORYSYNC_API_KEY&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Memory(storage=MemorySyncStorage(user_id=...))&lt;/code&gt; on your crew, or &lt;code&gt;register_memorysync()&lt;/code&gt; once.&lt;/li&gt;
&lt;li&gt;Pick the owner: one id per crew to share, one per end user to keep memory separate.&lt;/li&gt;
&lt;li&gt;Check a first run with &lt;code&gt;memory.recall(...)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Add tools or &lt;code&gt;get_crew_context&lt;/code&gt; only where an agent or task needs them.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;MemorySync CrewAI Memory guide: docs.memorysync.io/guides/crewai&lt;/li&gt;
&lt;li&gt;CrewAI memory docs: docs.crewai.com/en/concepts/memory&lt;/li&gt;
&lt;li&gt;PyPI: crewai-memorysync 1.1.0&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;If you want to see the save, reload and recall loop before wiring up a crew, the MemorySync playground runs it in the browser with each API call shown: &lt;a href="https://memorysync.io/try?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=pivot_oct19&amp;amp;utm_content=crewai-persistent-memory-memorysync" rel="noopener noreferrer"&gt;memorysync.io/try&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>agents</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Add long-term memory to a Vercel AI SDK chatbot (middleware, Next.js route and agent tools)</title>
      <dc:creator>Mohammed Rafay</dc:creator>
      <pubDate>Thu, 22 Oct 2026 16:18:29 +0000</pubDate>
      <link>https://dev.to/memorysync_rafay/add-long-term-memory-to-a-vercel-ai-sdk-chatbot-middleware-nextjs-route-and-agent-tools-4c2o</link>
      <guid>https://dev.to/memorysync_rafay/add-long-term-memory-to-a-vercel-ai-sdk-chatbot-middleware-nextjs-route-and-agent-tools-4c2o</guid>
      <description>&lt;p&gt;&lt;em&gt;Disclosure: I'm Mohammed, founder of MemorySync. I drafted this article with AI assistance and reviewed it for accuracy against the official MemorySync and AI SDK docs. The code is adapted from the docs and I haven't run it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The AI SDK's chatbot persistence guide shows how to save a chat: &lt;code&gt;useChat&lt;/code&gt; sends the chat id and messages, and you store the messages when the stream ends. Load them by chat id and the conversation picks up where it left off. Start a new chat, though, and the model knows nothing about the user. A transcript belongs to one chat. Long-term memory is the facts about a user that should show up in every chat.&lt;/p&gt;

&lt;p&gt;This post adds that second part to an AI SDK app with MemorySync's AI SDK integration, &lt;code&gt;memorysync-ai-sdk&lt;/code&gt;. MemorySync is a hosted memory layer, and I'm building it. The AI SDK behaviour comes from the official &lt;a href="https://ai-sdk.dev/docs/ai-sdk-core/middleware" rel="noopener noreferrer"&gt;Language Model Middleware&lt;/a&gt; and &lt;a href="https://ai-sdk.dev/docs/ai-sdk-ui/chatbot-message-persistence" rel="noopener noreferrer"&gt;Chatbot Message Persistence&lt;/a&gt; docs. Everything about MemorySync comes from its &lt;a href="https://docs.memorysync.io/guides/vercel-ai-sdk?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=pivot_oct19&amp;amp;utm_content=vercel-ai-sdk-long-term-memory-middleware" rel="noopener noreferrer"&gt;Vercel AI SDK Memory guide&lt;/a&gt;. The code is adapted from those docs. I haven't run it for this post, so check it against your versions.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it fits
&lt;/h2&gt;

&lt;p&gt;The AI SDK has a middleware layer: &lt;code&gt;wrapLanguageModel({ model, middleware })&lt;/code&gt; returns a model that behaves like any other, but with its calls intercepted. The SDK's docs list RAG, guardrails, caching and logging as typical uses. Memory fits the same slot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Before generation:&lt;/strong&gt; recall memories for this user and inject them as a system context block.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;After a successful finish:&lt;/strong&gt; send the user's message for fact extraction. Only the durable facts in it are stored. Assistant replies aren't stored.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keep your transcript persistence. The two do different jobs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;memorysync-ai-sdk ai
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set &lt;code&gt;MEMORYSYNC_API_KEY&lt;/code&gt; in the environment (or pass &lt;code&gt;apiKey&lt;/code&gt;). The guide lists &lt;code&gt;ai&lt;/code&gt; 6.0+ as the requirement and Node 18+ for the package. Note that &lt;code&gt;ai&lt;/code&gt; v7 itself needs Node 22+. The npm registry currently lists &lt;code&gt;memorysync-ai-sdk&lt;/code&gt; 1.1.0 with a peer range of &lt;code&gt;ai&lt;/code&gt; &amp;gt;=6.0.0 &amp;lt;8.0.0.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Wrap the model once
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;generateText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;wrapLanguageModel&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;openai&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@ai-sdk/openai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createMemorySyncMiddleware&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;memorysync-ai-sdk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;wrapLanguageModel&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;your-model&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;createMemorySyncMiddleware&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;customer-7&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// required: scopes memory to one end user&lt;/span&gt;
    &lt;span class="na"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;thread-42&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// groups stored facts by thread&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// First conversation&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;generateText&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;I'm vegetarian and I fly aisle.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// A later call for the same user&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;generateText&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Book my trip: flight plus a dinner spot.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second call doesn't repeat the diet or the seat. If memory works, the model has seen both in the injected context block. Two behaviours from the guide are worth knowing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Injection happens once per user turn. On tool-loop steps where the last message is a tool result, the prompt passes through untouched.&lt;/li&gt;
&lt;li&gt;Your own system prompt stays first. The memory block goes after it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 2: A Next.js chat route
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/api/chat/route.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;convertToModelMessages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;streamText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;wrapLanguageModel&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;openai&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@ai-sdk/openai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createMemorySyncMiddleware&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;memorysync-ai-sdk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sessionId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;wrapLanguageModel&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;your-model&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;createMemorySyncMiddleware&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sessionId&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;streamText&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;convertToModelMessages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toUIMessageStreamResponse&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two notes on this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The MemorySync guide calls &lt;code&gt;convertToModelMessages(messages)&lt;/code&gt; without &lt;code&gt;await&lt;/code&gt;. The current AI SDK persistence docs &lt;code&gt;await&lt;/code&gt; it, so I've added that here. Check which one your &lt;code&gt;ai&lt;/code&gt; version expects.&lt;/li&gt;
&lt;li&gt;The guide reads &lt;code&gt;userId&lt;/code&gt; from the request body to keep the example short. In a real app, take it from your auth session instead. Whoever controls &lt;code&gt;userId&lt;/code&gt; controls whose memory gets read.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On streaming, the guide says the user's message is sent once, after the stream's finish part. A stream that errors or is aborted never reaches its finish, so nothing is sent for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Read-only, write-only and tuning
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Recall context, never write&lt;/span&gt;
&lt;span class="nf"&gt;createMemorySyncMiddleware&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;persist&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Send user messages for extraction, inject nothing&lt;/span&gt;
&lt;span class="nf"&gt;createMemorySyncMiddleware&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;recall&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Recall depth and the shape of the injected block&lt;/span&gt;
&lt;span class="nf"&gt;createMemorySyncMiddleware&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;What you know about this user:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;{context}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read-only is handy for an internal admin view or an eval run where you don't want test prompts stored.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: If you don't want to wrap the model
&lt;/h2&gt;

&lt;p&gt;The same recall and persistence are available as helpers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;createMemorySyncOnFinish&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;getMemoryContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;saveTurn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;searchMemories&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;memorysync-ai-sdk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Prompt-ready context block ("" for a new user)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getMemoryContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;what should I cook?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;customer-7&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Scored raw results: [{ id, text, score }, ...]&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;searchMemories&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dietary preferences&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;customer-7&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Explicit persistence. This one throws on failure.&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;saveTurn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;I'm vegetarian&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;assistant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Noted!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;thread-42&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;customer-7&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Or persist from onFinish, which fires once after the full tool loop&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;streamText&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;onFinish&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;createMemorySyncOnFinish&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;customer-7&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Watch the error behaviour, because it differs. The middleware reports persistence failures through &lt;code&gt;onError&lt;/code&gt; (by default, a console warning) and never throws, and a failed recall just means the call runs without context. &lt;code&gt;saveTurn&lt;/code&gt; throws. The guide says mixing styles is safe: a turn sent by both the middleware and &lt;code&gt;saveTurn&lt;/code&gt; is recognised server-side and extracted once.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Give an agent memory tools
&lt;/h2&gt;

&lt;p&gt;If you want the model to decide when to save or look something up, add the five tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;generateText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isStepCount&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// AI SDK 6: stepCountIs&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;openai&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@ai-sdk/openai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;memorySyncTools&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;memorysync-ai-sdk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;generateText&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;your-model&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;memorySyncTools&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;customer-7&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;stopWhen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;isStepCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// AI SDK 6: stepCountIs(5)&lt;/span&gt;
  &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Remember that I prefer aisle seats.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// For agents you don't fully trust: search and list only&lt;/span&gt;
&lt;span class="nf"&gt;memorySyncTools&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;customer-7&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;readOnly&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The MemorySync guide uses &lt;code&gt;stepCountIs&lt;/code&gt;. AI SDK 7's migration guide renames it to &lt;code&gt;isStepCount&lt;/code&gt;, so I've used the new name with the old one in a comment.&lt;/p&gt;

&lt;p&gt;The tools are &lt;code&gt;addMemory&lt;/code&gt;, &lt;code&gt;searchMemory&lt;/code&gt;, &lt;code&gt;listMemories&lt;/code&gt;, &lt;code&gt;updateMemory&lt;/code&gt; (tags and importance only; memory text can't be changed) and &lt;code&gt;deleteMemory&lt;/code&gt;. They return readable error strings instead of throwing, so a memory failure doesn't abort the tool loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: See which memories were used
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;generateText&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;memories&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;providerMetadata&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;memorysync&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;memories&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// [{ id, text, score }, ...]&lt;/span&gt;

&lt;span class="c1"&gt;// Streaming: the same field on the awaited stream result&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;streamed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;streamText&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;meta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;streamed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;providerMetadata&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nx"&gt;memorysync&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is useful for a "why did it say that" panel, or for logging recalls in evals. One version note: in the current AI SDK reference, &lt;code&gt;providerMetadata&lt;/code&gt; on the &lt;code&gt;streamText&lt;/code&gt; result is marked deprecated in favour of &lt;code&gt;finalStep.providerMetadata&lt;/code&gt;, and &lt;code&gt;onFinish&lt;/code&gt; is a deprecated alias for &lt;code&gt;onEnd&lt;/code&gt;. The snippets above follow the MemorySync guide, so check both names against your &lt;code&gt;ai&lt;/code&gt; version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The second chat doesn't know anything.&lt;/strong&gt; Check that &lt;code&gt;userId&lt;/code&gt; is the same value in both requests. Then check that the first request finished. An aborted stream sends nothing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TypeScript complains on AI SDK 6.&lt;/strong&gt; Pass &lt;code&gt;specificationVersion: "v3"&lt;/code&gt; to the middleware for exact types. The guide says behaviour is the same either way.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duplicate context in multi-step agents.&lt;/strong&gt; It shouldn't happen: injection is skipped on tool-result steps. If you also call &lt;code&gt;getMemoryContext&lt;/code&gt; yourself, you're adding a second block.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;If you want to see the save, reload and recall loop before adding a package, the MemorySync playground runs it in your browser: &lt;a href="https://memorysync.io/try?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=pivot_oct19&amp;amp;utm_content=vercel-ai-sdk-long-term-memory-middleware" rel="noopener noreferrer"&gt;memorysync.io/try&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>javascript</category>
      <category>nextjs</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Persistent memory for Claude Code across sessions and compaction (hooks + an MCP memory server)</title>
      <dc:creator>Mohammed Rafay</dc:creator>
      <pubDate>Thu, 22 Oct 2026 16:17:43 +0000</pubDate>
      <link>https://dev.to/memorysync_rafay/persistent-memory-for-claude-code-across-sessions-and-compaction-hooks-an-mcp-memory-server-5e9g</link>
      <guid>https://dev.to/memorysync_rafay/persistent-memory-for-claude-code-across-sessions-and-compaction-hooks-an-mcp-memory-server-5e9g</guid>
      <description>&lt;p&gt;&lt;em&gt;Disclosure: I'm Mohammed, founder of MemorySync. I drafted this article with AI assistance and reviewed it for accuracy against the official MemorySync and Claude Code docs. The code is adapted from the docs and I haven't run it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Claude Code starts every session with a fresh context window, and when a long session fills up, compaction summarizes the conversation to free space. Claude Code's own docs say that summary "can lose important details". So the two problems (a new session and a compact) are really one question: how do I get the important stuff back at the start of a session, and again after a compact?&lt;/p&gt;

&lt;p&gt;This post walks through the layers, from the built-in ones to a hooks-based memory layer:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What CLAUDE.md and auto memory already give you.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;SessionStart&lt;/code&gt; hook that re-injects context after compaction (no extra service).&lt;/li&gt;
&lt;li&gt;The MemorySync plugin, which uses the same hooks to recall from a hosted memory layer, plus the hosted MemorySync MCP server.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The Claude Code behaviour comes from the official &lt;a href="https://code.claude.com/docs/en/memory" rel="noopener noreferrer"&gt;memory&lt;/a&gt;, &lt;a href="https://code.claude.com/docs/en/hooks" rel="noopener noreferrer"&gt;hooks reference&lt;/a&gt; and &lt;a href="https://code.claude.com/docs/en/hooks-guide" rel="noopener noreferrer"&gt;hooks guide&lt;/a&gt; pages. Everything about MemorySync comes from its &lt;a href="https://docs.memorysync.io/guides/claude-code?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=pivot_oct19&amp;amp;utm_content=claude-code-persistent-memory-sessions-compaction" rel="noopener noreferrer"&gt;Claude Code Memory guide&lt;/a&gt;. The commands and config are adapted from those docs. I haven't run them for this post, so check them against your Claude Code version.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. What's built in
&lt;/h2&gt;

&lt;p&gt;Claude Code has two ways to carry knowledge across sessions:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;CLAUDE.md&lt;/th&gt;
&lt;th&gt;Auto memory&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Who writes it&lt;/td&gt;
&lt;td&gt;You&lt;/td&gt;
&lt;td&gt;Claude, based on your corrections and preferences&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What it's for&lt;/td&gt;
&lt;td&gt;Instructions and rules: build commands, conventions, "always do X"&lt;/td&gt;
&lt;td&gt;Learnings Claude decides are worth keeping&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Loaded&lt;/td&gt;
&lt;td&gt;Every session&lt;/td&gt;
&lt;td&gt;Every session (first 200 lines or 25KB)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Start here. Put your build and test commands and project conventions in &lt;code&gt;./CLAUDE.md&lt;/code&gt; (or &lt;code&gt;./.claude/CLAUDE.md&lt;/code&gt;), keep it under about 200 lines as the docs suggest, and run &lt;code&gt;/context&lt;/code&gt; to confirm it shows up under &lt;strong&gt;Memory files&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;What these files don't give you, in my view, is a searchable store of facts that's scoped per project and follows you into other tools. That's the gap the next two layers fill.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Re-inject context after compaction with a hook
&lt;/h2&gt;

&lt;p&gt;Hooks are commands Claude Code runs at fixed points in a session. The one that matters here is &lt;code&gt;SessionStart&lt;/code&gt;, and its matcher says how the session began:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Matcher&lt;/th&gt;
&lt;th&gt;Fires on&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;startup&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A new session&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;resume&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;--resume&lt;/code&gt;, &lt;code&gt;--continue&lt;/code&gt; or &lt;code&gt;/resume&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;clear&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/clear&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;compact&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Auto or manual compaction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fork&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A session forked from an existing one&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For &lt;code&gt;SessionStart&lt;/code&gt;, Claude Code adds the plain text your command prints to stdout into Claude's context. That gives you a simple fix for compaction. This is the example from the hooks guide, in &lt;code&gt;.claude/settings.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"SessionStart"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"matcher"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"compact"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"echo 'Reminder: use Bun, not npm. Run bun test before committing. Current sprint: auth refactor.'"&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Swap the &lt;code&gt;echo&lt;/code&gt; for anything that prints useful state, such as &lt;code&gt;git log --oneline -5&lt;/code&gt;. Three limits from the reference are worth knowing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hook output (&lt;code&gt;additionalContext&lt;/code&gt; or plain stdout) is capped at 10,000 characters.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SessionStart&lt;/code&gt; runs on every session, so keep it fast.&lt;/li&gt;
&lt;li&gt;At launch, &lt;code&gt;SessionStart&lt;/code&gt; fires before the session's MCP servers are available, so &lt;code&gt;mcp_tool&lt;/code&gt; hooks are skipped then. Use a &lt;code&gt;type: "command"&lt;/code&gt; hook for anything the session needs at startup.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This works well for static reminders. It doesn't learn anything new on its own, though. Whatever you echo is whatever you wrote last.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. A memory layer on the same hooks: the MemorySync plugin
&lt;/h2&gt;

&lt;p&gt;MemorySync is a hosted memory layer, and I'm building it. Its Claude Code plugin wires the same lifecycle points to that memory. According to the guide:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Moment&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Session start, resume or clear&lt;/td&gt;
&lt;td&gt;Your profile and this project's context are recalled and injected before the first prompt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Every prompt&lt;/td&gt;
&lt;td&gt;The prompt is sent for fact extraction, and only the durable facts in it are stored. Prompts of 24 characters or more also recall memories relevant to that prompt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude finishes replying&lt;/td&gt;
&lt;td&gt;Nothing is sent. Replies aren't stored&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Context compaction&lt;/td&gt;
&lt;td&gt;Memory is re-injected after the compact&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;It also bundles the hosted MemorySync MCP server (search, add, list, update, delete and more), a &lt;code&gt;memory&lt;/code&gt; skill, and three commands: &lt;code&gt;/memorysync:status&lt;/code&gt;, &lt;code&gt;/memorysync:remember&lt;/code&gt; and &lt;code&gt;/memorysync:recall&lt;/code&gt;. Two details I like: facts are tagged with the git repository you're in, so recall at session start is about this project while preferences follow you everywhere, and the plugin never writes to CLAUDE.md or MEMORY.md. Your static rules stay yours.&lt;/p&gt;

&lt;h3&gt;
  
  
  Install
&lt;/h3&gt;

&lt;p&gt;You need Node.js 18+ on your PATH (the hook scripts use it) and an API key from app.memorysync.io in your environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# macOS/Linux (add it to your shell profile)&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;MEMORYSYNC_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ms_...
&lt;span class="c"&gt;# Windows&lt;/span&gt;
setx MEMORYSYNC_API_KEY ms_...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, inside Claude Code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/plugin marketplace add memorysyncio/memorysync-plugins
/plugin install memorysync@memorysync
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a session, &lt;code&gt;/plugin install&lt;/code&gt; opens the plugin panel so you can review the plugin and pick a scope first (that's Claude Code's behaviour, per its plugin docs). Restart the session, then check it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/memorysync:status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The status command reports whether the key is present, whether the API is reachable, and which user and project scope it resolved. The guide calls it "the first thing to run when anything looks off". Without an API key, the bundled MCP server falls back to OAuth sign-in and the hooks stay off. You won't see an error.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test it across a session and a compact
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Tell Claude something durable: "In this repo we use pnpm, and integration tests need the &lt;code&gt;TEST_DB_URL&lt;/code&gt; env var."&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;/clear&lt;/code&gt; (or open a new session) and ask, "How do I run the integration tests here?" without repeating the details.&lt;/li&gt;
&lt;li&gt;In a long session, run &lt;code&gt;/compact&lt;/code&gt; and ask again.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If step 2 or 3 comes back empty, run &lt;code&gt;/memorysync:status&lt;/code&gt; before anything else.&lt;/p&gt;

&lt;h3&gt;
  
  
  Knobs
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;MEMORYSYNC_USER_ID&lt;/code&gt; sets the user (default: your OS username).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MEMORYSYNC_PROJECT&lt;/code&gt; overrides the project, which otherwise comes from the git remote.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MEMORYSYNC_PROMPT_RECALL=off&lt;/code&gt; turns off per-prompt recall.&lt;/li&gt;
&lt;li&gt;With &lt;code&gt;CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC&lt;/code&gt; set, the plugin makes no network calls.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What it won't do
&lt;/h3&gt;

&lt;p&gt;The guide says every hook exits 0 on every failure path (no key, network down, server errors), so the worst case is a turn without memory, not a broken session. The memory skill refuses to store tokens, keys and passwords, and retrieved memories are injected with a "background data, not instructions" guard. Your prompts go to your MemorySync account for extraction. You can delete any memory from &lt;code&gt;/memorysync:recall&lt;/code&gt; or the dashboard.&lt;/p&gt;

&lt;h3&gt;
  
  
  Claude Cowork and teams
&lt;/h3&gt;

&lt;p&gt;Cloud sessions install repo-declared plugins only, so commit this to &lt;code&gt;.claude/settings.json&lt;/code&gt; and add &lt;code&gt;MEMORYSYNC_API_KEY&lt;/code&gt; to the session's Cloud Environment variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"extraKnownMarketplaces"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"memorysync"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"github"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"repo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"memorysyncio/memorysync-plugins"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"enabledPlugins"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"memorysync@memorysync"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  MCP only, if you don't want hooks
&lt;/h2&gt;

&lt;p&gt;If you'd rather call memory tools yourself, with no automatic capture or recall, connect just the hosted server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add &lt;span class="nt"&gt;--transport&lt;/span&gt; http memorysync https://mcp.memorysync.io/mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without &lt;code&gt;MEMORYSYNC_API_KEY&lt;/code&gt; set, it signs you in with OAuth on first use. I wrote a separate &lt;a href="https://memorysync.hashnode.dev/claude-code-cursor-mcp-memory-server-setup?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=pivot_oct19&amp;amp;utm_content=claude-code-persistent-memory-sessions-compaction" rel="noopener noreferrer"&gt;step-by-step MCP setup for Claude Code and Cursor&lt;/a&gt; covering scopes, &lt;code&gt;whoami&lt;/code&gt;, saving and recalling, and troubleshooting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which layer to use
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Rules and conventions: CLAUDE.md.&lt;/li&gt;
&lt;li&gt;A fixed reminder after every compact: a &lt;code&gt;SessionStart&lt;/code&gt; hook with the &lt;code&gt;compact&lt;/code&gt; matcher.&lt;/li&gt;
&lt;li&gt;Facts that should build up over time, per project and across sessions and compactions: a memory layer behind those hooks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can run all three at once. They don't overlap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;If you want to see the save, reload and recall loop before installing anything, the MemorySync playground runs it in your browser: &lt;a href="https://memorysync.io/try?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=pivot_oct19&amp;amp;utm_content=claude-code-persistent-memory-sessions-compaction" rel="noopener noreferrer"&gt;memorysync.io/try&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>mcp</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
