<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  <title>Agent memory · Avinash Gosavi</title>
  <subtitle>Posts tagged “Agent memory” by Avinash Gosavi.</subtitle>
  <id>https://www.avinashgosavi.com/feed/topic/agent-memory.xml</id>
  <link href="https://www.avinashgosavi.com/feed/topic/agent-memory.xml" rel="self" type="application/atom+xml"/>
  <link href="https://www.avinashgosavi.com/blog/#agent-memory" rel="alternate" type="text/html"/>
  <updated>2026-10-23T01:35:48+00:00</updated>
  <author><name>Avinash Gosavi</name><uri>https://www.avinashgosavi.com/</uri></author>
  <entry>
    <title type="html">Paper TL;DR: HNSW — How Vector Search Skips Most of the Haystack</title>
    <link href="https://www.avinashgosavi.com/post/paper-tldr-hnsw/" rel="alternate" type="text/html" title="Paper TL;DR: HNSW — How Vector Search Skips Most of the Haystack"/>
    <id>https://www.avinashgosavi.com/post/paper-tldr-hnsw/</id>
    <published>2026-10-20T20:45:00+00:00</published>
    <updated>2026-10-23T01:35:48+00:00</updated>
    <category term="Research"/>
    <category term="Agent memory"/>
    <summary type="html">A TL;DR of HNSW (Malkov &amp; Yashunin): a layered graph that finds approximate nearest vectors in roughly logarithmic time, behind much of today&apos;s vector search.</summary>
    <content type="html" xml:base="https://www.avinashgosavi.com/post/paper-tldr-hnsw/">&lt;p&gt;&lt;strong&gt;Paper:&lt;/strong&gt; Yu. A. Malkov &amp;amp; D. A. Yashunin, &lt;a href=&quot;https://arxiv.org/abs/1603.09320&quot;&gt;&lt;em&gt;Efficient and robust approximate nearest neighbor search using Hierarchical Navigable Small World graphs&lt;/em&gt;&lt;/a&gt; (arXiv, first posted March 2016; later published in IEEE Transactions on Pattern Analysis and Machine Intelligence, &lt;a href=&quot;https://doi.org/10.1109/TPAMI.2018.2889473&quot;&gt;DOI 10.1109/TPAMI.2018.2889473&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One-line TL;DR:&lt;/strong&gt; Stack a few neighbor graphs on top of each other — sparse ones with long jumps at the top, a dense one with every item at the bottom — and you can find the closest vectors to a query by hopping down the stack instead of checking everything.&lt;/p&gt;

&lt;h2 id=&quot;the-question&quot;&gt;The question&lt;/h2&gt;

&lt;p&gt;Every memory or &lt;a href=&quot;/post/agent-memory-glossary/#rag&quot;&gt;RAG&lt;/a&gt; system eventually asks the same thing: &lt;em&gt;which stored &lt;a href=&quot;/post/agent-memory-glossary/#embedding&quot;&gt;embeddings&lt;/a&gt; are closest to this query vector?&lt;/em&gt; Checking every vector works, but the cost grows linearly with the collection. Exact shortcuts like k-d trees stop helping once vectors have hundreds of dimensions. So practical systems use &lt;em&gt;approximate&lt;/em&gt; nearest neighbor (ANN) search: accept that you’ll occasionally miss a true neighbor, and measure how often you don’t with &lt;a href=&quot;/post/agent-memory-glossary/#recall-at-k&quot;&gt;recall&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;One family of ANN methods builds a &lt;em&gt;proximity graph&lt;/em&gt; — each item linked to its near neighbors — and searches by greedily walking toward the query. The authors’ own earlier method, Navigable Small World (NSW), did this but had two problems: search cost grew faster than logarithmically as the data grew, and on low-dimensional or tightly clustered data the greedy walk could get stuck in a “false” local minimum. HNSW is their fix.&lt;/p&gt;

&lt;h2 id=&quot;how-it-works&quot;&gt;How it works&lt;/h2&gt;

&lt;p&gt;The paper describes the idea as a &lt;a href=&quot;https://en.wikipedia.org/wiki/Skip_list&quot;&gt;skip list&lt;/a&gt; where the linked lists are replaced by proximity graphs.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Layers.&lt;/strong&gt; Every item lives in the bottom layer. When it’s inserted, it also draws a random top layer from an exponentially decaying distribution, so only a few items reach the upper layers. Upper layers are sparse and their links span long distances; the bottom layer is dense with short links.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Search.&lt;/strong&gt; Start at a fixed entry point on the top layer. On each layer, greedily move to whichever neighbor is closer to the query until none is, then drop down a layer and continue from there. On the bottom layer, keep a wider list of candidates (size &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ef&lt;/code&gt;) and return the best k. A bigger &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ef&lt;/code&gt; means higher recall and more work.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Insertion.&lt;/strong&gt; A new item is placed by running the same descent, then — on each layer from its own top layer down — linking to up to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;M&lt;/code&gt; neighbors found by a wider search (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;efConstruction&lt;/code&gt;). If a neighbor ends up with too many links, its list is pruned back to a cap.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;A smarter way to pick neighbors.&lt;/strong&gt; Rather than always linking to the closest candidates, the heuristic keeps a candidate only if it’s closer to the new item than to any neighbor already chosen. That spreads links out in different directions, which keeps separate clusters connected — exactly where plain NSW got stuck.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote class=&quot;diagram-text&quot;&gt;&lt;p&gt;&lt;strong&gt;Diagram: How an HNSW search hops down the layers&lt;/strong&gt; (&lt;a href=&quot;https://www.avinashgosavi.com/post/paper-tldr-hnsw/&quot;&gt;see it in the post&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;A sketch of three stacked neighbor graphs. The top layer has only two items joined by one long link; the middle layer has four; the bottom layer has every item, each linked to close neighbors. A search starts at the entry point on the top layer, hops to the neighbor closer to the query, and when no neighbor is closer it drops to the same item one layer down and continues. On the bottom layer it keeps a list of ef candidates around the query and returns the best k.&lt;/p&gt;&lt;p&gt;&lt;em&gt;A sketch, not real data: upper layers let the search take long jumps, the bottom layer lets it finish the job up close.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;The knobs the paper discusses are few: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;M&lt;/code&gt; (links per item; it suggests roughly 5–48, higher for high-recall or high-dimensional data), &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;efConstruction&lt;/code&gt; (build quality) and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ef&lt;/code&gt; (search effort). The graph links cost roughly 60–450 bytes per item on top of the vectors themselves.&lt;/p&gt;

&lt;h2 id=&quot;what-they-found&quot;&gt;What they found&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Roughly logarithmic search.&lt;/strong&gt; The authors argue search cost scales as O(log N) under idealized assumptions, and see logarithmic scaling empirically on low-dimensional data. Construction is roughly O(N log N) there.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Fastest in their vector benchmarks.&lt;/strong&gt; Single-threaded on the ann-benchmarks suite, HNSW clearly beat NSW, FLANN, Annoy, VP-tree and FALCONN (an LSH library) on SIFT, GloVe, DEEP and CoPhIR, and stayed ahead on random 4-dimensional data.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Not just for vectors.&lt;/strong&gt; Because it only needs a distance function, it also led on non-vector data — sparse text, topic histograms, image signatures, even DNA strings compared by edit distance. The biggest gain over NSW was roughly three orders of magnitude on one low-dimensional, clustered dataset.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Speed versus memory against compression.&lt;/strong&gt; On 200 million SIFT vectors, HNSW used more RAM than Faiss’s product-quantization index (about 64 GB versus 23.5–30 GB) but was more accurate, faster to search and much faster to build.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;why-it-matters-for-builders&quot;&gt;Why it matters for builders&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;It’s probably already in your stack.&lt;/strong&gt; The paper notes that Faiss added its own HNSW implementation, and RAG’s Wikipedia index used a FAISS HNSW index (see the &lt;a href=&quot;/post/paper-tldr-retrieval-augmented-generation/&quot;&gt;RAG TL;DR&lt;/a&gt;). &lt;a href=&quot;/post/vaultmem-part-2-multimodal-temporal-backends/&quot;&gt;VaultMem&lt;/a&gt; has an optional &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HNSWVectorIndex&lt;/code&gt; that wraps the &lt;a href=&quot;https://github.com/nmslib/hnswlib&quot;&gt;hnswlib&lt;/a&gt; library.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Small collections don’t need it.&lt;/strong&gt; The graph has real overhead, so brute force is often fine early on — VaultMem Part 2 falls back to exact cosine search below 1,000 memories and shows where the crossover happens.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Recall is a dial, not a constant.&lt;/strong&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ef&lt;/code&gt; trades speed for recall at query time, without rebuilding. When a memory system “forgets” something it stored, the index settings are one more place to look — alongside the embeddings and the &lt;a href=&quot;/post/agent-memory-glossary/#re-ranking&quot;&gt;re-ranking&lt;/a&gt; step.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;The index decides which candidates you even see.&lt;/strong&gt; Better embeddings (&lt;a href=&quot;/post/paper-tldr-sentence-bert/&quot;&gt;Sentence-BERT&lt;/a&gt;) and better prompt placement (&lt;a href=&quot;/post/paper-tldr-lost-in-the-middle/&quot;&gt;Lost in the Middle&lt;/a&gt;) only help with the neighbors the index returns.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;honest-caveats&quot;&gt;Honest caveats&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;The O(log N) story has gaps.&lt;/strong&gt; The guarantee leans on idealized graphs the heuristic doesn’t actually build, and the authors say high-dimensional behavior isn’t proven. On the 200M-vector test, query time drifted somewhat from pure logarithmic growth.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Memory hungry.&lt;/strong&gt; All those links live alongside the vectors, so it needs more RAM than compressed indexes like product quantization.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;No deletes or updates — at the time.&lt;/strong&gt; The paper lists element updates and removal as future work. That matters for memory systems that edit or &lt;a href=&quot;/post/agent-memory-glossary/#memory-unlearning&quot;&gt;unlearn&lt;/a&gt; facts; check what your library supports today.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Hard to distribute.&lt;/strong&gt; Every search starts at the top layer, which can become a hotspot, and the authors found that simply splitting data across machines scales poorly in total throughput.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Benchmarks are a snapshot.&lt;/strong&gt; Results depend on the hardware and the library versions of the time, and some baselines were older builds.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Earlier in the series: &lt;a href=&quot;/post/paper-tldr-retrieval-augmented-generation/&quot;&gt;RAG&lt;/a&gt; (the retrieve-then-generate loop this index sits inside) and &lt;a href=&quot;/post/paper-tldr-sentence-bert/&quot;&gt;Sentence-BERT&lt;/a&gt; (the embeddings it searches over).&lt;/p&gt;

&lt;p&gt;Terms like &lt;a href=&quot;/post/agent-memory-glossary/#hnsw&quot;&gt;&lt;em&gt;HNSW&lt;/em&gt;&lt;/a&gt;, &lt;a href=&quot;/post/agent-memory-glossary/#embedding&quot;&gt;&lt;em&gt;embedding&lt;/em&gt;&lt;/a&gt;, &lt;a href=&quot;/post/agent-memory-glossary/#semantic-search&quot;&gt;&lt;em&gt;semantic search&lt;/em&gt;&lt;/a&gt;, &lt;a href=&quot;/post/agent-memory-glossary/#top-k-retrieval&quot;&gt;&lt;em&gt;top-k retrieval&lt;/em&gt;&lt;/a&gt; and &lt;a href=&quot;/post/agent-memory-glossary/#recall-at-k&quot;&gt;&lt;em&gt;Recall@k&lt;/em&gt;&lt;/a&gt; are defined in the &lt;a href=&quot;/post/agent-memory-glossary/&quot;&gt;agent memory glossary&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title type="html">Paper TL;DR: RAG — The Paper That Named Retrieval-Augmented Generation</title>
    <link href="https://www.avinashgosavi.com/post/paper-tldr-retrieval-augmented-generation/" rel="alternate" type="text/html" title="Paper TL;DR: RAG — The Paper That Named Retrieval-Augmented Generation"/>
    <id>https://www.avinashgosavi.com/post/paper-tldr-retrieval-augmented-generation/</id>
    <published>2026-10-20T15:45:00+00:00</published>
    <updated>2026-10-23T01:35:48+00:00</updated>
    <category term="Research"/>
    <category term="Agent memory"/>
    <summary type="html">A TL;DR of the RAG paper (Lewis et al., 2020): a language model paired with a searchable Wikipedia index, so swapping the index updates what it knows.</summary>
    <content type="html" xml:base="https://www.avinashgosavi.com/post/paper-tldr-retrieval-augmented-generation/">&lt;p&gt;&lt;strong&gt;Paper:&lt;/strong&gt; Patrick Lewis, Ethan Perez, Aleksandra Piktus, Fabio Petroni, Vladimir Karpukhin, Naman Goyal, Heinrich Küttler, Mike Lewis, Wen-tau Yih, Tim Rocktäschel, Sebastian Riedel &amp;amp; Douwe Kiela (Facebook AI Research, University College London, New York University), &lt;a href=&quot;https://arxiv.org/abs/2005.11401v4&quot;&gt;&lt;em&gt;Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks&lt;/em&gt;&lt;/a&gt; (arXiv, 2020; revised 2021)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One-line TL;DR:&lt;/strong&gt; Instead of hoping a language model memorized the right fact, give it a search step: look up a few relevant Wikipedia passages, generate the answer from them, and train the search and the writer together.&lt;/p&gt;

&lt;h2 id=&quot;the-question&quot;&gt;The question&lt;/h2&gt;

&lt;p&gt;By 2020, large language models clearly stored a lot of facts in their weights. But the paper points out the downsides: that knowledge is hard to expand or revise, the model can’t show where an answer came from, and it can hallucinate. Earlier hybrid models that paired a language model with a retriever (REALM and ORQA) had only been tried on &lt;em&gt;extractive&lt;/em&gt; question answering — pulling answer spans out of passages rather than writing new text. This paper asks whether one general recipe can combine the two: a model that retrieves documents &lt;em&gt;and&lt;/em&gt; generates free-form answers, fine-tuned end to end for many different tasks.&lt;/p&gt;

&lt;h2 id=&quot;how-it-works&quot;&gt;How it works&lt;/h2&gt;

&lt;p&gt;The paper splits knowledge into two kinds of memory:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Parametric memory&lt;/strong&gt; — what’s baked into the model’s weights. Here that’s BART-large, a pre-trained sequence-to-sequence generator.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Non-parametric memory&lt;/strong&gt; — an external index you can search. Here that’s a December 2018 Wikipedia dump cut into 100-word chunks (about 21 million of them), each turned into an &lt;a href=&quot;/post/agent-memory-glossary/#embedding&quot;&gt;embedding&lt;/a&gt; by a BERT document encoder from DPR (Dense Passage Retrieval). The vectors live in a FAISS index using an &lt;a href=&quot;/post/paper-tldr-hnsw/&quot;&gt;HNSW&lt;/a&gt; approximation, so lookups stay fast.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At question time, a second BERT encoder turns the input into a query vector, the index returns the &lt;a href=&quot;/post/agent-memory-glossary/#top-k-retrieval&quot;&gt;top-k&lt;/a&gt; passages, and BART generates an answer conditioned on the input plus each passage. Nobody labels which passage is “right”: the retriever learns only from whether the final answer comes out well.&lt;/p&gt;

&lt;p&gt;Two variants decide how the passages get combined:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;RAG-Sequence&lt;/strong&gt; uses the same passage for the whole answer, then weighs the answers it would give for each passage.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;RAG-Token&lt;/strong&gt; can lean on a different passage for each word, so one answer can stitch together facts from several documents.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One practical shortcut: only the query encoder and the generator are trained. The document encoder — and so the whole 21-million-vector index — stays frozen, because re-embedding Wikipedia during training is expensive. The authors report they didn’t need that step for strong results.&lt;/p&gt;

&lt;blockquote class=&quot;diagram-text&quot;&gt;&lt;p&gt;&lt;strong&gt;Diagram: How RAG retrieves, then generates&lt;/strong&gt; (&lt;a href=&quot;https://www.avinashgosavi.com/post/paper-tldr-retrieval-augmented-generation/&quot;&gt;see it in the post&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;The input goes to a BERT query encoder, which turns it into a query vector. The vector searches a FAISS index of about 21 million 100-word Wikipedia chunks from December 2018, whose document encoder stays frozen. The top-k passages go to a BART generator, the model&apos;s parametric memory, which writes the answer. The only training signal is whether the final answer is right, and it updates just the query encoder and the generator. The index can be swapped for one built from a 2016 Wikipedia dump, which changes what the model knows without retraining.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Only the query encoder and the generator are trained. The index stays frozen, so swapping it for an older Wikipedia dump changes what the model knows.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;h2 id=&quot;what-they-found&quot;&gt;What they found&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Strong open-domain question answering.&lt;/strong&gt; On Natural Questions, WebQuestions and CuratedTrec, RAG beat the previous best systems in the paper’s comparison, while writing answers instead of copying spans. On TriviaQA the picture is mixed: it led on one test split but not the standard one.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;It can answer even when the passage doesn’t spell it out.&lt;/strong&gt; On Natural Questions, RAG still got about 11.8% of answers right in cases where the exact answer wasn’t in any retrieved passage — something a pure extractor can’t do.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;More factual, more specific text.&lt;/strong&gt; In a human evaluation of generated Jeopardy-style questions, raters judged RAG more factual than plain BART far more often than the other way round (42.7% vs. 7.1% of cases), and more specific too.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Small next to giant models.&lt;/strong&gt; RAG has 626M trainable parameters; the largest closed-book model it’s compared to, T5-11B, has 11 billion — yet RAG scored higher on open-domain QA.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Swap the index, update the knowledge.&lt;/strong&gt; The authors built a second index from a 2016 Wikipedia dump and asked “Who is {position}?” about 82 world leaders who changed between 2016 and 2018. With the matching index RAG got roughly 70% right; with the mismatched one, 4–12%. Changing what the model knows meant replacing the index, not retraining.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;why-it-matters-for-builders&quot;&gt;Why it matters for builders&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;It’s the template most “chat with your docs” systems still follow.&lt;/strong&gt; Embed chunks, search by meaning, paste the top results into the prompt, generate. Agent memory systems do the same thing with memories instead of Wikipedia pages — the &lt;a href=&quot;/post/agent-memory-glossary/#storing-and-finding-memories&quot;&gt;glossary’s “Storing and finding memories” section&lt;/a&gt; walks through each piece.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Updating knowledge is a data problem, not a training problem.&lt;/strong&gt; The hot-swap experiment is the cleanest argument for keeping facts outside the weights: if something changes, you change the index. The same idea is why memory systems store facts with timestamps and &lt;a href=&quot;/post/agent-memory-glossary/#versioned-facts&quot;&gt;versions&lt;/a&gt; rather than hoping the model “knows” the latest.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Provenance comes for free.&lt;/strong&gt; Because the answer is conditioned on specific passages, you can show where it came from — useful for debugging and for trust.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Retrieval quality still caps everything.&lt;/strong&gt; The retriever here is learned only through the final answer, and the paper reports it can fail badly (see caveats). Later work in this series picks up from there: &lt;a href=&quot;/post/paper-tldr-lost-in-the-middle/&quot;&gt;Lost in the Middle&lt;/a&gt; shows that even good passages help less when they land mid-prompt, and &lt;a href=&quot;/post/paper-tldr-memgpt/&quot;&gt;MemGPT&lt;/a&gt; treats the context window as something to manage rather than fill.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;honest-caveats&quot;&gt;Honest caveats&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;This is 2020-era plumbing.&lt;/strong&gt; “RAG” today usually means an instruction-tuned LLM plus an off-the-shelf retriever, with no joint training. The paper’s specific recipe — BART, DPR, a frozen Wikipedia index — is mostly of historical interest now; the &lt;em&gt;idea&lt;/em&gt; is what stuck.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Retrieval can collapse.&lt;/strong&gt; In early story-generation experiments, the retriever learned to return the same documents no matter the input, after which the generator ignored them and RAG behaved just like plain BART. The authors suggest tasks with long outputs or less need for facts may give the retriever too weak a signal.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Not state of the art everywhere.&lt;/strong&gt; On FEVER fact verification, RAG came within about 4.3% of the best pipeline (3-way) without being given gold evidence — close, but not ahead.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;The index is only as good as its source.&lt;/strong&gt; The authors note that Wikipedia will likely never be fully factual or free of bias, and that the same misuse concerns as other text generators apply.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Big infrastructure for its time.&lt;/strong&gt; The paper notes the Wikipedia index initially needed about 100 GB of CPU memory, cut to 36 GB with FAISS compression.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Earlier in the series: &lt;a href=&quot;/post/paper-tldr-sentence-bert/&quot;&gt;Sentence-BERT&lt;/a&gt; (the kind of embeddings that make the search step work), &lt;a href=&quot;/post/paper-tldr-lost-in-the-middle/&quot;&gt;Lost in the Middle&lt;/a&gt; (where retrieved passages land in the prompt) and &lt;a href=&quot;/post/paper-tldr-memgpt/&quot;&gt;MemGPT&lt;/a&gt; (an agent that pages its own memory in and out). Next in the series: &lt;a href=&quot;/post/paper-tldr-hnsw/&quot;&gt;HNSW&lt;/a&gt;, the graph index behind fast nearest-neighbor lookups like this one.&lt;/p&gt;

&lt;p&gt;Terms like &lt;a href=&quot;/post/agent-memory-glossary/#rag&quot;&gt;&lt;em&gt;retrieval-augmented generation&lt;/em&gt;&lt;/a&gt;, &lt;a href=&quot;/post/agent-memory-glossary/#embedding&quot;&gt;&lt;em&gt;embedding&lt;/em&gt;&lt;/a&gt;, &lt;a href=&quot;/post/agent-memory-glossary/#semantic-search&quot;&gt;&lt;em&gt;semantic search&lt;/em&gt;&lt;/a&gt;, &lt;a href=&quot;/post/agent-memory-glossary/#top-k-retrieval&quot;&gt;&lt;em&gt;top-k retrieval&lt;/em&gt;&lt;/a&gt; and &lt;a href=&quot;/post/agent-memory-glossary/#hnsw&quot;&gt;&lt;em&gt;HNSW&lt;/em&gt;&lt;/a&gt; are defined in the &lt;a href=&quot;/post/agent-memory-glossary/&quot;&gt;agent memory glossary&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title type="html">Paper TL;DR: Lost in the Middle — Where You Put Context Matters</title>
    <link href="https://www.avinashgosavi.com/post/paper-tldr-lost-in-the-middle/" rel="alternate" type="text/html" title="Paper TL;DR: Lost in the Middle — Where You Put Context Matters"/>
    <id>https://www.avinashgosavi.com/post/paper-tldr-lost-in-the-middle/</id>
    <published>2026-10-20T10:45:00+00:00</published>
    <updated>2026-10-23T01:35:48+00:00</updated>
    <category term="Research"/>
    <category term="Agent memory"/>
    <summary type="html">A TL;DR of Lost in the Middle (Liu et al., 2023): models use facts at the start and end of a long prompt far better than the middle, and what it means for RAG.</summary>
    <content type="html" xml:base="https://www.avinashgosavi.com/post/paper-tldr-lost-in-the-middle/">&lt;p&gt;&lt;strong&gt;Paper:&lt;/strong&gt; Nelson F. Liu, Kevin Lin, John Hewitt, Ashwin Paranjape, Michele Bevilacqua, Fabio Petroni &amp;amp; Percy Liang (Stanford, UC Berkeley, Samaya AI), &lt;a href=&quot;https://arxiv.org/abs/2307.03172v3&quot;&gt;&lt;em&gt;Lost in the Middle: How Language Models Use Long Contexts&lt;/em&gt;&lt;/a&gt; (arXiv, 2023)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One-line TL;DR:&lt;/strong&gt; Give a model a long prompt with one useful passage in it, and how well it answers depends on &lt;em&gt;where&lt;/em&gt; that passage sits: best at the very start or very end, noticeably worse in the middle — even for models built for long contexts.&lt;/p&gt;

&lt;h2 id=&quot;the-question&quot;&gt;The question&lt;/h2&gt;

&lt;p&gt;Retrieval-augmented generation and agent memory both work the same way at the last step: fetch some passages, paste them into the prompt, and hope the model reads them. Bigger context windows make it tempting to paste in more. This paper asks a simple question first: does the model actually use everything in a long prompt equally well?&lt;/p&gt;

&lt;h2 id=&quot;how-they-tested-it&quot;&gt;How they tested it&lt;/h2&gt;

&lt;p&gt;Two tasks, each designed so the authors could move the one relevant piece of information around while keeping everything else the same.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Multi-document question answering.&lt;/strong&gt; Questions from NaturalQuestions-Open, each paired with one Wikipedia passage that contains the answer plus distractor passages that look relevant but don’t. Prompts held 10, 20 or 30 passages, and the answer passage was moved from first to last.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Key-value retrieval.&lt;/strong&gt; A synthetic stress test: a JSON object of random UUID keys and values (75, 140 or 300 pairs), and the model has to return the value for one key. No language cues, just “find this and copy it.”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They ran open models (MPT-30B-Instruct, LongChat-13B) and API models (GPT-3.5-Turbo, Claude-1.3, and their longer-context versions, plus GPT-4 on a subset).&lt;/p&gt;

&lt;h2 id=&quot;what-they-found&quot;&gt;What they found&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;A U-shaped curve.&lt;/strong&gt; Accuracy was highest when the answer passage came at the very start or the very end, and dropped when it sat in the middle — by more than 20% in some settings. The authors describe this as a primacy bias plus a recency bias.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;The middle can be worse than nothing.&lt;/strong&gt; In one 20-document setting, GPT-3.5-Turbo with the answer buried mid-prompt scored lower than the same model answering with no documents at all.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;A longer window didn’t fix it.&lt;/strong&gt; When a prompt fit in both a model and its extended-context version, the two scored almost the same. A bigger window lets you paste more; it doesn’t make the model read it better.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;More retrieved documents stop helping early.&lt;/strong&gt; In an open-domain QA case study, the retriever kept finding the answer more often as it returned more passages, but the model’s accuracy flattened out well before that. Going from 20 to 50 passages bought only a point or two, at much higher cost and latency.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Asking the question twice helps for lookups.&lt;/strong&gt; Putting the query both before and after the data made key-value retrieval near-perfect, but it barely changed the multi-document QA results.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote class=&quot;diagram-text&quot;&gt;&lt;p&gt;&lt;strong&gt;Diagram: The U-shaped curve from Lost in the Middle&lt;/strong&gt; (&lt;a href=&quot;https://www.avinashgosavi.com/post/paper-tldr-lost-in-the-middle/&quot;&gt;see it in the post&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;A qualitative sketch, not the paper&apos;s data. The horizontal axis is where the one useful passage sits in a prompt of many passages, from first to last. The vertical axis is answer accuracy. The curve is high when the passage is first (primacy bias), dips lowest when it is in the middle, and rises again when it is last (recency bias).&lt;/p&gt;&lt;p&gt;&lt;em&gt;A sketch of the shape the paper reports, not its numbers: the same passage helps most at the edges of the prompt and least in the middle.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;h2 id=&quot;why-it-matters-for-builders&quot;&gt;Why it matters for builders&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Order is part of retrieval.&lt;/strong&gt; Ranking isn’t just about &lt;em&gt;which&lt;/em&gt; passages make the cut but &lt;em&gt;where&lt;/em&gt; they land. Putting the best matches first (and not burying them under weaker ones) is cheap and directly addresses this. A &lt;a href=&quot;/post/agent-memory-glossary/#re-ranking&quot;&gt;re-ranking&lt;/a&gt; pass earns its keep here.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Fewer, better passages beat more passages.&lt;/strong&gt; The case study suggests a tighter &lt;a href=&quot;/post/agent-memory-glossary/#top-k-retrieval&quot;&gt;top-k&lt;/a&gt; can be nearly as accurate and a lot cheaper. It also connects to the idea of &lt;a href=&quot;/post/agent-memory-glossary/#sufficient-evidence&quot;&gt;sufficient evidence&lt;/a&gt;: the goal is enough context, not all of it.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Test position, not just recall.&lt;/strong&gt; A memory system can retrieve the right fact and still fail if the fact lands mid-prompt. Benchmarks like &lt;a href=&quot;/post/paper-tldr-locomo-long-term-memory-benchmark/&quot;&gt;LoCoMo&lt;/a&gt; measure the end answer; this paper’s trick of moving the gold passage around is an easy extra check to run on your own pipeline.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Keep the important stuff at the edges.&lt;/strong&gt; Designs like &lt;a href=&quot;/post/paper-tldr-memgpt/&quot;&gt;MemGPT&lt;/a&gt; keep a small, always-visible working context near the top of the prompt. This paper is one reason that kind of layout makes sense.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;honest-caveats&quot;&gt;Honest caveats&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;These are 2023 models.&lt;/strong&gt; The results cover GPT-3.5, Claude-1.3, MPT and LongChat, with only partial GPT-4 runs because of cost. Newer models may show a weaker (or different) position effect, so it’s worth measuring on whatever you actually use.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;The “why” is still open.&lt;/strong&gt; The authors test a few explanations — architecture, instruction tuning, model size — and call those analyses preliminary. Encoder-decoder models were fairly robust within their training length but showed the U-shape on longer inputs; base models without instruction tuning showed it too.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;One answer per prompt.&lt;/strong&gt; Each test has a single relevant passage. Real questions often need several facts combined, which this setup doesn’t measure.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Greedy decoding only,&lt;/strong&gt; and the key-value task is deliberately artificial, so it shows a capability gap rather than typical real-world behavior.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Earlier in the series: &lt;a href=&quot;/post/paper-tldr-memgpt/&quot;&gt;MemGPT&lt;/a&gt; (paging what goes into the context window), &lt;a href=&quot;/post/paper-tldr-generative-agents/&quot;&gt;Generative Agents&lt;/a&gt; (ranking memories by recency, importance and relevance) and &lt;a href=&quot;/post/paper-tldr-sentence-bert/&quot;&gt;Sentence-BERT&lt;/a&gt; (the embeddings behind most of the retrieval step). Next in the series: &lt;a href=&quot;/post/paper-tldr-retrieval-augmented-generation/&quot;&gt;RAG&lt;/a&gt;, the 2020 paper that named the retrieve-then-generate setup this paper stress-tests.&lt;/p&gt;

&lt;p&gt;Terms like &lt;a href=&quot;/post/agent-memory-glossary/#context-window&quot;&gt;&lt;em&gt;context window&lt;/em&gt;&lt;/a&gt;, &lt;a href=&quot;/post/agent-memory-glossary/#position-bias&quot;&gt;&lt;em&gt;position bias&lt;/em&gt;&lt;/a&gt;, &lt;a href=&quot;/post/agent-memory-glossary/#re-ranking&quot;&gt;&lt;em&gt;re-ranking&lt;/em&gt;&lt;/a&gt; and &lt;a href=&quot;/post/agent-memory-glossary/#top-k-retrieval&quot;&gt;&lt;em&gt;top-k retrieval&lt;/em&gt;&lt;/a&gt; are defined in the &lt;a href=&quot;/post/agent-memory-glossary/&quot;&gt;agent memory glossary&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title type="html">Paper TL;DR: Generative Agents — Memory Streams, Reflection and Plans</title>
    <link href="https://www.avinashgosavi.com/post/paper-tldr-generative-agents/" rel="alternate" type="text/html" title="Paper TL;DR: Generative Agents — Memory Streams, Reflection and Plans"/>
    <id>https://www.avinashgosavi.com/post/paper-tldr-generative-agents/</id>
    <published>2026-10-19T21:40:00+00:00</published>
    <updated>2026-10-23T01:35:48+00:00</updated>
    <category term="Research"/>
    <category term="Agent memory"/>
    <summary type="html">A TL;DR of Generative Agents (Park et al., 2023): how 25 LLM characters in a sandbox town remember, reflect and plan, and which parts builders still copy.</summary>
    <content type="html" xml:base="https://www.avinashgosavi.com/post/paper-tldr-generative-agents/">&lt;p&gt;&lt;strong&gt;Paper:&lt;/strong&gt; Joon Sung Park, Joseph C. O’Brien, Carrie J. Cai, Meredith Ringel Morris, Percy Liang &amp;amp; Michael S. Bernstein (Stanford, Google Research, Google DeepMind), &lt;a href=&quot;https://arxiv.org/abs/2304.03442&quot;&gt;&lt;em&gt;Generative Agents: Interactive Simulacra of Human Behavior&lt;/em&gt;&lt;/a&gt; (UIST 2023)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One-line TL;DR:&lt;/strong&gt; Give an LLM a running log of everything it observes, a way to score which memories matter right now, and a habit of periodically writing down higher-level conclusions — and simulated characters start to behave believably over days, not just single replies.&lt;/p&gt;

&lt;h2 id=&quot;the-setup&quot;&gt;The setup&lt;/h2&gt;

&lt;p&gt;The authors built &lt;strong&gt;Smallville&lt;/strong&gt;, a small sandbox town populated by 25 characters. Each one is driven by an LLM (gpt-3.5-turbo in the paper) and starts from a single paragraph describing who they are: job, relationships, habits. From there the characters wake up, go to work, chat, form opinions of each other and make plans — all generated, step by step, by the model.&lt;/p&gt;

&lt;p&gt;The interesting part isn’t the town. It’s the memory design that keeps each character consistent over time, which the paper splits into three pieces: a &lt;strong&gt;memory stream&lt;/strong&gt;, &lt;strong&gt;reflection&lt;/strong&gt; and &lt;strong&gt;planning&lt;/strong&gt;.&lt;/p&gt;

&lt;h2 id=&quot;1-the-memory-stream&quot;&gt;1. The memory stream&lt;/h2&gt;

&lt;p&gt;Every character keeps a &lt;strong&gt;memory stream&lt;/strong&gt;: a plain list of memories, each written in natural language with a creation time and a “last accessed” time. The basic memories are &lt;strong&gt;observations&lt;/strong&gt; — things the character perceived, such as (to give an illustrative example) “a neighbor is watering the garden.”&lt;/p&gt;

&lt;p&gt;The stream grows far too large to fit into a prompt, so each time a character needs to act, it retrieves only the most useful memories. Each memory gets three scores:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Recency:&lt;/strong&gt; recently used memories score higher, fading exponentially with each in-game hour (the paper uses a decay factor of 0.995).&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Importance:&lt;/strong&gt; when a memory is created, the model rates it from 1 (mundane, like cleaning a room) to 10 (poignant, like asking someone out).&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Relevance:&lt;/strong&gt; cosine similarity between the memory’s embedding and the current situation’s embedding.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each score is scaled to 0–1 and the three are added up (the paper weights them equally). The top-scoring memories that fit into the context window go into the prompt.&lt;/p&gt;

&lt;h2 id=&quot;2-reflection&quot;&gt;2. Reflection&lt;/h2&gt;

&lt;p&gt;Raw observations are too low-level to generalize from. Fifty separate observations of a neighbor reading in the library don’t, by themselves, tell a character “she cares a lot about her research.”&lt;/p&gt;

&lt;p&gt;So characters &lt;strong&gt;reflect&lt;/strong&gt;. When the summed importance of recent memories passes a threshold (150 in the paper — roughly two or three times per simulated day), the model looks at the 100 most recent memories, asks itself three high-level questions about them, retrieves memories relevant to each question, and writes down insights that cite the memories they came from.&lt;/p&gt;

&lt;p&gt;Those insights go back into the same memory stream, where they can be retrieved — and reflected on again. The result is a &lt;strong&gt;reflection tree&lt;/strong&gt;: raw observations at the leaves, more abstract conclusions higher up.&lt;/p&gt;

&lt;blockquote class=&quot;diagram-text&quot;&gt;&lt;p&gt;&lt;strong&gt;Diagram: How a generative agent remembers, retrieves and reflects&lt;/strong&gt; (&lt;a href=&quot;https://www.avinashgosavi.com/post/paper-tldr-generative-agents/&quot;&gt;see it in the post&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;Each character keeps a memory stream of natural-language entries with a creation time and a last-accessed time: observations, plans and reflections. When the character needs to act, every memory is scored on recency (decaying each game hour), importance (rated 1 to 10 by the model) and relevance (cosine similarity of embeddings); the scores are scaled to 0 to 1 and added. The top memories go into the prompt to choose the next action. When the importance of recent memories adds up past a threshold, the character reflects, and the insights it writes are stored back in the memory stream.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Everything lives in one stream. Retrieval ranks it three ways; reflection writes new, higher-level memories back into it (dashed loop).&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;h2 id=&quot;3-planning&quot;&gt;3. Planning&lt;/h2&gt;

&lt;p&gt;To keep behavior coherent across a day, each character drafts a rough plan (about five to eight chunks), then breaks each chunk into hour-long actions, then into 5–15 minute steps. Plans are stored in the memory stream too. At every step, the model decides whether something it just noticed is worth reacting to; if so, it rewrites the plan from that point.&lt;/p&gt;

&lt;h2 id=&quot;what-they-tested&quot;&gt;What they tested&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Interviews.&lt;/strong&gt; The authors “interviewed” characters with questions about themselves, their memories, plans, reactions and reflections. 100 human evaluators ranked answers from the full architecture, versions with pieces removed, and human crowdworkers role-playing the same characters. The full architecture ranked most believable; removing reflection, then planning, then memory each made answers noticeably worse. The crowdworker answers landed below every version that still had memory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Emergent behavior over two game days.&lt;/strong&gt; One character was seeded with the idea of throwing a Valentine’s Day party. Without further scripting, word spread from 1 character to 13 of 25, and 5 of the 12 who were invited showed up. A mayoral candidacy spread from 1 character to 8 the same way.&lt;/p&gt;

&lt;h2 id=&quot;why-it-matters-for-builders&quot;&gt;Why it matters for builders&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Rank memories on more than similarity.&lt;/strong&gt; Recency + importance + relevance is a simple, readable scoring rule, and variations of it show up across agent memory systems. &lt;a href=&quot;/post/vaultmem-encrypted-memory-for-ai-agents/&quot;&gt;VaultMem&lt;/a&gt;, for example, re-ranks on cosine similarity plus a significance score that decays over time.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Store conclusions, not just events.&lt;/strong&gt; Reflections are cheap summaries the agent can retrieve later, and keeping links back to the source memories makes them auditable.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Memory and planning are the same store.&lt;/strong&gt; Writing plans into the memory stream means “what am I doing next?” is answered by the same retrieval as “what happened?”&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;honest-caveats&quot;&gt;Honest caveats&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Retrieval still misses.&lt;/strong&gt; The authors show characters failing to recall something they had heard, or recalling a detail of an event but not the event itself.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Embellishment.&lt;/strong&gt; Characters sometimes added details nobody had said, or blended in the model’s world knowledge — one described a neighbor as the author of a famous 18th-century economics book.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Too polite.&lt;/strong&gt; The authors attribute overly formal, overly agreeable dialogue to instruction tuning; one character rarely said no to suggestions, and her own interests drifted as a result.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Expensive.&lt;/strong&gt; Running 25 characters for two game days took multiple days and, in the authors’ words, “thousands of dollars in token credits.”&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Believability isn’t accuracy.&lt;/strong&gt; The evaluation measured whether behavior &lt;em&gt;seemed&lt;/em&gt; human, not whether memories were retrieved correctly. Benchmarks like &lt;a href=&quot;/post/paper-tldr-locomo-long-term-memory-benchmark/&quot;&gt;LoCoMo&lt;/a&gt; test that more directly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Earlier in the series: &lt;a href=&quot;/post/paper-tldr-memgpt/&quot;&gt;MemGPT&lt;/a&gt; (an OS-style alternative where the model pages its own memory), &lt;a href=&quot;/post/paper-tldr-complementary-learning-systems/&quot;&gt;Complementary Learning Systems&lt;/a&gt; (the brain’s version of turning episodes into general knowledge — close in spirit to reflection), and &lt;a href=&quot;/post/paper-tldr-sentence-bert/&quot;&gt;Sentence-BERT&lt;/a&gt; (the kind of embeddings behind the relevance score). Next in the series: &lt;a href=&quot;/post/paper-tldr-lost-in-the-middle/&quot;&gt;Lost in the Middle&lt;/a&gt;, on what happens to retrieved text once it lands in a long prompt.&lt;/p&gt;

&lt;p&gt;Terms like &lt;a href=&quot;/post/agent-memory-glossary/#reflection&quot;&gt;&lt;em&gt;reflection&lt;/em&gt;&lt;/a&gt;, &lt;a href=&quot;/post/agent-memory-glossary/#consolidation&quot;&gt;&lt;em&gt;consolidation&lt;/em&gt;&lt;/a&gt; and &lt;a href=&quot;/post/agent-memory-glossary/#decay&quot;&gt;&lt;em&gt;decay&lt;/em&gt;&lt;/a&gt; are defined in the &lt;a href=&quot;/post/agent-memory-glossary/#how-memories-change-over-time&quot;&gt;agent memory glossary&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title type="html">Paper TL;DR: MemGPT — Treating the Context Window Like RAM</title>
    <link href="https://www.avinashgosavi.com/post/paper-tldr-memgpt/" rel="alternate" type="text/html" title="Paper TL;DR: MemGPT — Treating the Context Window Like RAM"/>
    <id>https://www.avinashgosavi.com/post/paper-tldr-memgpt/</id>
    <published>2026-10-19T10:40:00+00:00</published>
    <updated>2026-10-23T01:35:48+00:00</updated>
    <category term="Research"/>
    <category term="Agent memory"/>
    <summary type="html">A TL;DR of MemGPT (Packer et al., 2023): how an LLM can page information in and out of a small context window with function calls, like an operating system.</summary>
    <content type="html" xml:base="https://www.avinashgosavi.com/post/paper-tldr-memgpt/">&lt;p&gt;&lt;strong&gt;Paper:&lt;/strong&gt; Charles Packer, Sarah Wooders, Kevin Lin, Vivian Fang, Shishir G. Patil, Ion Stoica &amp;amp; Joseph E. Gonzalez (UC Berkeley), &lt;a href=&quot;https://arxiv.org/abs/2310.08560&quot;&gt;&lt;em&gt;MemGPT: Towards LLMs as Operating Systems&lt;/em&gt;&lt;/a&gt; (arXiv, 2023)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One-line TL;DR:&lt;/strong&gt; Instead of waiting for bigger context windows, MemGPT treats the prompt like a computer’s RAM and lets the model itself move information between that small fast space and larger external storage, using function calls.&lt;/p&gt;

&lt;h2 id=&quot;the-problem&quot;&gt;The problem&lt;/h2&gt;

&lt;p&gt;An LLM can only “see” what fits in its context window. Long chats and big document collections don’t fit, so something has to decide what stays in view. Operating systems solved a version of this decades ago: programs act as if they have lots of memory, while the OS quietly pages data between fast RAM and slower disk. The paper calls its version &lt;strong&gt;virtual context management&lt;/strong&gt;.&lt;/p&gt;

&lt;h2 id=&quot;how-it-works&quot;&gt;How it works&lt;/h2&gt;

&lt;p&gt;MemGPT splits memory into two places.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Main context&lt;/strong&gt; — what’s actually in the prompt — has three parts:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;System instructions:&lt;/strong&gt; read-only rules, including how to use the memory functions.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Working context:&lt;/strong&gt; a small, fixed-size scratchpad of key facts (for example, things learned about the user). The model can only change it through function calls.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;A FIFO message queue:&lt;/strong&gt; the recent conversation. Its first entry is a running summary of older messages that were pushed out.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;External context&lt;/strong&gt; lives outside the prompt:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Recall storage:&lt;/strong&gt; the full message history, searchable on demand.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Archival storage:&lt;/strong&gt; a store for arbitrary text the model chooses to save or look up later.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote class=&quot;diagram-text&quot;&gt;&lt;p&gt;&lt;strong&gt;Diagram: MemGPT&apos;s two tiers of memory&lt;/strong&gt; (&lt;a href=&quot;https://www.avinashgosavi.com/post/paper-tldr-memgpt/&quot;&gt;see it in the post&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;Main context is what is in the prompt: read-only system instructions, a small working context of key facts edited through function calls, and a FIFO message queue that starts with a summary of older messages. External context sits outside the prompt: recall storage holds the full message history and archival storage holds any text the model chooses to save. The oldest messages are evicted from the queue into recall storage, and the model searches and saves across both tiers with function calls.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Main context is the prompt; external context is everything the model can page back in. The model itself moves information between them.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;The clever part is who moves things around: &lt;strong&gt;the model does&lt;/strong&gt;. Its output is parsed as function calls (search recall, write to archival, edit working context), and the results are fed back in. A few mechanics make this work:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Memory pressure warnings.&lt;/strong&gt; When the prompt gets close to full (the paper’s example uses 70%), the system adds a warning so the model can save what matters before it’s lost.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Eviction with summaries.&lt;/strong&gt; When the prompt is full, a chunk of the oldest messages is evicted and folded into the running summary. Evicted messages aren’t deleted — they stay in recall storage.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Heartbeats.&lt;/strong&gt; A function call can ask to keep going (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;request_heartbeat&lt;/code&gt;), so the model can chain several lookups before replying.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Events.&lt;/strong&gt; User messages, system alerts and scheduled timers all wake the agent up, so it doesn’t only act when a person types.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;what-they-tested&quot;&gt;What they tested&lt;/h2&gt;

&lt;p&gt;Two settings, each with a “can it really remember?” task:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Multi-session chat.&lt;/strong&gt; In a deep memory retrieval task, the agent is asked about something from an earlier session. MemGPT answered far more of these correctly than the same models with a fixed context. In a “conversation opener” task, its openers drew on what it knew about the person, and the authors found that saving facts to working context was key.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Document analysis.&lt;/strong&gt; For question answering over many documents, MemGPT could page through more search results than fit in one prompt. In a nested key-value lookup task, where one value points to another key, fixed-context models broke down as nesting got deeper, while MemGPT with GPT-4 kept answering by looking things up repeatedly.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;why-it-matters-for-builders&quot;&gt;Why it matters for builders&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Memory can be a set of tools, not just a bigger prompt.&lt;/strong&gt; Giving the model explicit read/write/search functions makes memory something you can inspect and log.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Separate “always in view” from “look it up.”&lt;/strong&gt; A small working context of key facts plus searchable history is a pattern that shows up again and again in agent memory designs.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Plan for overflow.&lt;/strong&gt; Warning before the window fills, and summarizing instead of silently truncating, is a simple idea worth copying.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;honest-caveats&quot;&gt;Honest caveats&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;It leans on the model’s function-calling skill.&lt;/strong&gt; The authors report that results dropped noticeably with GPT-3.5, which was weaker at calling functions.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Retrieval is still the bottleneck.&lt;/strong&gt; With embedding search, the right document often wasn’t near the top, and the agent tended to stop paging before exhausting the results.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;More model isn’t always better.&lt;/strong&gt; On the nested lookup task, the GPT-4 Turbo version did worse than GPT-4, and some versions gave up after too few lookups.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Self-managed memory is a judgment call by the model.&lt;/strong&gt; What gets saved, summarized or forgotten depends on the model’s choices, which is harder to predict than a fixed rule.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Earlier in the series: &lt;a href=&quot;/post/paper-tldr-locomo-long-term-memory-benchmark/&quot;&gt;LoCoMo&lt;/a&gt; (a benchmark for exactly this kind of long-term memory), &lt;a href=&quot;/post/paper-tldr-sentence-bert/&quot;&gt;Sentence-BERT&lt;/a&gt; (the embeddings behind most semantic search), and &lt;a href=&quot;/post/paper-tldr-complementary-learning-systems/&quot;&gt;Complementary Learning Systems&lt;/a&gt; (a brain-inspired split between fast and slow memory). Next in the series: &lt;a href=&quot;/post/paper-tldr-generative-agents/&quot;&gt;Generative Agents&lt;/a&gt;, which ranks a long memory stream by recency, importance and relevance.&lt;/p&gt;

&lt;p&gt;Terms like &lt;a href=&quot;/post/agent-memory-glossary/#virtual-context&quot;&gt;&lt;em&gt;virtual context&lt;/em&gt;&lt;/a&gt;, &lt;a href=&quot;/post/agent-memory-glossary/#context-window&quot;&gt;&lt;em&gt;context window&lt;/em&gt;&lt;/a&gt;, &lt;a href=&quot;/post/agent-memory-glossary/#episodic-memory&quot;&gt;&lt;em&gt;episodic memory&lt;/em&gt;&lt;/a&gt; and &lt;a href=&quot;/post/agent-memory-glossary/#top-k-retrieval&quot;&gt;&lt;em&gt;top-k retrieval&lt;/em&gt;&lt;/a&gt; are defined in the &lt;a href=&quot;/post/agent-memory-glossary/&quot;&gt;agent memory glossary&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title type="html">An Agent Memory Glossary: Terms in Plain Language</title>
    <link href="https://www.avinashgosavi.com/post/agent-memory-glossary/" rel="alternate" type="text/html" title="An Agent Memory Glossary: Terms in Plain Language"/>
    <id>https://www.avinashgosavi.com/post/agent-memory-glossary/</id>
    <published>2026-10-19T02:45:00+00:00</published>
    <updated>2026-10-23T01:35:48+00:00</updated>
    <category term="Agent memory"/>
    <category term="Explainers"/>
    <summary type="html">Plain-language definitions of the terms that come up when building memory for AI agents — episodic vs. semantic memory, embeddings, HNSW, re-ranking and more.</summary>
    <content type="html" xml:base="https://www.avinashgosavi.com/post/agent-memory-glossary/">&lt;p&gt;The vocabulary around AI agent memory borrows from neuroscience, databases, search and cryptography all at once, which makes it easy to talk past each other. This is a working glossary — short, plain definitions, with links to where each idea shows up in my own projects. I’ll keep adding to it.&lt;/p&gt;

&lt;h2 id=&quot;kinds-of-memory&quot;&gt;Kinds of memory&lt;/h2&gt;

&lt;p id=&quot;context-window&quot; class=&quot;glossary-term&quot;&gt;&lt;strong&gt;Context window (working memory).&lt;/strong&gt; What the model can see right now: the prompt, the recent conversation and anything retrieved for this turn. Fast and precise, but limited in size and gone when the session ends. Everything else on this list exists because of that limit.&lt;/p&gt;

&lt;p id=&quot;episodic-memory&quot; class=&quot;glossary-term&quot;&gt;&lt;strong&gt;Episodic memory.&lt;/strong&gt; Specific events: &lt;em&gt;what happened, when, with whom.&lt;/em&gt; “On Tuesday you said the demo slipped to Friday.” In &lt;a href=&quot;/post/vaultmem-encrypted-memory-for-ai-agents/&quot;&gt;VaultMem&lt;/a&gt; these are one of the memory types the agent can store and retrieve.&lt;/p&gt;

&lt;p id=&quot;semantic-memory&quot; class=&quot;glossary-term&quot;&gt;&lt;strong&gt;Semantic memory.&lt;/strong&gt; General facts and knowledge, detached from when they were learned. “Flash Attention reduces memory traffic by tiling.” Often distilled from many episodes.&lt;/p&gt;

&lt;p id=&quot;procedural-memory&quot; class=&quot;glossary-term&quot;&gt;&lt;strong&gt;Procedural memory.&lt;/strong&gt; How to do things: workflows, preferences about process, steps that worked before. “When debugging, check the logs before the config.”&lt;/p&gt;

&lt;p id=&quot;persona-memory&quot; class=&quot;glossary-term&quot;&gt;&lt;strong&gt;Persona (profile) memory.&lt;/strong&gt; Stable facts about the person the agent works with — name, role, standing preferences. Small, high-value and read on almost every turn.&lt;/p&gt;

&lt;p id=&quot;virtual-context&quot; class=&quot;glossary-term&quot;&gt;&lt;strong&gt;Virtual context (paging).&lt;/strong&gt; Treating the context window like a computer’s RAM: the agent moves information in and out of the prompt — saving facts to external storage, searching old messages, summarizing what gets pushed out — usually through function calls. Popularized by &lt;a href=&quot;/post/paper-tldr-memgpt/&quot;&gt;MemGPT&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;storing-and-finding-memories&quot;&gt;Storing and finding memories&lt;/h2&gt;

&lt;p id=&quot;embedding&quot; class=&quot;glossary-term&quot;&gt;&lt;strong&gt;Embedding.&lt;/strong&gt; A list of numbers that represents the meaning of a piece of text, so that similar meanings land close together. Most memory systems store an embedding next to each memory. See the &lt;a href=&quot;/post/paper-tldr-sentence-bert/&quot;&gt;Sentence-BERT TL;DR&lt;/a&gt; for where modern sentence embeddings come from.&lt;/p&gt;

&lt;p id=&quot;semantic-search&quot; class=&quot;glossary-term&quot;&gt;&lt;strong&gt;Semantic search.&lt;/strong&gt; Finding memories by meaning instead of exact words: embed the query, then return the stored memories whose embeddings are closest.&lt;/p&gt;

&lt;p id=&quot;top-k-retrieval&quot; class=&quot;glossary-term&quot;&gt;&lt;strong&gt;Top-k retrieval.&lt;/strong&gt; Returning the &lt;em&gt;k&lt;/em&gt; closest matches (say, the top 10) and putting them into the context window. Simple and effective; also where most retrieval mistakes start.&lt;/p&gt;

&lt;p id=&quot;rag&quot; class=&quot;glossary-term&quot;&gt;&lt;strong&gt;Retrieval-augmented generation (RAG).&lt;/strong&gt; Search first, then write: fetch the passages most relevant to the input and have the model generate its answer from them, instead of relying only on what’s stored in its weights. The term comes from a 2020 paper that also showed you can update what the system knows by swapping the search index — see the &lt;a href=&quot;/post/paper-tldr-retrieval-augmented-generation/&quot;&gt;RAG TL;DR&lt;/a&gt;.&lt;/p&gt;

&lt;p id=&quot;hnsw&quot; class=&quot;glossary-term&quot;&gt;&lt;strong&gt;Vector index (HNSW).&lt;/strong&gt; A data structure that makes “find the closest vectors” fast at scale. HNSW (Hierarchical Navigable Small World) builds layered graphs of neighbors so a search can hop quickly toward the right region instead of comparing against every vector. (&lt;a href=&quot;/post/paper-tldr-hnsw/&quot;&gt;Paper TL;DR&lt;/a&gt;)&lt;/p&gt;

&lt;p id=&quot;re-ranking&quot; class=&quot;glossary-term&quot;&gt;&lt;strong&gt;Re-ranking.&lt;/strong&gt; A second, slower and more careful pass over the top results — often a cross-encoder that reads the query and each candidate together — to put the truly relevant ones first.&lt;/p&gt;

&lt;p id=&quot;position-bias&quot; class=&quot;glossary-term&quot;&gt;&lt;strong&gt;Position bias (lost in the middle).&lt;/strong&gt; Models tend to use information at the start and end of a long prompt better than information buried in the middle, so &lt;em&gt;where&lt;/em&gt; retrieved memories land in the prompt affects whether they get used. Putting the strongest matches first, and keeping top-k small, helps. See the &lt;a href=&quot;/post/paper-tldr-lost-in-the-middle/&quot;&gt;Lost in the Middle TL;DR&lt;/a&gt;.&lt;/p&gt;

&lt;p id=&quot;hybrid-search&quot; class=&quot;glossary-term&quot;&gt;&lt;strong&gt;Hybrid search.&lt;/strong&gt; Combining meaning-based search with keyword search (or filters like dates and tags), because embeddings are bad at exact names, numbers and IDs.&lt;/p&gt;

&lt;p id=&quot;context-frame&quot; class=&quot;glossary-term&quot;&gt;&lt;strong&gt;Context frame.&lt;/strong&gt; The situation a memory was formed in — topic, people involved, task, session — stored alongside it so retrieval can filter by context, not just content. This is the core idea in &lt;a href=&quot;/post/attractor-memory-engine/&quot;&gt;Attractor&lt;/a&gt;.&lt;/p&gt;

&lt;p id=&quot;sufficient-evidence&quot; class=&quot;glossary-term&quot;&gt;&lt;strong&gt;Sufficient evidence.&lt;/strong&gt; The idea that a memory system’s job isn’t just to return &lt;em&gt;relevant&lt;/em&gt; memories but a set that is &lt;em&gt;enough&lt;/em&gt; to answer. Ranking memories one at a time can miss this: several records may say the same thing (so the set gets bloated), or two weak-looking records may only make sense together (so both get dropped). One recent paper defines a sufficient set as a small subset that leads the agent to the same decision it would make with its full memory (&lt;a href=&quot;https://neurips.cc/virtual/2026/169578&quot;&gt;Beyond Marginal Influence&lt;/a&gt;).&lt;/p&gt;

&lt;h2 id=&quot;how-memories-change-over-time&quot;&gt;How memories change over time&lt;/h2&gt;

&lt;p id=&quot;consolidation&quot; class=&quot;glossary-term&quot;&gt;&lt;strong&gt;Consolidation.&lt;/strong&gt; Turning many specific episodes into durable, general knowledge — summarizing a month of conversations into “prefers short answers,” for example. The brain’s version is covered in the &lt;a href=&quot;/post/paper-tldr-complementary-learning-systems/&quot;&gt;Complementary Learning Systems TL;DR&lt;/a&gt;.&lt;/p&gt;

&lt;p id=&quot;reflection&quot; class=&quot;glossary-term&quot;&gt;&lt;strong&gt;Reflection.&lt;/strong&gt; Having the agent periodically read its recent memories and write down higher-level conclusions (“she cares a lot about her research”), stored back as memories that point to the evidence they came from. Introduced for LLM agents in &lt;a href=&quot;/post/paper-tldr-generative-agents/&quot;&gt;Generative Agents&lt;/a&gt;, which also ranks memories by recency, importance and relevance together.&lt;/p&gt;

&lt;p id=&quot;decay&quot; class=&quot;glossary-term&quot;&gt;&lt;strong&gt;Decay (forgetting).&lt;/strong&gt; Letting memories that are never used fade in importance, so old noise doesn’t crowd out what matters. Forgetting is a feature.&lt;/p&gt;

&lt;p id=&quot;event-time&quot; class=&quot;glossary-term&quot;&gt;&lt;strong&gt;Event time vs. write time.&lt;/strong&gt; Two different timestamps for one memory: when the thing happened, and when it was saved. A photo from 2019 imported today should come back for “what did I do in 2019?”, not for “what did I add today?”. &lt;a href=&quot;/post/vaultmem-part-2-multimodal-temporal-backends/#temporal-search&quot;&gt;VaultMem Part 2&lt;/a&gt; keeps them as separate fields (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;captured_at&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;created_at&lt;/code&gt;) for exactly this reason.&lt;/p&gt;

&lt;p id=&quot;versioned-facts&quot; class=&quot;glossary-term&quot;&gt;&lt;strong&gt;Versioned facts (temporal state).&lt;/strong&gt; Keeping every value a fact has had — “lives in Boston”, then “lives in Seattle” — as a list of versions linked to where each came from, instead of overwriting the old one. The newest version answers “where does she live?”, while the older ones still answer “where did she live before?”. The &lt;a href=&quot;https://www.emergentmind.com/papers/2610.21900&quot;&gt;TEM paper&lt;/a&gt; adds one more step: older versions are marked as historical before the model sees them, and the authors report that this label alone gave large gains on questions about repeatedly updated facts.&lt;/p&gt;

&lt;p id=&quot;associative-memory&quot; class=&quot;glossary-term&quot;&gt;&lt;strong&gt;Associative (content-addressable) memory.&lt;/strong&gt; Retrieving a whole memory from a partial or noisy piece of it, rather than by key or address. Hopfield networks are the classic example — see &lt;a href=&quot;/post/paper-tldr-hopfield-1982/&quot;&gt;Hopfield (1982)&lt;/a&gt; and &lt;a href=&quot;/post/paper-tldr-hopfield-networks-is-all-you-need/&quot;&gt;the modern version&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;privacy&quot;&gt;Privacy&lt;/h2&gt;

&lt;p id=&quot;zero-knowledge-storage&quot; class=&quot;glossary-term&quot;&gt;&lt;strong&gt;Zero-knowledge storage.&lt;/strong&gt; The service storing your data cannot read it, because it’s encrypted with a key only you hold. The operator sees ciphertext. This is VaultMem’s core design (&lt;a href=&quot;/post/vaultmem-your-ai-agent-shouldnt-read-your-diary/&quot;&gt;why it matters, in plain language&lt;/a&gt;).&lt;/p&gt;

&lt;p id=&quot;client-side-encryption&quot; class=&quot;glossary-term&quot;&gt;&lt;strong&gt;Client-side encryption.&lt;/strong&gt; Encrypting data before it leaves your device (or your session), so the server only ever handles encrypted bytes. The usual way to get zero-knowledge storage.&lt;/p&gt;

&lt;p id=&quot;pii-sanitization&quot; class=&quot;glossary-term&quot;&gt;&lt;strong&gt;PII sanitization.&lt;/strong&gt; Detecting and masking personal details — names, emails, phone numbers — before text is sent to a model provider, then restoring them in the response. It closes a gap encryption at rest can’t: the model still has to read the text. See &lt;a href=&quot;/post/vaultmem-part-3-pii-sanitizer/&quot;&gt;VaultMem Part 3&lt;/a&gt;.&lt;/p&gt;

&lt;p id=&quot;memory-unlearning&quot; class=&quot;glossary-term&quot;&gt;&lt;strong&gt;Memory unlearning (forget requests).&lt;/strong&gt; Deleting a specific fact when the user asks — and only that fact. Done well, the fact can no longer be recalled or pieced together from other memories, while everything unrelated survives. The &lt;a href=&quot;https://www.emergentmind.com/papers/2610.18052&quot;&gt;Bemula benchmark&lt;/a&gt; tests exactly this and reports two common failures: the fact stays extractable with simple prompts, or the system deletes far more than it was asked to.&lt;/p&gt;

&lt;h2 id=&quot;measuring-it&quot;&gt;Measuring it&lt;/h2&gt;

&lt;p id=&quot;recall-at-k&quot; class=&quot;glossary-term&quot;&gt;&lt;strong&gt;Recall@k.&lt;/strong&gt; Of the questions asked, how often the right memory appears somewhere in the top &lt;em&gt;k&lt;/em&gt; results. Measures “did we find it at all.”&lt;/p&gt;

&lt;p id=&quot;mrr&quot; class=&quot;glossary-term&quot;&gt;&lt;strong&gt;MRR (mean reciprocal rank).&lt;/strong&gt; Rewards putting the right memory near the top: 1 if it’s first, ½ if second, ⅓ if third, averaged over all questions.&lt;/p&gt;

&lt;p id=&quot;locomo&quot; class=&quot;glossary-term&quot;&gt;&lt;strong&gt;LoCoMo.&lt;/strong&gt; A benchmark of very long, multi-session conversations with questions across single-hop, multi-hop, temporal, open-domain and adversarial categories. See the &lt;a href=&quot;/post/paper-tldr-locomo-long-term-memory-benchmark/&quot;&gt;LoCoMo TL;DR&lt;/a&gt;.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Missing a term, or think one of these is off? I’d like to hear it — I’ll keep this page up to date.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title type="html">Paper TL;DR: Hopfield (1982) — Memory as a Valley You Roll Into</title>
    <link href="https://www.avinashgosavi.com/post/paper-tldr-hopfield-1982/" rel="alternate" type="text/html" title="Paper TL;DR: Hopfield (1982) — Memory as a Valley You Roll Into"/>
    <id>https://www.avinashgosavi.com/post/paper-tldr-hopfield-1982/</id>
    <published>2026-10-18T22:00:00+00:00</published>
    <updated>2026-10-23T01:35:48+00:00</updated>
    <category term="Research"/>
    <category term="Agent memory"/>
    <summary type="html">A TL;DR of John Hopfield&apos;s 1982 paper on associative memory: energy landscapes, Hebbian storage, recall from partial cues and its limits — with a NumPy demo.</summary>
    <content type="html" xml:base="https://www.avinashgosavi.com/post/paper-tldr-hopfield-1982/">&lt;p&gt;&lt;strong&gt;Paper:&lt;/strong&gt; J. J. Hopfield, &lt;a href=&quot;https://www.pnas.org/doi/10.1073/pnas.79.8.2554&quot;&gt;&lt;em&gt;Neural networks and physical systems with emergent collective computational abilities&lt;/em&gt;&lt;/a&gt;, PNAS 79(8), 1982&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One-line TL;DR:&lt;/strong&gt; connect lots of simple on/off neurons with symmetric weights and the network gets an energy that only goes down as it runs — so stored memories become stable valleys, and a partial or noisy cue rolls downhill into the full memory.&lt;/p&gt;

&lt;p&gt;Almost everything I’ve built around memory started here. It’s also one of the most readable classic papers I know: five pages, very little math, a physicist explaining computation.&lt;/p&gt;

&lt;h2 id=&quot;the-setup&quot;&gt;The setup&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Neurons&lt;/strong&gt; are either on or off (+1 or −1).&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Every neuron connects to every other&lt;/strong&gt;, and the connection strengths are &lt;strong&gt;symmetric&lt;/strong&gt;: the weight from A to B equals the weight from B to A.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Storing a memory&lt;/strong&gt; uses a Hebbian rule — “neurons that fire together wire together.” For each pattern you want to remember, strengthen connections between neurons that agree and weaken those between neurons that disagree. Store several patterns by adding up their contributions.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Recalling&lt;/strong&gt; means picking neurons one at a time and flipping each to agree with the weighted vote of its neighbors, until nothing changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;the-key-insight-an-energy-that-only-goes-down&quot;&gt;The key insight: an energy that only goes down&lt;/h2&gt;

&lt;p&gt;Hopfield’s move was to define an &lt;strong&gt;energy&lt;/strong&gt; for the whole network’s state and show that, with symmetric weights and one-at-a-time updates, every update either lowers the energy or leaves it unchanged. The network can’t cycle forever; it has to settle.&lt;/p&gt;

&lt;p&gt;Stored patterns sit at the bottoms of valleys in that energy landscape. Start anywhere nearby — a corrupted photo, half a phone number — and the network slides down into the nearest stored memory. That’s &lt;strong&gt;content-addressable memory&lt;/strong&gt;: you retrieve by &lt;em&gt;what&lt;/em&gt; you remember, not by &lt;em&gt;where&lt;/em&gt; it’s stored. It’s also robust: knock out some neurons or connections and recall still mostly works.&lt;/p&gt;

&lt;blockquote class=&quot;diagram-text&quot;&gt;&lt;p&gt;&lt;strong&gt;Diagram: Storing and recalling a memory in a Hopfield network&lt;/strong&gt; (&lt;a href=&quot;https://www.avinashgosavi.com/post/paper-tldr-hopfield-1982/&quot;&gt;see it in the post&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;Storing: the Hebbian rule strengthens connections between neurons that agree, and several patterns are stored by adding up their contributions. Recalling: start from a noisy cue in which some neurons are flipped; neurons update one at a time to agree with the weighted vote of their neighbors, so the wrong ones flip back until the stored pattern reappears and nothing changes. Underneath, the network&apos;s energy only goes down or stays the same with each update, like rolling downhill into a valley, and it settles at the bottom, which is the stored memory.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Illustration with 25 neurons: store with the Hebbian rule, then a noisy cue rolls downhill into the stored pattern.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;h2 id=&quot;twenty-five-lines-of-numpy&quot;&gt;Twenty-five lines of NumPy&lt;/h2&gt;

&lt;p&gt;Store five random patterns in 100 neurons, flip 30% of the bits of one, and let it settle:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;numpy&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;rng&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;default_rng&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;N&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;P&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;patterns&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rng&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;choice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;P&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;N&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;W&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;patterns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;@&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;patterns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;N&lt;/span&gt;        &lt;span class=&quot;c1&quot;&gt;# Hebbian storage
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fill_diagonal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;W&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;                 &lt;span class=&quot;c1&quot;&gt;# no self-connections
&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;energy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.5&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;@&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;W&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;@&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;recall&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sweeps&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;copy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sweeps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rng&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;permutation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;N&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;   &lt;span class=&quot;c1&quot;&gt;# one neuron at a time
&lt;/span&gt;            &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;W&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;@&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;target&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;patterns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;noisy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;copy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;noisy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rng&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;choice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;N&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;out&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;recall&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;noisy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;noisy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;mean&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;out&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;mean&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;   &lt;span class=&quot;c1&quot;&gt;# 0.7 -&amp;gt; 1.0
&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;round&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;energy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;noisy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;round&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;energy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;     &lt;span class=&quot;c1&quot;&gt;# -8.6 -&amp;gt; -50.5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;70% correct goes to 100% correct, and the energy drops as it does.&lt;/p&gt;

&lt;h2 id=&quot;the-limits&quot;&gt;The limits&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Capacity is small.&lt;/strong&gt; Hopfield found the network reliably holds only about &lt;strong&gt;0.15 memories per neuron&lt;/strong&gt;. Push past that and recall falls apart.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Spurious memories.&lt;/strong&gt; The landscape also grows valleys nobody stored — blends of real memories — and a cue can get stuck in one.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Similar memories interfere.&lt;/strong&gt; Patterns that overlap a lot blur into each other.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those limits are exactly what the 2020 follow-up fixes: &lt;a href=&quot;/post/paper-tldr-hopfield-networks-is-all-you-need/&quot;&gt;modern Hopfield networks&lt;/a&gt; sharpen the valleys so capacity grows exponentially — and their update turns out to be transformer attention.&lt;/p&gt;

&lt;h2 id=&quot;why-it-still-matters-to-me&quot;&gt;Why it still matters to me&lt;/h2&gt;

&lt;p&gt;Databases retrieve by key. Vector stores retrieve by nearest neighbor. Hopfield networks retrieve by &lt;strong&gt;settling&lt;/strong&gt; — pattern completion from a partial cue, which is much closer to how remembering feels. That idea is the reason &lt;a href=&quot;/post/attractor-memory-engine/&quot;&gt;Attractor&lt;/a&gt; is called Attractor.&lt;/p&gt;

&lt;p&gt;That completes the six papers from &lt;a href=&quot;/post/paper-tldr-six-papers-on-memory/&quot;&gt;Part 1&lt;/a&gt;. The whole list, with short TL;DRs, lives on my &lt;a href=&quot;/research/#papershelf&quot;&gt;papershelf&lt;/a&gt;. The series keeps going with &lt;a href=&quot;/post/paper-tldr-memgpt/&quot;&gt;MemGPT&lt;/a&gt;, which treats an LLM’s context window like an operating system’s memory.&lt;/p&gt;

&lt;p&gt;For a one-paragraph definition of &lt;a href=&quot;/post/agent-memory-glossary/#associative-memory&quot;&gt;&lt;em&gt;associative (content-addressable) memory&lt;/em&gt;&lt;/a&gt;, see the &lt;a href=&quot;/post/agent-memory-glossary/&quot;&gt;agent memory glossary&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title type="html">Paper TL;DR: Sentence-BERT — The Quiet Workhorse Behind Semantic Search</title>
    <link href="https://www.avinashgosavi.com/post/paper-tldr-sentence-bert/" rel="alternate" type="text/html" title="Paper TL;DR: Sentence-BERT — The Quiet Workhorse Behind Semantic Search"/>
    <id>https://www.avinashgosavi.com/post/paper-tldr-sentence-bert/</id>
    <published>2026-10-18T21:00:00+00:00</published>
    <updated>2026-10-23T12:36:36+00:00</updated>
    <category term="Research"/>
    <category term="Agent memory"/>
    <summary type="html">A TL;DR of Sentence-BERT (Reimers &amp; Gurevych, 2019): why BERT is too slow for search, how siamese training fixes it, and why it still powers semantic search.</summary>
    <content type="html" xml:base="https://www.avinashgosavi.com/post/paper-tldr-sentence-bert/">&lt;p&gt;&lt;strong&gt;Paper:&lt;/strong&gt; Nils Reimers &amp;amp; Iryna Gurevych, &lt;a href=&quot;https://arxiv.org/abs/1908.10084&quot;&gt;&lt;em&gt;Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks&lt;/em&gt;&lt;/a&gt; (EMNLP 2019)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One-line TL;DR:&lt;/strong&gt; fine-tune BERT so that every sentence gets its own fixed-size embedding you can compare with cosine similarity — turning semantic search over thousands of sentences from hours into seconds.&lt;/p&gt;

&lt;p&gt;It’s the least flashy paper in this series and the one my code depends on most. Every time &lt;a href=&quot;/post/attractor-memory-engine/&quot;&gt;Attractor&lt;/a&gt;, &lt;a href=&quot;/series/#vaultmem&quot;&gt;VaultMem&lt;/a&gt; or &lt;a href=&quot;/post/daylog-security-architecture/&quot;&gt;daylog&lt;/a&gt; “searches by meaning,” it’s using a model descended from this paper.&lt;/p&gt;

&lt;h2 id=&quot;the-problem-bert-is-a-slow-judge&quot;&gt;The problem: BERT is a slow judge&lt;/h2&gt;

&lt;p&gt;In 2019, BERT was the best tool for deciding whether two sentences meant the same thing — but only if you fed it &lt;strong&gt;both sentences together&lt;/strong&gt;. That works for scoring one pair. It falls apart for search.&lt;/p&gt;

&lt;p&gt;The paper’s example: to find the most similar pair in a collection of 10,000 sentences, BERT would need around 50 million pairwise passes — roughly &lt;strong&gt;65 hours&lt;/strong&gt; on a GPU. And you can’t precompute anything, because every score depends on both sentences at once.&lt;/p&gt;

&lt;p&gt;The obvious shortcut — run each sentence through BERT alone and average its outputs (or take the special &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[CLS]&lt;/code&gt; token) — produced embeddings that were often &lt;em&gt;worse&lt;/em&gt; than old-fashioned averaged word vectors.&lt;/p&gt;

&lt;h2 id=&quot;the-fix-train-it-to-embed&quot;&gt;The fix: train it to embed&lt;/h2&gt;

&lt;p&gt;Sentence-BERT puts two copies of the same BERT side by side (a &lt;strong&gt;siamese&lt;/strong&gt; network — shared weights), runs one sentence through each, pools each output into a single vector, and trains the whole thing so that:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;sentences that mean similar things land close together, and&lt;/li&gt;
  &lt;li&gt;sentences that don’t land far apart.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They trained on natural-language-inference data (pairs labeled entailment / contradiction / neutral) and on similarity-scored pairs. Afterward you throw away the “pair” setup: each sentence goes through the model &lt;strong&gt;once&lt;/strong&gt;, you store its vector, and comparing two sentences is a cheap cosine similarity.&lt;/p&gt;

&lt;p&gt;That same 10,000-sentence search drops to about &lt;strong&gt;5 seconds&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote class=&quot;diagram-text&quot;&gt;&lt;p&gt;&lt;strong&gt;Diagram: Plain BERT versus Sentence-BERT for comparing sentences&lt;/strong&gt; (&lt;a href=&quot;https://www.avinashgosavi.com/post/paper-tldr-sentence-bert/&quot;&gt;see it in the post&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;Top: plain BERT reads both sentences together and outputs one similarity score for that pair, so nothing can be precomputed; finding the most similar pair among 10,000 sentences takes about 50 million passes, roughly 65 hours on a GPU. Bottom: Sentence-BERT runs each sentence through BERT on its own, with two copies sharing the same weights, pools the output into one vector per sentence, and compares the two vectors with cosine similarity. Each vector is computed once and stored, so the same search takes about 5 seconds.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Plain BERT has to re-read every pair together. Sentence-BERT gives each sentence its own vector once, so comparing two is just a cosine similarity.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;h2 id=&quot;why-it-still-matters&quot;&gt;Why it still matters&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;It made vector search practical.&lt;/strong&gt; Embed once, store in an index (pgvector, Qdrant, &lt;a href=&quot;/post/paper-tldr-hnsw/&quot;&gt;HNSW&lt;/a&gt;), and query in milliseconds. That’s the foundation of nearly every &lt;a href=&quot;/post/paper-tldr-retrieval-augmented-generation/&quot;&gt;RAG&lt;/a&gt; system and agent memory store.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;The library outlived the paper.&lt;/strong&gt; The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sentence-transformers&lt;/code&gt; package that came out of this work is still how most people get embedding models running in a few lines.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Small models are often enough.&lt;/strong&gt; The 384-dimension model I use in &lt;a href=&quot;/post/attractor-memory-engine/&quot;&gt;Attractor&lt;/a&gt; runs comfortably on a CPU — handy when the whole point is &lt;a href=&quot;/uses/&quot;&gt;keeping data on hardware you own&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;caveats-worth-knowing&quot;&gt;Caveats worth knowing&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Bi-encoders like SBERT trade a little accuracy for speed. When precision matters, a common pattern is to retrieve with embeddings, then &lt;strong&gt;re-rank&lt;/strong&gt; the top results with a slower cross-encoder.&lt;/li&gt;
  &lt;li&gt;“Similar meaning” is whatever the training data said it was. Domain-specific text (logs, configs, medical notes) often needs a domain-tuned model.&lt;/li&gt;
  &lt;li&gt;Embeddings find &lt;em&gt;related&lt;/em&gt; text; they don’t reason. Multi-hop questions — the ones &lt;a href=&quot;/post/paper-tldr-locomo-long-term-memory-benchmark/&quot;&gt;LoCoMo&lt;/a&gt; showed are hardest — need more than nearest neighbors.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Earlier in the series: &lt;a href=&quot;/post/paper-tldr-six-papers-on-memory/&quot;&gt;six papers on AI memory&lt;/a&gt;, &lt;a href=&quot;/post/paper-tldr-hopfield-networks-is-all-you-need/&quot;&gt;Hopfield Networks Is All You Need&lt;/a&gt;, &lt;a href=&quot;/post/paper-tldr-complementary-learning-systems/&quot;&gt;Complementary Learning Systems&lt;/a&gt;, &lt;a href=&quot;/post/paper-tldr-locomo-long-term-memory-benchmark/&quot;&gt;LoCoMo&lt;/a&gt; and &lt;a href=&quot;/post/paper-tldr-memory-as-asset/&quot;&gt;Memory as Asset&lt;/a&gt;. Next in the series: &lt;a href=&quot;/post/paper-tldr-hopfield-1982/&quot;&gt;Hopfield (1982)&lt;/a&gt;, the original associative memory that recalls from a partial cue.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/post/agent-memory-glossary/#embedding&quot;&gt;&lt;em&gt;Embeddings&lt;/em&gt;&lt;/a&gt;, &lt;a href=&quot;/post/agent-memory-glossary/#hnsw&quot;&gt;&lt;em&gt;HNSW&lt;/em&gt;&lt;/a&gt; and &lt;a href=&quot;/post/agent-memory-glossary/#re-ranking&quot;&gt;&lt;em&gt;re-ranking&lt;/em&gt;&lt;/a&gt; each get a short definition in the &lt;a href=&quot;/post/agent-memory-glossary/#storing-and-finding-memories&quot;&gt;agent memory glossary&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title type="html">Paper TL;DR: Memory as Asset — Whose Memory Is It Anyway?</title>
    <link href="https://www.avinashgosavi.com/post/paper-tldr-memory-as-asset/" rel="alternate" type="text/html" title="Paper TL;DR: Memory as Asset — Whose Memory Is It Anyway?"/>
    <id>https://www.avinashgosavi.com/post/paper-tldr-memory-as-asset/</id>
    <published>2026-10-18T20:00:00+00:00</published>
    <updated>2026-10-24T05:39:10+00:00</updated>
    <category term="Research"/>
    <category term="Agent memory"/>
    <category term="Privacy"/>
    <summary type="html">A TL;DR of Memory as Asset (Pan, Huang &amp; Yang, 2026): moving AI memory from agent-centric to human-centric, its three principles, and how it shaped VaultMem.</summary>
    <content type="html" xml:base="https://www.avinashgosavi.com/post/paper-tldr-memory-as-asset/">&lt;p&gt;&lt;strong&gt;Paper:&lt;/strong&gt; Yanqi Pan, Qinghao Huang &amp;amp; Weihao Yang, &lt;a href=&quot;https://arxiv.org/abs/2603.14212&quot;&gt;&lt;em&gt;Memory as Asset: From Agent-centric to Human-centric Memory Management&lt;/em&gt;&lt;/a&gt; (arXiv, March 2026)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One-line TL;DR:&lt;/strong&gt; the memory an AI keeps about you is designed today as the agent’s internal resource; this paper argues it should be &lt;em&gt;your&lt;/em&gt; asset — owned by you, shared only under your permissions, and able to grow over time.&lt;/p&gt;

&lt;p&gt;This is the paper that reshaped &lt;a href=&quot;/series/#vaultmem&quot;&gt;VaultMem&lt;/a&gt;. I found it while I was stuck on a vague feeling that something about agent memory was backwards, and it gave that feeling a name.&lt;/p&gt;

&lt;h2 id=&quot;the-problem-agent-centric-memory&quot;&gt;The problem: agent-centric memory&lt;/h2&gt;

&lt;p&gt;Most memory systems treat what an agent remembers about you as part of the agent — stored by the platform, shaped for the platform’s use, and stuck there. The authors call this &lt;strong&gt;agent-centric&lt;/strong&gt; memory. The consequences are familiar: you can’t take your history with you, you can’t decide who sees it, and every product rebuilds its own isolated “memory island” about the same person.&lt;/p&gt;

&lt;h2 id=&quot;the-proposal-memory-as-a-personal-asset&quot;&gt;The proposal: memory as a personal asset&lt;/h2&gt;

&lt;p&gt;They propose flipping the default, built on three principles:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Memory in hand&lt;/strong&gt; — individuals own and control their memories, not centralized platforms.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Memory groups&lt;/strong&gt; — memories can be shared and combined, but only through permissions the owner sets.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Collective memory evolution&lt;/strong&gt; — memory isn’t frozen: it’s recorded, consolidated and abstracted into higher-level knowledge over time, and can be exchanged between groups under control.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each memory object carries its content, context, an explicit &lt;strong&gt;owner&lt;/strong&gt;, an &lt;strong&gt;access policy&lt;/strong&gt; and a &lt;strong&gt;version&lt;/strong&gt;. Ownership and permissions are part of the data itself, not an afterthought in some admin panel.&lt;/p&gt;

&lt;h2 id=&quot;the-three-layers&quot;&gt;The three layers&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Personal storage&lt;/strong&gt; — fast storage of each person’s private memory objects.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Evolution&lt;/strong&gt; — updates memory from interactions: recording experiences, merging related memories, abstracting knowledge. It can run locally for privacy or remotely for compute.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Memory exchange network&lt;/strong&gt; — a decentralized way to discover, authenticate and exchange memory assets.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Notably, the paper doesn’t insist everything stays on your device. If a provider hosts your memory, it should be encrypted so only you can read it.&lt;/p&gt;

&lt;blockquote class=&quot;diagram-text&quot;&gt;&lt;p&gt;&lt;strong&gt;Diagram: Memory as Asset: memory objects and the three layers&lt;/strong&gt; (&lt;a href=&quot;https://www.avinashgosavi.com/post/paper-tldr-memory-as-asset/&quot;&gt;see it in the post&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;Each memory object carries its content, its context and a version, plus an explicit owner and an access policy, so ownership travels with the data. Memory objects are kept in layer 1, personal storage: each person&apos;s private memory objects. VaultMem implements this layer, encrypted with a key only the user holds. Layer 2, evolution, updates memory from interactions: it records experiences, merges related memories and abstracts knowledge, and can run locally or remotely. Layer 3, the memory exchange network, discovers, authenticates and exchanges memory assets; the paper sketches it rather than solving it. Overall: owned by you, shared only through your permissions.&lt;/p&gt;&lt;p&gt;&lt;em&gt;The paper&apos;s design in one picture: owner and access policy live inside every memory object, and three layers store, evolve and exchange those objects. VaultMem is the first layer.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;h2 id=&quot;how-it-shaped-what-i-built&quot;&gt;How it shaped what I built&lt;/h2&gt;

&lt;p&gt;That last point is where VaultMem lives. I treat it as an implementation of the &lt;strong&gt;first layer&lt;/strong&gt;: the platform stores your memories, but they’re encrypted with a key only you hold, so the operator cryptographically can’t read them. Everything in &lt;a href=&quot;/series/#vaultmem&quot;&gt;the VaultMem series&lt;/a&gt; — client-side encryption, encrypted search, &lt;a href=&quot;/post/vaultmem-part-3-pii-sanitizer/&quot;&gt;keeping PII away from the model provider&lt;/a&gt; — follows from taking “memory in hand” literally.&lt;/p&gt;

&lt;h2 id=&quot;caveats-worth-knowing&quot;&gt;Caveats worth knowing&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;The authors are upfront that this is an &lt;strong&gt;early draft&lt;/strong&gt;: the terms and design are still evolving.&lt;/li&gt;
  &lt;li&gt;It’s a &lt;strong&gt;conceptual and architectural&lt;/strong&gt; paper — there are no benchmarks or experiments yet. The claim that existing systems meet at most two of the three principles is an argument, not a measurement.&lt;/li&gt;
  &lt;li&gt;The exchange network raises hard questions (trust, incentives, revocation) that the paper sketches rather than solves.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I still think it’s the right question to ask before building any memory feature: &lt;em&gt;whose memory is this?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Earlier in the series: &lt;a href=&quot;/post/paper-tldr-six-papers-on-memory/&quot;&gt;six papers on AI memory&lt;/a&gt;, &lt;a href=&quot;/post/paper-tldr-hopfield-networks-is-all-you-need/&quot;&gt;Hopfield Networks Is All You Need&lt;/a&gt;, &lt;a href=&quot;/post/paper-tldr-complementary-learning-systems/&quot;&gt;Complementary Learning Systems&lt;/a&gt; and &lt;a href=&quot;/post/paper-tldr-locomo-long-term-memory-benchmark/&quot;&gt;LoCoMo&lt;/a&gt;. Next in the series: &lt;a href=&quot;/post/paper-tldr-sentence-bert/&quot;&gt;Sentence-BERT&lt;/a&gt;, the sentence embeddings behind most semantic search.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/post/agent-memory-glossary/#zero-knowledge-storage&quot;&gt;&lt;em&gt;Zero-knowledge storage&lt;/em&gt;&lt;/a&gt; and &lt;a href=&quot;/post/agent-memory-glossary/#client-side-encryption&quot;&gt;&lt;em&gt;client-side encryption&lt;/em&gt;&lt;/a&gt; are defined in the privacy section of the &lt;a href=&quot;/post/agent-memory-glossary/#privacy&quot;&gt;agent memory glossary&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title type="html">Paper TL;DR: LoCoMo — How Good Is Your Agent&apos;s Memory, Really?</title>
    <link href="https://www.avinashgosavi.com/post/paper-tldr-locomo-long-term-memory-benchmark/" rel="alternate" type="text/html" title="Paper TL;DR: LoCoMo — How Good Is Your Agent&apos;s Memory, Really?"/>
    <id>https://www.avinashgosavi.com/post/paper-tldr-locomo-long-term-memory-benchmark/</id>
    <published>2026-10-18T19:00:00+00:00</published>
    <updated>2026-10-23T01:35:48+00:00</updated>
    <category term="Research"/>
    <category term="Agent memory"/>
    <summary type="html">A TL;DR of the LoCoMo benchmark (Maharana et al., 2024) for very long-term conversational memory — what it tests, what it found, and how I used it on VaultMem.</summary>
    <content type="html" xml:base="https://www.avinashgosavi.com/post/paper-tldr-locomo-long-term-memory-benchmark/">&lt;p&gt;&lt;strong&gt;Paper:&lt;/strong&gt; Maharana et al., &lt;a href=&quot;https://arxiv.org/abs/2402.17753&quot;&gt;&lt;em&gt;Evaluating Very Long-Term Conversational Memory of LLM Agents&lt;/em&gt;&lt;/a&gt; (ACL 2024)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One-line TL;DR:&lt;/strong&gt; LoCoMo is a benchmark of very long, multi-session conversations with questions that probe whether a system really remembers — and even strong long-context models and retrieval setups fall well short of people on it.&lt;/p&gt;

&lt;p&gt;If you’re building memory for an agent, this is the paper that keeps you honest.&lt;/p&gt;

&lt;h2 id=&quot;whats-in-the-benchmark&quot;&gt;What’s in the benchmark&lt;/h2&gt;

&lt;p&gt;Most chat benchmarks are a few turns long. LoCoMo conversations run for &lt;strong&gt;dozens of sessions and hundreds of turns&lt;/strong&gt;, spread over simulated weeks and months. The authors generated them with LLM agents that each had a persona and a timeline of life events, then had people review and fix them so the conversations stay consistent. Some turns include shared images, too.&lt;/p&gt;

&lt;p&gt;On top of the conversations they built three tasks: answering questions, summarizing events, and generating responses. The question-answering part is the one I care about, and its categories are what make it useful:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Single-hop&lt;/strong&gt; — one fact from one place in the history.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Multi-hop&lt;/strong&gt; — needs to connect facts from different sessions.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Temporal&lt;/strong&gt; — when did something happen, or what happened before or after.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Open-domain&lt;/strong&gt; — combine the conversation with general knowledge.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Adversarial&lt;/strong&gt; — questions about things that &lt;em&gt;never&lt;/em&gt; happened, where the right answer is “that wasn’t mentioned.”&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote class=&quot;diagram-text&quot;&gt;&lt;p&gt;&lt;strong&gt;Diagram: How the LoCoMo benchmark is built&lt;/strong&gt; (&lt;a href=&quot;https://www.avinashgosavi.com/post/paper-tldr-locomo-long-term-memory-benchmark/&quot;&gt;see it in the post&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;Each LoCoMo conversation averages about 300 turns and 9 thousand tokens, over up to 35 sessions spread across simulated weeks and months. Three tasks are built on top: question answering, event summarization, and multi-modal dialogue generation. Question answering splits into five categories: single-hop, one fact from one place; multi-hop, connecting facts across sessions; temporal, when something happened or what came before or after; open-domain, combining the conversation with general knowledge; and adversarial, questions about things that never happened, where the right answer is that it was not mentioned.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Scoring each category on its own keeps a strong overall number from hiding weak temporal or multi-hop answers.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;h2 id=&quot;what-they-found&quot;&gt;What they found&lt;/h2&gt;

&lt;p&gt;Even with long context windows or &lt;a href=&quot;/post/paper-tldr-retrieval-augmented-generation/&quot;&gt;retrieval-augmented generation&lt;/a&gt;, models trail human performance by a wide margin. Retrieval helps, but the hard categories stay hard — especially reasoning about time and resisting the urge to make up answers to adversarial questions. Bigger context alone doesn’t fix it.&lt;/p&gt;

&lt;h2 id=&quot;how-i-used-it&quot;&gt;How I used it&lt;/h2&gt;

&lt;p&gt;When I built &lt;a href=&quot;/series/#vaultmem&quot;&gt;VaultMem&lt;/a&gt;, my first benchmark was 18 synthetic memories I wrote myself. It looked great — which was the problem. LoCoMo gave me a test I didn’t design. &lt;a href=&quot;/post/vaultmem-part-2-multimodal-temporal-backends/&quot;&gt;On a sample of its questions&lt;/a&gt;, retrieval was strong for single-hop facts and weakest for multi-hop, which needs pieces that weren’t individually asked about. That’s a retrieval problem before it’s a model problem, and it changed what I worked on next.&lt;/p&gt;

&lt;h2 id=&quot;the-takeaway-for-builders&quot;&gt;The takeaway for builders&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Test against data you didn’t write.&lt;/strong&gt; A memory system always looks good on its author’s examples.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Score by category, not just overall.&lt;/strong&gt; “85% accurate” hides that temporal and multi-hop questions might be failing.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Measure the “I don’t know.”&lt;/strong&gt; A memory that confidently invents a past is worse than one that admits a gap.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Earlier in the series: &lt;a href=&quot;/post/paper-tldr-six-papers-on-memory/&quot;&gt;six papers on AI memory&lt;/a&gt;, &lt;a href=&quot;/post/paper-tldr-hopfield-networks-is-all-you-need/&quot;&gt;Hopfield Networks Is All You Need&lt;/a&gt;, and &lt;a href=&quot;/post/paper-tldr-complementary-learning-systems/&quot;&gt;Complementary Learning Systems&lt;/a&gt;. Next in the series: &lt;a href=&quot;/post/paper-tldr-memory-as-asset/&quot;&gt;Memory as Asset&lt;/a&gt;, on who should own an agent’s memory.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/post/agent-memory-glossary/#recall-at-k&quot;&gt;&lt;em&gt;Recall@k&lt;/em&gt;&lt;/a&gt; and &lt;a href=&quot;/post/agent-memory-glossary/#mrr&quot;&gt;&lt;em&gt;MRR&lt;/em&gt;&lt;/a&gt;, the usual ways to score retrieval, are explained in the &lt;a href=&quot;/post/agent-memory-glossary/#measuring-it&quot;&gt;agent memory glossary&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title type="html">Paper TL;DR: Complementary Learning Systems — Why Agents Need Two Kinds of Memory</title>
    <link href="https://www.avinashgosavi.com/post/paper-tldr-complementary-learning-systems/" rel="alternate" type="text/html" title="Paper TL;DR: Complementary Learning Systems — Why Agents Need Two Kinds of Memory"/>
    <id>https://www.avinashgosavi.com/post/paper-tldr-complementary-learning-systems/</id>
    <published>2026-10-18T18:00:00+00:00</published>
    <updated>2026-10-23T01:35:48+00:00</updated>
    <category term="Research"/>
    <category term="Agent memory"/>
    <summary type="html">A TL;DR of McClelland, McNaughton &amp; O&apos;Reilly (1995): the brain&apos;s fast and slow learners, catastrophic interference, and what it suggests for AI agent memory.</summary>
    <content type="html" xml:base="https://www.avinashgosavi.com/post/paper-tldr-complementary-learning-systems/">&lt;p&gt;&lt;strong&gt;Paper:&lt;/strong&gt; McClelland, McNaughton &amp;amp; O’Reilly, &lt;a href=&quot;https://doi.org/10.1037/0033-295X.102.3.419&quot;&gt;&lt;em&gt;Why there are complementary learning systems in the hippocampus and neocortex&lt;/em&gt;&lt;/a&gt;, Psychological Review (1995)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One-line TL;DR:&lt;/strong&gt; the brain learns with two systems on purpose — a fast one that grabs specific experiences and a slow one that gradually extracts general knowledge — because a single system can’t do both without wrecking what it already knows.&lt;/p&gt;

&lt;p&gt;It’s a 1995 psychology paper, and it’s one of the most useful things I’ve read about designing memory for AI agents.&lt;/p&gt;

&lt;h2 id=&quot;the-problem-learning-fast-breaks-things&quot;&gt;The problem: learning fast breaks things&lt;/h2&gt;

&lt;p&gt;The paper starts from a puzzle that anyone who has fine-tuned a neural network will recognize. If you teach a network something new by adjusting the same shared weights that hold everything else, the new learning can overwrite the old. This is &lt;strong&gt;catastrophic interference&lt;/strong&gt;: learn B, forget A.&lt;/p&gt;

&lt;p&gt;The fix that works in artificial networks is to learn slowly and &lt;em&gt;interleave&lt;/em&gt; — mix new examples with old ones so the weights find a setting that serves both. But that’s slow. You can’t wait for hundreds of repetitions to remember where you parked this morning.&lt;/p&gt;

&lt;h2 id=&quot;the-brains-answer-two-learners&quot;&gt;The brain’s answer: two learners&lt;/h2&gt;

&lt;p&gt;The authors argue the brain splits the job:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Hippocampus — the fast learner.&lt;/strong&gt; Stores specific episodes quickly, often after a single exposure, using sparse, separate codes so similar memories don’t collide. Great for “what happened at Tuesday’s meeting.”&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Neocortex — the slow learner.&lt;/strong&gt; Learns gradually, with overlapping representations that capture structure shared across many experiences. Great for “how meetings at my company usually go.”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The bridge between them is &lt;strong&gt;replay&lt;/strong&gt;. Over time — notably during sleep — the hippocampus reactivates recent episodes, and the neocortex learns from those replays &lt;em&gt;interleaved&lt;/em&gt; with older knowledge. That’s consolidation: specific memories slowly become general knowledge without overwriting what came before.&lt;/p&gt;

&lt;blockquote class=&quot;diagram-text&quot;&gt;&lt;p&gt;&lt;strong&gt;Diagram: Two learners connected by replay&lt;/strong&gt; (&lt;a href=&quot;https://www.avinashgosavi.com/post/paper-tldr-complementary-learning-systems/&quot;&gt;see it in the post&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;A new experience is written quickly into the hippocampus, the fast learner that stores specific episodes; in an agent this is an episodic log or vector store. Replay, interleaved with older knowledge, slowly teaches the neocortex, the slow learner that extracts general structure; in an agent this is consolidated facts and preferences.&lt;/p&gt;&lt;p&gt;&lt;em&gt;The two-learner idea from the paper, with the agent-memory equivalent of each part.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;The paper backs this with evidence from people with hippocampal damage, who struggle to form new episodic memories yet keep older knowledge, and with network simulations showing why interleaved learning avoids interference.&lt;/p&gt;

&lt;h2 id=&quot;why-it-matters-for-ai-agents&quot;&gt;Why it matters for AI agents&lt;/h2&gt;

&lt;p&gt;Read with agents in mind, the paper is basically a design doc:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Keep an episodic store separate from the model.&lt;/strong&gt; Fine-tuning a model on every conversation is the “learn fast in shared weights” mistake. A separate, fast-writing memory — a log or vector store of specific events — is the hippocampus.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Consolidate on a schedule, not on every write.&lt;/strong&gt; Periodically summarize or distill many episodes into durable facts and preferences, mixing in what’s already known instead of overwriting it.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Let memories change over time.&lt;/strong&gt; Things that keep getting recalled should get stronger; one-off details can fade. A store that treats every memory as equally permanent forever is missing half the design.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is the model I borrowed for &lt;a href=&quot;/post/attractor-memory-engine/&quot;&gt;Attractor&lt;/a&gt;: memories carry the context they were formed in, track how often they’re used, and are meant to strengthen or decay rather than sit frozen.&lt;/p&gt;

&lt;h2 id=&quot;caveats-worth-knowing&quot;&gt;Caveats worth knowing&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;The theory has been extended since 1995 — the neocortex can learn new things quickly when they fit an existing schema — so “neocortex is always slow” is a simplification.&lt;/li&gt;
  &lt;li&gt;Brains aren’t databases; the analogy is a guide for design choices, not a blueprint.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next in the series: the benchmark that told me how good my own memory system actually was. Earlier parts: &lt;a href=&quot;/post/paper-tldr-six-papers-on-memory/&quot;&gt;six papers on AI memory&lt;/a&gt; and &lt;a href=&quot;/post/paper-tldr-hopfield-networks-is-all-you-need/&quot;&gt;Hopfield Networks Is All You Need&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Quick definitions of &lt;a href=&quot;/post/agent-memory-glossary/#consolidation&quot;&gt;&lt;em&gt;consolidation&lt;/em&gt;&lt;/a&gt; and &lt;a href=&quot;/post/agent-memory-glossary/#decay&quot;&gt;&lt;em&gt;decay&lt;/em&gt;&lt;/a&gt; are in the &lt;a href=&quot;/post/agent-memory-glossary/&quot;&gt;agent memory glossary&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title type="html">Paper TL;DR: Hopfield Networks Is All You Need — Attention Is a Memory Lookup</title>
    <link href="https://www.avinashgosavi.com/post/paper-tldr-hopfield-networks-is-all-you-need/" rel="alternate" type="text/html" title="Paper TL;DR: Hopfield Networks Is All You Need — Attention Is a Memory Lookup"/>
    <id>https://www.avinashgosavi.com/post/paper-tldr-hopfield-networks-is-all-you-need/</id>
    <published>2026-10-18T17:00:00+00:00</published>
    <updated>2026-10-24T02:36:34+00:00</updated>
    <category term="Research"/>
    <category term="Agent memory"/>
    <summary type="html">A walkthrough of Ramsauer et al. (2020): how modern Hopfield networks store exponentially many memories and why their update rule is transformer attention.</summary>
    <content type="html" xml:base="https://www.avinashgosavi.com/post/paper-tldr-hopfield-networks-is-all-you-need/">&lt;p&gt;&lt;strong&gt;Paper:&lt;/strong&gt; Ramsauer, Schäfl, Lehner et al., &lt;a href=&quot;https://arxiv.org/abs/2008.02217&quot;&gt;&lt;em&gt;Hopfield Networks is All You Need&lt;/em&gt;&lt;/a&gt; (2020)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One-line TL;DR:&lt;/strong&gt; a continuous version of the 1982 Hopfield network can store exponentially many memories, usually retrieves one in a single step — and that single step is exactly the attention operation inside every transformer.&lt;/p&gt;

&lt;p&gt;This is the paper that made me stop thinking of Hopfield networks as history. Here’s the idea in plain language, plus a tiny demo you can run.&lt;/p&gt;

&lt;h2 id=&quot;the-old-idea-memories-as-valleys&quot;&gt;The old idea: memories as valleys&lt;/h2&gt;

&lt;p&gt;In &lt;a href=&quot;https://www.pnas.org/doi/10.1073/pnas.79.8.2554&quot;&gt;Hopfield’s 1982 network&lt;/a&gt;, you store patterns (say, strings of +1 and −1) in the connection weights. The network has an “energy,” and each stored pattern sits at the bottom of a valley. Start from a corrupted pattern, let the network update, and it rolls downhill into the nearest stored memory. That’s &lt;em&gt;content-addressable memory&lt;/em&gt;: you find a memory by giving a piece of it, not by knowing its address.&lt;/p&gt;

&lt;p&gt;The catch: the classic network only holds about 0.14 patterns per neuron. Store more than that and the valleys blur together into spurious memories.&lt;/p&gt;

&lt;h2 id=&quot;the-new-idea-sharpen-the-valleys&quot;&gt;The new idea: sharpen the valleys&lt;/h2&gt;

&lt;p&gt;Ramsauer et al. swap the old quadratic energy for one built on &lt;strong&gt;log-sum-exp&lt;/strong&gt; — a smooth version of “max.” Intuitively, each stored pattern now carves a much sharper, deeper valley, so many more of them fit without interfering. The paper shows the capacity grows &lt;em&gt;exponentially&lt;/em&gt; with the dimension of the patterns, and that retrieval typically converges after &lt;strong&gt;one&lt;/strong&gt; update.&lt;/p&gt;

&lt;p&gt;That one update is:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;new_state = X · softmax(β · Xᵀ · state)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;where the columns of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;X&lt;/code&gt; are the stored patterns and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;β&lt;/code&gt; controls how sharp the valleys are.&lt;/p&gt;

&lt;h2 id=&quot;wait--thats-attention&quot;&gt;Wait — that’s attention&lt;/h2&gt;

&lt;p&gt;Now look at transformer attention:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;output = V · softmax(Kᵀ · q / √d)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Set the &lt;strong&gt;query&lt;/strong&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;q&lt;/code&gt; to the current state, use the stored patterns as both &lt;strong&gt;keys&lt;/strong&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;K&lt;/code&gt; and &lt;strong&gt;values&lt;/strong&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;V&lt;/code&gt;, and pick &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;β = 1/√d&lt;/code&gt;. It’s the same equation. Every attention head in an LLM is doing a one-step associative memory lookup: compare the query against everything stored, softmax the similarities, return a weighted blend that’s usually dominated by the best match.&lt;/p&gt;

&lt;p&gt;The paper goes further, showing how different heads and layers behave like different retrieval regimes — some settle on one memory, some average over many — but the core equivalence is the part that stuck with me.&lt;/p&gt;

&lt;h2 id=&quot;twenty-lines-of-numpy&quot;&gt;Twenty lines of NumPy&lt;/h2&gt;

&lt;p&gt;Store five random ±1 patterns, flip 40% of the bits in one of them, and do a single update:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;numpy&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;rng&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;default_rng&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;X&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rng&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;choice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;   &lt;span class=&quot;c1&quot;&gt;# stored memories (columns)
&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;softmax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;z&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;exp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;z&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;z&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;retrieve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;xi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;beta&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;X&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;@&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;softmax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;beta&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;X&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;@&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;      &lt;span class=&quot;c1&quot;&gt;# one modern-Hopfield update
&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;target&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;X&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[:,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;noisy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;copy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;noisy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rng&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;choice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.4&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;out&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;retrieve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;noisy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;overlap before:&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;noisy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;@&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;       &lt;span class=&quot;c1&quot;&gt;# 14 / 64
&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;overlap after: &lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sign&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;@&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# 64 / 64
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;One step takes a pattern that’s only loosely related to the target back to a perfect copy. The softmax weights tell the story: about 0.97 on the right memory and crumbs on the rest — exactly what an attention map looks like.&lt;/p&gt;

&lt;blockquote class=&quot;diagram-text&quot;&gt;&lt;p&gt;&lt;strong&gt;Diagram: One modern Hopfield update, step by step&lt;/strong&gt; (&lt;a href=&quot;https://www.avinashgosavi.com/post/paper-tldr-hopfield-networks-is-all-you-need/&quot;&gt;see it in the post&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;A noisy state, the query, starts with 40 percent of its bits flipped and an overlap of 14 out of 64 with stored pattern x3. It is compared against all five stored patterns x1 to x5, which act as the keys. A softmax with sharpness beta turns the similarities into weights: about 0.97 on x3 and crumbs on the rest. The output blends the stored patterns, which act as the values, by those weights. After this single step the new state matches x3 with an overlap of 64 out of 64. This is the same equation as transformer attention with the query set to the state, keys and values set to the stored patterns, and beta equal to one over the square root of d.&lt;/p&gt;&lt;p&gt;&lt;em&gt;The twenty-line demo as a picture: one comparison, one softmax, one blend — and the noisy pattern snaps back to the memory it came from.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;h2 id=&quot;why-i-care-as-someone-building-agent-memory&quot;&gt;Why I care, as someone building agent memory&lt;/h2&gt;

&lt;p&gt;Most agent memory today is “embed everything, return the top-k nearest neighbors” (usually through an index like &lt;a href=&quot;/post/paper-tldr-hnsw/&quot;&gt;HNSW&lt;/a&gt;). This paper reframes that: nearest-neighbor search is a crude, non-differentiable cousin of what attention already does well. It nudged me to build &lt;a href=&quot;/post/attractor-memory-engine/&quot;&gt;Attractor&lt;/a&gt; around retrieval by partial cues and context, and to think of memory as something that &lt;em&gt;settles&lt;/em&gt; rather than something you &lt;em&gt;look up&lt;/em&gt;.&lt;/p&gt;

&lt;h2 id=&quot;caveats-worth-knowing&quot;&gt;Caveats worth knowing&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;“Exponential capacity” is a statement about well-separated patterns in high dimensions; real embeddings are messy and correlated.&lt;/li&gt;
  &lt;li&gt;The one-step convergence holds when β is large enough relative to how similar the stored patterns are — similar memories still blur together.&lt;/li&gt;
  &lt;li&gt;Treating attention as memory is a lens, not a full explanation of what transformers learn.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you read only one modern paper about associative memory, make it this one. Next in the series: &lt;a href=&quot;/post/paper-tldr-complementary-learning-systems/&quot;&gt;Complementary Learning Systems&lt;/a&gt;, the brain’s split between a fast and a slow learner. The rest of what I’m reading is on my &lt;a href=&quot;/research/#papershelf&quot;&gt;papershelf&lt;/a&gt;, and &lt;a href=&quot;/post/paper-tldr-six-papers-on-memory/&quot;&gt;Part 1 of this series&lt;/a&gt; covers the other papers behind my memory projects.&lt;/p&gt;

&lt;p&gt;Terms like &lt;a href=&quot;/post/agent-memory-glossary/#top-k-retrieval&quot;&gt;&lt;em&gt;top-k retrieval&lt;/em&gt;&lt;/a&gt; and &lt;a href=&quot;/post/agent-memory-glossary/#associative-memory&quot;&gt;&lt;em&gt;associative memory&lt;/em&gt;&lt;/a&gt; are defined in plain language in the &lt;a href=&quot;/post/agent-memory-glossary/&quot;&gt;agent memory glossary&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title type="html">Paper TL;DR: Six Papers That Shaped How I Think About AI Memory</title>
    <link href="https://www.avinashgosavi.com/post/paper-tldr-six-papers-on-memory/" rel="alternate" type="text/html" title="Paper TL;DR: Six Papers That Shaped How I Think About AI Memory"/>
    <id>https://www.avinashgosavi.com/post/paper-tldr-six-papers-on-memory/</id>
    <published>2026-10-18T16:00:00+00:00</published>
    <updated>2026-10-27T00:40:07+00:00</updated>
    <category term="Research"/>
    <category term="Agent memory"/>
    <summary type="html">Short, plain-language summaries of six papers behind my AI memory side projects — from Hopfield&apos;s 1982 associative memory to user-owned memory for agents.</summary>
    <content type="html" xml:base="https://www.avinashgosavi.com/post/paper-tldr-six-papers-on-memory/">&lt;p&gt;Most of my side projects over the past year have circled one question: &lt;strong&gt;how should an AI system remember things?&lt;/strong&gt; Not just “where do we put the vectors,” but how memory gets formed, found again, strengthened or forgotten — and who gets to read it.&lt;/p&gt;

&lt;p&gt;I didn’t start with papers. I started building, got stuck, and went looking for how other people had thought about the problem. These six are the ones that actually changed what I built. Here’s each one in a few sentences, in plain language, plus what I took from it. If you only read one, read the first.&lt;/p&gt;

&lt;blockquote class=&quot;diagram-text&quot;&gt;&lt;p&gt;&lt;strong&gt;Diagram: Which of the six papers fed into which project&lt;/strong&gt; (&lt;a href=&quot;https://www.avinashgosavi.com/post/paper-tldr-six-papers-on-memory/&quot;&gt;see it in the post&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;Hopfield 1982, Modern Hopfield networks and Complementary Learning Systems fed into Attractor. Sentence-BERT fed into Attractor, daylog and VaultMem. LoCoMo and Memory as Asset fed into VaultMem.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Where each paper&apos;s takeaway landed, from the notes below. Sentence-BERT is the only one under all three projects.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;h2 id=&quot;1-hopfield-1982--memory-as-a-place-you-roll-into&quot;&gt;1. Hopfield (1982) — memory as a place you roll into&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://www.pnas.org/doi/10.1073/pnas.79.8.2554&quot;&gt;&lt;em&gt;Neural networks and physical systems with emergent collective computational abilities&lt;/em&gt;&lt;/a&gt; · PNAS&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Wire up simple on/off neurons with symmetric connections and the network gets an “energy” that can only go down as it updates. Stored memories sit at the bottoms of valleys in that landscape. Give it a partial or noisy pattern and it slides downhill into the full memory. You don’t look memories up by key — you fall into them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I took from it:&lt;/strong&gt; this was the “oh” moment behind &lt;a href=&quot;/post/attractor-memory-engine/&quot;&gt;Attractor&lt;/a&gt;. Recall from partial cues feels much closer to how I actually remember things than a database lookup does.&lt;/p&gt;

&lt;h2 id=&quot;2-ramsauer-et-al-2020--attention-is-a-hopfield-update&quot;&gt;2. Ramsauer et al. (2020) — attention is a Hopfield update&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://arxiv.org/abs/2008.02217&quot;&gt;&lt;em&gt;Hopfield Networks is All You Need&lt;/em&gt;&lt;/a&gt; · arXiv&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; A “modern” Hopfield network with continuous states can store exponentially many patterns and usually retrieves one in a single step. And that single update step is mathematically the same operation as transformer attention.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I took from it:&lt;/strong&gt; the 1982 idea isn’t a museum piece — it’s sitting inside every LLM. That made me take attractor-style retrieval seriously as a design for agent memory, not just a curiosity.&lt;/p&gt;

&lt;h2 id=&quot;3-mcclelland-mcnaughton--oreilly-1995--two-learners-not-one&quot;&gt;3. McClelland, McNaughton &amp;amp; O’Reilly (1995) — two learners, not one&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://doi.org/10.1037/0033-295X.102.3.419&quot;&gt;&lt;em&gt;Why there are complementary learning systems in the hippocampus and neocortex&lt;/em&gt;&lt;/a&gt; · Psychological Review&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; The brain uses a fast learner that grabs specific episodes (hippocampus) and a slow learner that gradually pulls out general structure (neocortex). Replaying episodes into the slow system lets it learn without overwriting what it already knows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I took from it:&lt;/strong&gt; a vocabulary for something agents get wrong — treating every memory the same forever. It’s why Attractor tracks access and why I think memories should strengthen with use and fade without it.&lt;/p&gt;

&lt;h2 id=&quot;4-reimers--gurevych-2019--embeddings-you-can-actually-search&quot;&gt;4. Reimers &amp;amp; Gurevych (2019) — embeddings you can actually search&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://arxiv.org/abs/1908.10084&quot;&gt;&lt;em&gt;Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks&lt;/em&gt;&lt;/a&gt; · arXiv&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Plain BERT has to compare sentences two at a time, which is painfully slow for search. Training it in a “siamese” setup gives every sentence its own embedding you can compare with cosine similarity, so finding the closest match among thousands takes seconds instead of hours.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I took from it:&lt;/strong&gt; the unglamorous workhorse. The sentence-transformers models behind semantic search in Attractor, VaultMem and &lt;a href=&quot;/post/daylog-security-architecture/&quot;&gt;daylog&lt;/a&gt; all come from this line of work.&lt;/p&gt;

&lt;h2 id=&quot;5-maharana-et-al-2024--testing-memory-honestly&quot;&gt;5. Maharana et al. (2024) — testing memory honestly&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://arxiv.org/abs/2402.17753&quot;&gt;&lt;em&gt;Evaluating Very Long-Term Conversational Memory of LLM Agents&lt;/em&gt; (LoCoMo)&lt;/a&gt; · arXiv&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; A benchmark of very long, multi-session conversations with questions that need single facts, multi-step reasoning, time awareness, or the sense to say “that never happened.” Even strong long-context models and &lt;a href=&quot;/post/agent-memory-glossary/#rag&quot;&gt;RAG&lt;/a&gt; setups struggle with it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I took from it:&lt;/strong&gt; my own little synthetic test was flattering me. Running &lt;a href=&quot;/post/vaultmem-part-2-multimodal-temporal-backends/&quot;&gt;VaultMem against LoCoMo&lt;/a&gt; gave me numbers I could actually trust — and showed me where retrieval was weak.&lt;/p&gt;

&lt;h2 id=&quot;6-pan-huang--yang-2026--whose-memory-is-it&quot;&gt;6. Pan, Huang &amp;amp; Yang (2026) — whose memory is it?&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://arxiv.org/abs/2603.14212&quot;&gt;&lt;em&gt;Memory as Asset: From Agent-centric to Human-centric Memory Management&lt;/em&gt;&lt;/a&gt; · arXiv&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Argues that agent memory today is built for the agent rather than the person, and proposes treating the memory an AI keeps about you as an asset you own, share under your own permissions, and grow over time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I took from it:&lt;/strong&gt; this one reshaped &lt;a href=&quot;/series/#vaultmem&quot;&gt;VaultMem&lt;/a&gt; entirely. It gave words to the feeling that an agent’s memory of your life shouldn’t be readable by whoever runs the agent. VaultMem ended up implementing its first layer.&lt;/p&gt;

&lt;h2 id=&quot;what-ties-them-together&quot;&gt;What ties them together&lt;/h2&gt;

&lt;p&gt;Reading these in a row, I keep landing on the same picture: good memory is &lt;strong&gt;found by meaning and context&lt;/strong&gt;, &lt;strong&gt;changes over time&lt;/strong&gt;, and &lt;strong&gt;belongs to the person it’s about&lt;/strong&gt;. Most systems today nail the first, ignore the second, and get the third backwards.&lt;/p&gt;

&lt;p&gt;All six live on my &lt;a href=&quot;/research/#papershelf&quot;&gt;papershelf&lt;/a&gt; with the rest of what I’m reading. If any of the vocabulary here is new, the &lt;a href=&quot;/post/agent-memory-glossary/&quot;&gt;agent memory glossary&lt;/a&gt; has short definitions. If there’s a paper on memory or agents you think I should read next, I’d genuinely love to hear about it.&lt;/p&gt;

&lt;p&gt;Next in the series: &lt;a href=&quot;/post/paper-tldr-hopfield-networks-is-all-you-need/&quot;&gt;Hopfield Networks Is All You Need&lt;/a&gt;, how modern Hopfield networks store exponentially many memories and why their update rule is transformer attention. Every part is listed on the &lt;a href=&quot;/paper-tldr/&quot;&gt;Paper TL;DR&lt;/a&gt; page.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title type="html">Attractor: Building a Memory Engine Inspired by How the Brain Works</title>
    <link href="https://www.avinashgosavi.com/post/attractor-memory-engine/" rel="alternate" type="text/html" title="Attractor: Building a Memory Engine Inspired by How the Brain Works"/>
    <id>https://www.avinashgosavi.com/post/attractor-memory-engine/</id>
    <published>2026-04-05T00:00:00+00:00</published>
    <updated>2026-10-26T13:51:50+00:00</updated>
    <category term="Agent memory"/>
    <category term="Research"/>
    <summary type="html">Attractor, a memory engine inspired by how brains remember — Hopfield-style retrieval by partial cues and context instead of keys or nearest neighbors.</summary>
    <content type="html" xml:base="https://www.avinashgosavi.com/post/attractor-memory-engine/">&lt;p&gt;I’ve been thinking about memory — not human memory, but how machines store and retrieve it. Most systems today are either key-value stores (fast, exact, dumb) or vector databases (semantic, but still just nearest-neighbour). Neither of them works the way a brain does.&lt;/p&gt;

&lt;p&gt;So I started building something different. I called it &lt;strong&gt;Attractor&lt;/strong&gt;.&lt;/p&gt;

&lt;h2 id=&quot;the-problem-with-how-machines-remember&quot;&gt;The Problem with How Machines Remember&lt;/h2&gt;

&lt;p&gt;When you search a database, you give it a key and it returns a value. When you search a vector database, you give it a query and it returns the closest vectors by distance (usually via an approximate index like &lt;a href=&quot;/post/paper-tldr-hnsw/&quot;&gt;HNSW&lt;/a&gt;). Both are useful. But neither captures something important about how biological memory works — &lt;strong&gt;context&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Think about how you remember things. You don’t retrieve memories by key. You retrieve them by &lt;em&gt;partial cues&lt;/em&gt; — a smell, a phrase, a situation. And the retrieval is shaped by context: what you were doing, who you were with, what problem you were trying to solve. Two memories with identical content mean different things depending on when and why they were formed.&lt;/p&gt;

&lt;p&gt;Machines don’t do this. And for AI agents that need to reason across long conversations or accumulated knowledge, this is a real limitation.&lt;/p&gt;

&lt;h2 id=&quot;hopfield-networks--the-intriguing-part&quot;&gt;Hopfield Networks — The Intriguing Part&lt;/h2&gt;

&lt;p&gt;In 1982, John Hopfield described a type of recurrent neural network that stores memories as &lt;strong&gt;energy minima&lt;/strong&gt; &lt;a href=&quot;#references&quot;&gt;[1]&lt;/a&gt;. The idea is elegant: each memory is a stable state — a basin of attraction — in an energy landscape. When you give the network a partial or noisy input, it doesn’t search for the closest match. It &lt;em&gt;converges&lt;/em&gt; — like a ball rolling downhill — to the nearest stable state.&lt;/p&gt;

&lt;p&gt;This is called &lt;strong&gt;content-addressable memory&lt;/strong&gt;. You don’t need the exact key. You need something close enough to fall into the right basin.&lt;/p&gt;

&lt;blockquote class=&quot;diagram-text&quot;&gt;&lt;p&gt;&lt;strong&gt;Diagram: Energy landscape: memories as basins of attraction&lt;/strong&gt; (&lt;a href=&quot;https://www.avinashgosavi.com/post/attractor-memory-engine/&quot;&gt;see it in the post&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;A qualitative sketch. The vertical axis is energy and the horizontal axis is the space of possible states. The curve has two valleys. Memory A, &quot;switched from JWT to session tokens&quot;, sits at the bottom of the first valley; Memory B, &quot;auth tokens should never be stored in localStorage&quot;, sits at the bottom of the second. A query partway up the slope of the first valley rolls downhill and settles on Memory A, without needing an exact match.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Memories sit at the bottom of basins. A partial cue doesn&apos;t need an exact match: it rolls downhill into the nearest one.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;The name Attractor comes directly from this. In dynamical systems, an attractor is a state that a system converges to over time. Hopfield memories are attractors. Retrieval is the process of finding which attractor your input belongs to.&lt;/p&gt;

&lt;p&gt;What makes this intriguing is that it mirrors something real about biological memory. Your brain isn’t doing a database lookup when you remember something. It’s doing something closer to energy minimisation — pattern completion from partial cues.&lt;/p&gt;

&lt;p&gt;What’s also interesting: in 2020, researchers showed that modern Hopfield networks with continuous states have exponentially higher storage capacity than the original binary version &lt;a href=&quot;#references&quot;&gt;[2]&lt;/a&gt; — and that the attention mechanism in transformers is mathematically equivalent to a single Hopfield update step. The architecture powering every LLM today is, at its core, a Hopfield retrieval operation.&lt;/p&gt;

&lt;h2 id=&quot;what-i-built--phase-1&quot;&gt;What I Built — Phase 1&lt;/h2&gt;

&lt;p&gt;Attractor is a standalone memory service. Right now it’s Phase 1: a working foundation that stores memories with context and retrieves them by meaning.&lt;/p&gt;

&lt;blockquote class=&quot;diagram-text&quot;&gt;&lt;p&gt;&lt;strong&gt;Diagram: Attractor Phase 1 architecture&lt;/strong&gt; (&lt;a href=&quot;https://www.avinashgosavi.com/post/attractor-memory-engine/&quot;&gt;see it in the post&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;A client — memlog, an agent, or any HTTP client — calls a FastAPI service over REST. FastAPI exposes POST /memories, POST /memories/search, GET /memories/:id and DELETE /memories/:id. It uses an encoder (sentence-transformers all-MiniLM-L6-v2, producing a 384-dimensional vector) and PgStore (an asyncpg connection pool for write, get, search and delete). Both lead to PostgreSQL with pgvector, which stores content as TEXT, context and tags as JSONB, vector as vector(384), access_count as INT and created_at as TIMESTAMPTZ. The service runs on port 7747, &quot;loci&quot; on a phone keypad.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Phase 1: a FastAPI service that embeds each memory and stores it, with its context, in Postgres + pgvector.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;The data model&lt;/strong&gt; is built around something I call a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ContextFrame&lt;/code&gt; — the situational state at the time a memory was formed:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ContextFrame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BaseModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;topic&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;      &lt;span class=&quot;c1&quot;&gt;# what was being discussed
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;entities&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;    &lt;span class=&quot;c1&quot;&gt;# who or what was involved
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;       &lt;span class=&quot;c1&quot;&gt;# what was being done
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;session_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# which session it came from
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This isn’t metadata. It’s a retrieval key. When you search, you can filter by context — “find memories from this topic” or “find memories from this session” — and combine that with semantic search over the content itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The storage layer&lt;/strong&gt; uses PostgreSQL with pgvector &lt;a href=&quot;#references&quot;&gt;[3]&lt;/a&gt;. Each memory stores its content, context frame, tags, timestamps, access count, and a 384-dimensional embedding from a sentence-transformers model &lt;a href=&quot;#references&quot;&gt;[4]&lt;/a&gt;. The embedding enables semantic search — retrieving by meaning rather than exact words.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The API&lt;/strong&gt; is simple:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;POST /memories          — store a memory with context
GET  /memories/:id      — retrieve by ID
POST /memories/search   — search by content + context filters
DELETE /memories/:id    — remove a memory
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The CLI&lt;/strong&gt; wraps the service:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;attractor start    &lt;span class=&quot;c&quot;&gt;# starts the service on port 7747&lt;/span&gt;
attractor connect  &lt;span class=&quot;c&quot;&gt;# verifies it&apos;s running&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Port 7747 is “loci” on a phone keypad — a nod to the method of loci, the memory palace technique &lt;a href=&quot;#references&quot;&gt;[5]&lt;/a&gt;, which was the project’s original name before I found a better one.&lt;/p&gt;

&lt;h2 id=&quot;a-concrete-example&quot;&gt;A Concrete Example&lt;/h2&gt;

&lt;p&gt;Here’s what makes it different from a plain vector search. Say you store these two memories:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&quot;auth tokens should never be stored in localStorage&quot;
  context: { topic: &quot;security review&quot;, task: &quot;reviewing PR #42&quot; }

&quot;switched from JWT to session tokens — stateless was causing issues&quot;
  context: { topic: &quot;auth refactor&quot;, task: &quot;architecture decision&quot; }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now you search: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;why did we change the auth approach&quot;&lt;/code&gt; with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;context_filter: { topic: &quot;auth refactor&quot; }&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote class=&quot;diagram-text&quot;&gt;&lt;p&gt;&lt;strong&gt;Diagram: Context filtering: same query, different results&lt;/strong&gt; (&lt;a href=&quot;https://www.avinashgosavi.com/post/attractor-memory-engine/&quot;&gt;see it in the post&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;The query &quot;why did we change the auth approach&quot; is embedded as a 384-dimensional vector. With vector search only, both memories surface: &quot;switched from JWT to session tokens&quot; and &quot;auth tokens should never be stored in localStorage&quot;. Running the same query with the context filter topic equals &quot;auth refactor&quot; surfaces only the session-tokens memory; the localStorage memory, formed during a security review, is filtered out.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Same query, two searches. Vector search alone returns both auth memories; adding the context filter keeps only the one formed during the auth refactor.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;The second memory surfaces — not because the words match, but because:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;The embedding is semantically close to the query&lt;/li&gt;
  &lt;li&gt;The context filter narrows to the right topic&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Without the context filter, both memories are relevant. With it, you get the one that was formed in the right situation. That’s the difference.&lt;/p&gt;

&lt;h2 id=&quot;the-roadmap&quot;&gt;The Roadmap&lt;/h2&gt;

&lt;p&gt;Phase 1 is the foundation. The more interesting work is ahead:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 2 — Association graph&lt;/strong&gt;: Memories aren’t just points in a vector space. They’re nodes in a graph with typed edges — &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;supports&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;contradicts&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;caused_by&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;followed_by&lt;/code&gt;. Retrieval triggers spreading activation: surface the memory you asked for, and the ones connected to it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 3 — Decay and consolidation&lt;/strong&gt;: Not all memories are equally important. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;access_count&lt;/code&gt; field is already tracking how often each memory is retrieved. Phase 3 uses this to implement decay — memories that are never accessed fade, memories that are frequently retrieved strengthen. This mirrors how biological memory consolidation works &lt;a href=&quot;#references&quot;&gt;[6]&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 4 — Hopfield retrieval&lt;/strong&gt;: Replace vector similarity with a proper Hopfield weight matrix. Retrieval becomes pattern completion — give the network a partial cue and it converges to the full memory. This is the step that moves from “semantic search with context” to something genuinely brain-inspired.&lt;/p&gt;

&lt;h2 id=&quot;isnt-this-already-being-done&quot;&gt;Isn’t This Already Being Done?&lt;/h2&gt;

&lt;p&gt;Honestly — yes, in parts. Vector search with metadata filtering exists in Pinecone, Weaviate, and Qdrant. Memory layers for agents exist in LangChain and LlamaIndex. And the “Hopfield Networks is All You Need” paper already showed that transformer attention &lt;em&gt;is mathematically a Hopfield update&lt;/em&gt; — so in a sense, every LLM is already doing content-addressable retrieval implicitly.&lt;/p&gt;

&lt;p&gt;Phase 1 of Attractor is not novel. It’s a clean, focused implementation of something that exists in various forms.&lt;/p&gt;

&lt;p&gt;The bet is on Phases 3 and 4. Most agent memory systems don’t implement decay — memories don’t weaken over time unless reinforced, they don’t consolidate the way biological memory does, and retrieval is always ranking rather than convergence. That’s the gap Attractor is trying to close.&lt;/p&gt;

&lt;p&gt;Whether that gap matters in practice is an open question — and the honest answer is that it depends entirely on whether the difference shows up in real use. That’s why I’m building &lt;strong&gt;memlog&lt;/strong&gt; on top of Attractor as a daily-use testbed: log thoughts, decisions, and notes every day, and see whether context-aware retrieval with decay actually surfaces better results than a plain vector search would. If it does, the model is right. If it doesn’t, the model needs revision.&lt;/p&gt;

&lt;p&gt;Theory is cheap. The feedback loop is the thing.&lt;/p&gt;

&lt;h2 id=&quot;why-this-matters-for-ai-agents&quot;&gt;Why This Matters for AI Agents&lt;/h2&gt;

&lt;p&gt;Most AI agents today are stateless between sessions or rely on crude context windows. The ones that do have memory bolt it on as an afterthought — store the last N messages, embed them, retrieve by cosine similarity.&lt;/p&gt;

&lt;p&gt;Attractor is an attempt to do it properly. Not because the engineering is especially hard, but because the &lt;em&gt;model&lt;/em&gt; of memory matters. If you treat memory as a lookup table, you get lookup-table behaviour. If you treat it as an energy landscape with context and decay, you get something closer to how intelligence actually works.&lt;/p&gt;

&lt;p&gt;Phase 1 is live. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pip install attractor-engine&lt;/code&gt; — start storing memories with context, retrieve by meaning.&lt;/p&gt;

&lt;p&gt;The interesting phases are coming.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Hopfield, J.J. (1982). &lt;a href=&quot;https://www.pnas.org/doi/10.1073/pnas.79.8.2554&quot;&gt;Neural networks and physical systems with emergent collective computational abilities&lt;/a&gt;. &lt;em&gt;Proceedings of the National Academy of Sciences&lt;/em&gt;, 79(8), 2554–2558. Plain-language summary: &lt;a href=&quot;/post/paper-tldr-hopfield-1982/&quot;&gt;Paper TL;DR&lt;/a&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Ramsauer, H., Schäfl, B., Lehner, J., et al. (2020). &lt;a href=&quot;https://arxiv.org/abs/2008.02217&quot;&gt;Hopfield Networks is All You Need&lt;/a&gt;. &lt;em&gt;arXiv:2008.02217&lt;/em&gt;. &lt;em&gt;(Shows modern Hopfield networks have exponential capacity and that transformer attention is a Hopfield update.)&lt;/em&gt; Plain-language summary: &lt;a href=&quot;/post/paper-tldr-hopfield-networks-is-all-you-need/&quot;&gt;Paper TL;DR&lt;/a&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;pgvector. (2021). &lt;a href=&quot;https://github.com/pgvector/pgvector&quot;&gt;Open-source vector similarity search for Postgres&lt;/a&gt;. GitHub.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Reimers, N. &amp;amp; Gurevych, I. (2019). &lt;a href=&quot;https://arxiv.org/abs/1908.10084&quot;&gt;Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks&lt;/a&gt;. &lt;em&gt;arXiv:1908.10084&lt;/em&gt;. Plain-language summary: &lt;a href=&quot;/post/paper-tldr-sentence-bert/&quot;&gt;Paper TL;DR&lt;/a&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Yates, F.A. (1966). &lt;em&gt;The Art of Memory&lt;/em&gt;. University of Chicago Press. &lt;em&gt;(The definitive account of the method of loci — the memory palace technique.)&lt;/em&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;McClelland, J.L., McNaughton, B.L., &amp;amp; O’Reilly, R.C. (1995). &lt;a href=&quot;https://doi.org/10.1037/0033-295X.102.3.419&quot;&gt;Why there are complementary learning systems in the hippocampus and neocortex&lt;/a&gt;. &lt;em&gt;Psychological Review&lt;/em&gt;, 102(3), 419–457. &lt;em&gt;(The foundational paper on memory consolidation and why some memories strengthen while others decay.)&lt;/em&gt; Plain-language summary: &lt;a href=&quot;/post/paper-tldr-complementary-learning-systems/&quot;&gt;Paper TL;DR&lt;/a&gt;.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;em&gt;Source: &lt;a href=&quot;https://github.com/aag1091-alt/attractor&quot;&gt;github.com/aag1091-alt/attractor&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Context frames, consolidation, decay and associative memory are each defined in a sentence or two in the &lt;a href=&quot;/post/agent-memory-glossary/&quot;&gt;agent memory glossary&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title type="html">VaultMem Part 3 — Hiding PII from Your LLM Provider</title>
    <link href="https://www.avinashgosavi.com/post/vaultmem-part-3-pii-sanitizer/" rel="alternate" type="text/html" title="VaultMem Part 3 — Hiding PII from Your LLM Provider"/>
    <id>https://www.avinashgosavi.com/post/vaultmem-part-3-pii-sanitizer/</id>
    <published>2026-04-01T00:00:00+00:00</published>
    <updated>2026-10-22T17:36:32+00:00</updated>
    <category term="Agent memory"/>
    <category term="Privacy"/>
    <summary type="html">VaultMem v0.3: a PII sanitizer that keeps personal details away from the LLM provider — the gap that encryption at rest doesn&apos;t close.</summary>
    <content type="html" xml:base="https://www.avinashgosavi.com/post/vaultmem-part-3-pii-sanitizer/">&lt;p&gt;&lt;a href=&quot;/post/vaultmem-encrypted-memory-for-ai-agents/&quot;&gt;Part 1&lt;/a&gt; covered the core encryption model. &lt;a href=&quot;/post/vaultmem-part-2-multimodal-temporal-backends/&quot;&gt;Part 2&lt;/a&gt; covered multi-modal ingestion and pluggable backends. This post covers v0.3.x — a new layer of privacy that addresses a problem encryption at rest cannot solve.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Install:&lt;/strong&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pip install &apos;vaultmem[spacy]&apos;&lt;/code&gt; · &lt;strong&gt;Code:&lt;/strong&gt; &lt;a href=&quot;https://github.com/aag1091-alt/vaultmem-sdk&quot;&gt;github.com/aag1091-alt/vaultmem-sdk&lt;/a&gt; · &lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href=&quot;https://vaultmem-demo.streamlit.app&quot;&gt;vaultmem-demo.streamlit.app&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-gap-encryption-at-rest-doesnt-close&quot;&gt;The gap encryption at rest doesn’t close&lt;/h2&gt;

&lt;p&gt;VaultMem stores every memory atom encrypted with AES-256-GCM. Your passphrase never leaves your device. The platform operator cryptographically cannot read your vault. The threat model comes from the Memory-as-Asset framework (Pan, Huang &amp;amp; Yang, &lt;a href=&quot;https://arxiv.org/abs/2603.14212&quot;&gt;arXiv:2603.14212&lt;/a&gt;; &lt;a href=&quot;/post/paper-tldr-memory-as-asset/&quot;&gt;plain-language TL;DR&lt;/a&gt;): memory is a personal asset, not a platform resource.&lt;/p&gt;

&lt;p&gt;But there is a second exposure point I hadn’t fully addressed: the moment you decrypt a memory and inject it into an LLM prompt.&lt;/p&gt;

&lt;p&gt;Say your vault contains:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;I met Sarah Chen at the NeurIPS conference. She works on ML infra at Google.
My accountant James Park can be reached at james@parkaccounting.com.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When you ask your assistant “what do I know about Sarah?”, VaultMem retrieves those memories, decrypts them, and sends them as context to the LLM provider — Groq, OpenAI, Anthropic, whoever you’re using. At that point, Sarah’s name, her employer, and James’s email address are all sitting in plaintext in an HTTP request to a third-party cloud.&lt;/p&gt;

&lt;p&gt;Encryption at rest doesn’t help here. The data is decrypted before it goes out the door.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-solution-strip-pii-before-injection-restore-it-after&quot;&gt;The solution: strip PII before injection, restore it after&lt;/h2&gt;

&lt;p&gt;VaultMem v0.3.0 ships a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sanitizer&lt;/code&gt; class that sits between your vault and your LLM call.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vaultmem&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;VaultSession&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Sanitizer&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;san&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Sanitizer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;backend&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;spacy&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;   &lt;span class=&quot;c1&quot;&gt;# one instance per session
&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;VaultSession&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;./my_vault&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;passphrase&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;memories&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;sarah chen&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;top_k&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;- &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;atom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;memories&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Sanitize context AND query with the same instance
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;sanitized_context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rmap&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;san&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sanitize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;sanitized_query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rmap&lt;/span&gt;   &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;san&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sanitize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user_query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;raw_reply&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;llm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;chat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sanitized_query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sanitized_context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Restore real values in the LLM&apos;s response
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;final_reply&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;san&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;restore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;raw_reply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rmap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The LLM provider receives:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;I met Jordan at the NeurIPS conference. She works on ML infra at Globex.
My accountant Casey can be reached at [EMAIL_1].
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It reasons about this correctly, responds naturally, and VaultMem restores the real values client-side before the answer is shown to the user.&lt;/p&gt;

&lt;p&gt;The provider never sees the real names, companies, or contact details.&lt;/p&gt;

&lt;blockquote class=&quot;diagram-text&quot;&gt;&lt;p&gt;&lt;strong&gt;Diagram: How the VaultMem sanitizer round-trips a prompt&lt;/strong&gt; (&lt;a href=&quot;https://www.avinashgosavi.com/post/vaultmem-part-3-pii-sanitizer/&quot;&gt;see it in the post&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;On your device, memories are decrypted from the vault, for example Sarah Chen, who works at Google, and James&apos;s email address. Sanitizer.sanitize swaps them for a pseudonym, a fictional company and a typed token: Jordan, Globex and EMAIL_1, using one mapping for the whole session. Only that sanitized text goes to the LLM provider. The provider&apos;s reply mentions Jordan. On your device again, Sanitizer.restore maps Jordan back to Sarah Chen, so the answer you read uses the real names.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Real names and contact details stay on your device; the provider only ever reasons about Jordan, Globex and [EMAIL_1].&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;pseudonyms-not-tokens&quot;&gt;Pseudonyms, not tokens&lt;/h2&gt;

&lt;p&gt;Person names are replaced with natural-sounding pseudonyms — Jordan, Casey, Morgan, Riley, Avery — rather than opaque tokens like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[PERSON_1]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The reason is fluency. If the LLM sees &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[PERSON_1]&lt;/code&gt; in context and addresses the user with it, the response reads awkwardly. With a pseudonym, it can write “Based on what Jordan told you at NeurIPS…” and after restoration the user reads “Based on what Sarah Chen told you at NeurIPS…” — completely natural.&lt;/p&gt;

&lt;p&gt;Organisations get fictional company names (Acme, Globex, Initech, Pied Piper). Structured PII — emails, phone numbers, SSNs, credit cards, IPs — gets typed tokens (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[EMAIL_1]&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[PHONE_2]&lt;/code&gt;) because there’s no natural-sounding substitute for those.&lt;/p&gt;

&lt;p&gt;The mapping is &lt;strong&gt;session-scoped and stable&lt;/strong&gt;: if “Sarah Chen” appears in five different memories, she is always Jordan in that session. The LLM can reason about her as a single consistent person across all memories.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;two-backends&quot;&gt;Two backends&lt;/h2&gt;

&lt;p&gt;Presidio supports different NLP backends. VaultMem exposes both:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Lightweight — ~12 MB spaCy model, no PyTorch dependency
# Works on Streamlit Cloud, CI, anywhere torch is unavailable
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;san&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Sanitizer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;backend&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;spacy&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# High-accuracy — ~400 MB transformers model, requires PyTorch
# Best for local deployments or privacy-critical production use
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;san&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Sanitizer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;backend&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;transformers&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The spaCy backend (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;en_core_web_sm&lt;/code&gt;) is the default. It handles the common cases — person names, organisations, locations, emails, phone numbers — and is small enough to deploy anywhere without a GPU or a multi-gigabyte dependency install.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# spaCy (default)&lt;/span&gt;
pip &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;vaultmem[spacy]&apos;&lt;/span&gt;
python &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; spacy download en_core_web_sm

&lt;span class=&quot;c&quot;&gt;# transformers (high accuracy)&lt;/span&gt;
pip &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;vaultmem[presidio]&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;what-gets-skipped&quot;&gt;What gets skipped&lt;/h2&gt;

&lt;p&gt;Not everything Presidio detects is PII. Temporal expressions (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DATE_TIME&lt;/code&gt;), cardinal numbers, percentages, monetary values, and quantities are identifying in context but not on their own — and redacting them breaks the coherence of the context significantly.&lt;/p&gt;

&lt;p&gt;VaultMem v0.3.2 introduced a skip list:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;_SKIP_ENTITY_TYPES&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;frozenset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
    &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;DATE_TIME&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;CARDINAL&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;ORDINAL&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;PERCENT&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;MONEY&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;QUANTITY&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;LANGUAGE&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Before this, a memory like “Every Friday at 4pm I do a weekly review — takes 20 minutes” was returned to the LLM as “Every [DATE_TIME_3] at [DATE_TIME_4] I do a weekly review — takes [DATE_TIME_5]”. Correct in theory, unreadable in practice. The skip list fixes this.&lt;/p&gt;

&lt;p&gt;The same release fixed two spaCy-specific label mismatches: spaCy reports organisations as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ORGANIZATION&lt;/code&gt; (not &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ORG&lt;/code&gt;) and geopolitical entities as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GPE&lt;/code&gt; (not &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LOCATION&lt;/code&gt;). Before the fix these fell through to typed tokens instead of receiving pseudonyms from the pools.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;a-subtle-bug-case-insensitive-deduplication&quot;&gt;A subtle bug: case-insensitive deduplication&lt;/h2&gt;

&lt;p&gt;During testing I hit a bug that’s easy to miss until you actually run the system with real queries.&lt;/p&gt;

&lt;p&gt;The memory stored &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;Sarah Chen&quot;&lt;/code&gt; (title case). The user asked &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;who is sarah chen?&quot;&lt;/code&gt; (lowercase). Presidio detected &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;sarah chen&quot;&lt;/code&gt; as a PERSON — but the forward map looked it up with an exact string match. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;sarah chen&quot; != &quot;Sarah Chen&quot;&lt;/code&gt;, so it was treated as a new entity and assigned a fresh pseudonym: Casey.&lt;/p&gt;

&lt;p&gt;The LLM then received context about Jordan but a query about Casey. Its response: &lt;em&gt;“There is no information about Casey.”&lt;/em&gt; Technically accurate. Completely useless.&lt;/p&gt;

&lt;p&gt;The fix in v0.3.3: all forward-map keys are normalised to lowercase. A separate canonical map preserves the first-seen capitalisation so restoration returns &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;Sarah Chen&quot;&lt;/code&gt;, not &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;sarah chen&quot;&lt;/code&gt;. The variant seen first wins on casing; all subsequent variants map to the same pseudonym.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;_assign&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;real&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;entity_type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;real&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;lower&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_forward&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_forward&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;          &lt;span class=&quot;c1&quot;&gt;# &quot;sarah chen&quot; hits &quot;Sarah Chen&quot;&apos;s entry
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_canonical&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;real&lt;/span&gt;            &lt;span class=&quot;c1&quot;&gt;# first-seen casing for restoration
&lt;/span&gt;    &lt;span class=&quot;bp&quot;&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;what-this-doesnt-solve&quot;&gt;What this doesn’t solve&lt;/h2&gt;

&lt;p&gt;The Sanitizer addresses what leaves your device. It doesn’t address what the LLM infers or reconstructs. If your memory says “I grew up two blocks from the Eiffel Tower”, stripping names doesn’t prevent the LLM from inferring something about where you’re from.&lt;/p&gt;

&lt;p&gt;The complete solution is TEE-based inference — running the model inside a trusted execution environment so the provider’s infrastructure never sees plaintext at all. That’s the direction the research is heading (see the arXiv paper above), but it’s not production-ready for general-purpose models yet.&lt;/p&gt;

&lt;p&gt;PII stripping is the practical near-term answer. It’s not perfect, but it meaningfully shrinks the surface area of personal data your LLM provider sees on every query.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;changelog&quot;&gt;Changelog&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;v0.3.0&lt;/strong&gt; — &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sanitizer&lt;/code&gt; class, transformers backend, pseudonym pools, restoration map&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;v0.3.1&lt;/strong&gt; — spaCy backend (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;en_core_web_sm&lt;/code&gt;), new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[spacy]&lt;/code&gt; optional extra, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;backend=&lt;/code&gt; parameter&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;v0.3.2&lt;/strong&gt; — &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DATE_TIME&lt;/code&gt; skip list, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ORGANIZATION&lt;/code&gt;/&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GPE&lt;/code&gt; label fixes for spaCy&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;v0.3.3&lt;/strong&gt; — case-insensitive entity deduplication&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pip install &apos;vaultmem[spacy]&apos;&lt;/code&gt; gets you the latest.&lt;/p&gt;

&lt;p&gt;If you’re building an AI agent that touches personal data, I’d love to hear what you’re working on.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;For short definitions of PII sanitization, zero-knowledge storage and the other terms in this series, see the &lt;a href=&quot;/post/agent-memory-glossary/&quot;&gt;agent memory glossary&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title type="html">Your AI Agent Shouldn&apos;t Be Able to Read Your Diary</title>
    <link href="https://www.avinashgosavi.com/post/vaultmem-your-ai-agent-shouldnt-read-your-diary/" rel="alternate" type="text/html" title="Your AI Agent Shouldn&apos;t Be Able to Read Your Diary"/>
    <id>https://www.avinashgosavi.com/post/vaultmem-your-ai-agent-shouldnt-read-your-diary/</id>
    <published>2026-03-28T00:00:00+00:00</published>
    <updated>2026-10-28T01:36:04+00:00</updated>
    <category term="Agent memory"/>
    <category term="Privacy"/>
    <summary type="html">A plain-language case for AI memory the platform can&apos;t read: if your assistant remembers your life, who else gets to see it?</summary>
    <content type="html" xml:base="https://www.avinashgosavi.com/post/vaultmem-your-ai-agent-shouldnt-read-your-diary/">&lt;p&gt;Imagine you have a personal AI assistant. Over months, you tell it everything — your health worries, your relationship friction, your career anxieties, the habits you’re trying to break. It remembers. It helps.&lt;/p&gt;

&lt;p&gt;Now ask yourself: who else can read that?&lt;/p&gt;

&lt;p&gt;The honest answer, with every AI memory product that exists today, is: the company running it.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-problem-isnt-the-ai-its-where-the-memory-lives&quot;&gt;The problem isn’t the AI. It’s where the memory lives.&lt;/h2&gt;

&lt;p&gt;When you use an AI agent with persistent memory — whether that’s a health companion, a journaling assistant, a personal coach, or a productivity tool — your conversations get stored somewhere. That “somewhere” is a server owned by the platform. And the platform can read it.&lt;/p&gt;

&lt;p&gt;This is true even when there’s a privacy policy. A privacy policy is a promise. It can be broken, subpoenaed, acquired, or quietly changed. A cryptographic guarantee cannot.&lt;/p&gt;

&lt;p&gt;I spent a few weeks building something that fixes this at the foundation:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/aag1091-alt/vaultmem-sdk&quot;&gt;VaultMem&lt;/a&gt;&lt;/strong&gt; — a Python library that gives AI agents encrypted, persistent, portable memory where the platform operator mathematically cannot read what users store.&lt;/p&gt;

&lt;p&gt;Not “we promise not to look.” Not “we anonymize your data.” The key never leaves your session. The math makes it true regardless of what the policy says.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;a-real-world-example-the-health-companion&quot;&gt;A real-world example: the health companion&lt;/h2&gt;

&lt;p&gt;Say you’re building a personal health AI — the kind of agent a person with a chronic condition might use to track symptoms, medications, and how they’re feeling day to day.&lt;/p&gt;

&lt;p&gt;Without VaultMem, your architecture probably looks like this:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;User tells agent “my migraines have been worse since I started the new medication”&lt;/li&gt;
  &lt;li&gt;Agent stores that in a database you control&lt;/li&gt;
  &lt;li&gt;You — the developer — can query that database&lt;/li&gt;
  &lt;li&gt;So can anyone who compromises it, or a legal subpoena, or your future acquirer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With VaultMem, it looks like this:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vaultmem&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;VaultSession&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LocalEmbedder&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;VaultSession&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;./alice_vault&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;passphrase&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;alice_secret&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                        &lt;span class=&quot;n&quot;&gt;embedder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;LocalEmbedder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Migraines worse since starting new medication on March 15th&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Blood pressure 120/80 at checkup — within normal range&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Feeling anxious about the scan results, haven&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;t slept well&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;results&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;recent health concerns&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;top_k&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You — the developer — receive an encrypted blob you cannot open. Alice holds the key. Her memories are hers.&lt;/p&gt;

&lt;blockquote class=&quot;diagram-text&quot;&gt;&lt;p&gt;&lt;strong&gt;Diagram: Who can read an agent&apos;s memory, with and without VaultMem&lt;/strong&gt; (&lt;a href=&quot;https://www.avinashgosavi.com/post/vaultmem-your-ai-agent-shouldnt-read-your-diary/&quot;&gt;see it in the post&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;Typical agent memory: what you tell the agent is stored in the platform&apos;s database, readable by the platform, so the developer, anyone who breaches it, a subpoena or a future acquirer can read every memory. With VaultMem: you hold the key, the VaultSession encrypts in your session, and the platform stores only an encrypted blob, so the developer, a breach, a subpoena or an acquirer see only encrypted bytes.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Same app, same people around it. The difference is where the key lives: with VaultMem it never leaves your session.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;The same principle applies to a therapist AI, a relationship coach, a financial planner, a personal journal. Anywhere the content is deeply personal and the user has good reason not to trust a third party with it.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;what-it-does&quot;&gt;What it does&lt;/h2&gt;

&lt;div style=&quot;text-align:center; margin: 2rem 0;&quot;&gt;
  &lt;picture&gt;
    &lt;source type=&quot;image/webp&quot; sizes=&quot;(max-width: 42rem) 100vw, 42rem&quot; srcset=&quot;/assets/images/vaultmem-explainer-720.webp 720w, /assets/images/vaultmem-explainer-1400.webp 1400w&quot; /&gt;
    &lt;img width=&quot;1400&quot; height=&quot;746&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;/assets/images/vaultmem-explainer.jpg&quot; alt=&quot;VaultMem: your memories stay yours&quot; style=&quot;width:100%; max-width:860px; border-radius:12px; box-shadow: 0 2px 16px rgba(0,0,0,0.08);&quot; /&gt;
  &lt;/picture&gt;
&lt;/div&gt;

&lt;p&gt;The library handles the full memory lifecycle — not just encryption:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Four memory types&lt;/strong&gt; auto-classified from content: &lt;a href=&quot;/post/agent-memory-glossary/#episodic-memory&quot;&gt;episodic&lt;/a&gt; (events), &lt;a href=&quot;/post/agent-memory-glossary/#semantic-memory&quot;&gt;semantic&lt;/a&gt; (facts), &lt;a href=&quot;/post/agent-memory-glossary/#persona-memory&quot;&gt;persona&lt;/a&gt; (preferences), &lt;a href=&quot;/post/agent-memory-glossary/#procedural-memory&quot;&gt;procedural&lt;/a&gt; (how-tos)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Smart retrieval&lt;/strong&gt; that surfaces recurring habits and patterns above one-off mentions — so “my morning coffee” surfaces a pattern summary, not just the last time you mentioned coffee&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Time-aware search&lt;/strong&gt; — ask “what was I doing last summer?” and it searches by when events actually happened, not when you typed them in&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Multi-modal&lt;/strong&gt; — photos, voice notes, PDFs ingested as encrypted memory atoms with EXIF dates and GPS stripped from metadata&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Portable&lt;/strong&gt; — the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.vmem&lt;/code&gt; format is open and self-describing; your vault can move between apps&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Production backends&lt;/strong&gt; — S3 for storage, Postgres for indexing, &lt;a href=&quot;/post/paper-tldr-hnsw/&quot;&gt;HNSW&lt;/a&gt; for fast search at scale&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Three core dependencies. No mandatory cloud service. No phone-home.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;its-early--and-id-love-feedback&quot;&gt;It’s early — and I’d love feedback&lt;/h2&gt;

&lt;p&gt;This is a work in progress. The library is usable and the core is solid, but I’m still building toward the vision.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try the interactive demo&lt;/strong&gt; (no install needed): &lt;a href=&quot;https://vaultmem-demo.streamlit.app&quot;&gt;vaultmem-demo.streamlit.app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It runs a pre-seeded vault with 36 example memories and shows you both sides — what you see (meaningful memories retrieved by your question) and what the platform sees (encrypted bytes that reveal nothing).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Install the SDK:&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;pip &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;vaultmem
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Read the technical writeups:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/post/vaultmem-encrypted-memory-for-ai-agents/&quot;&gt;Part 1 — the encryption model, memory types, and retrieval&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/post/vaultmem-part-2-multimodal-temporal-backends/&quot;&gt;Part 2 — multi-modal, temporal search, and production backends&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/post/vaultmem-part-3-pii-sanitizer/&quot;&gt;Part 3 — hiding PII from your LLM provider&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The research:&lt;/strong&gt; VaultMem implements Layer 1 of the &lt;a href=&quot;https://arxiv.org/abs/2603.14212&quot;&gt;Memory-as-Asset framework&lt;/a&gt; — a formal model for user-owned AI memory (&lt;a href=&quot;/post/paper-tldr-memory-as-asset/&quot;&gt;plain-language TL;DR&lt;/a&gt;). The preprint is on &lt;a href=&quot;https://doi.org/10.5281/zenodo.19154079&quot;&gt;Zenodo&lt;/a&gt;.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;If you’re building an AI agent and want to give your users memory that’s genuinely theirs — or if you have thoughts on where this should go — I’d love to hear from you.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title type="html">VaultMem Part 2 — Multi-Modal Memory, Temporal Search, and Pluggable Backends</title>
    <link href="https://www.avinashgosavi.com/post/vaultmem-part-2-multimodal-temporal-backends/" rel="alternate" type="text/html" title="VaultMem Part 2 — Multi-Modal Memory, Temporal Search, and Pluggable Backends"/>
    <id>https://www.avinashgosavi.com/post/vaultmem-part-2-multimodal-temporal-backends/</id>
    <published>2026-03-28T00:00:00+00:00</published>
    <updated>2026-10-24T12:36:24+00:00</updated>
    <category term="Agent memory"/>
    <category term="Privacy"/>
    <summary type="html">VaultMem v0.2: multi-modal memory, temporal search, pluggable storage backends, and a first evaluation against the LoCoMo benchmark.</summary>
    <content type="html" xml:base="https://www.avinashgosavi.com/post/vaultmem-part-2-multimodal-temporal-backends/">&lt;p&gt;&lt;a href=&quot;/post/vaultmem-encrypted-memory-for-ai-agents/&quot;&gt;Part 1&lt;/a&gt; covered why I built VaultMem, how the encryption works, and the core memory model. This post covers what came next in v0.2.0 and v0.2.1 — the parts that took the library from a solid foundation to something that can handle real-world agent use cases: multi-modal memory ingestion, time-aware retrieval, production-grade storage backends, O(log N) ANN search, and a pluggable query normalizer.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Install:&lt;/strong&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pip install vaultmem&lt;/code&gt; · &lt;strong&gt;Code:&lt;/strong&gt; &lt;a href=&quot;https://github.com/aag1091-alt/vaultmem-sdk&quot;&gt;github.com/aag1091-alt/vaultmem-sdk&lt;/a&gt; · &lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href=&quot;https://vaultmem-demo.streamlit.app&quot;&gt;vaultmem-demo.streamlit.app&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;multi-modal-memory&quot;&gt;Multi-modal memory&lt;/h2&gt;

&lt;p&gt;Text-only memory is a significant limitation for a personal AI agent. The moments that matter most — a photo from a trip, a voice note recorded mid-commute, a scanned medical document — aren’t text.&lt;/p&gt;

&lt;p&gt;VaultMem v0.2.0 added &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;add_media()&lt;/code&gt;, which ingests an image, audio file, PDF, or video and stores it as a fully encrypted memory atom. The raw bytes go into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vault/media/{uuid}.enc&lt;/code&gt; (AES-256-GCM, same key architecture as text atoms). The extracted content becomes the searchable atom.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;VaultSession&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;./vault&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;passphrase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;embedder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;embedder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Image — extracts EXIF date, GPS coordinates, optional OCR
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;atom&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;add_media&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;holiday_photo.jpg&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;passphrase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;atom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;      &lt;span class=&quot;c1&quot;&gt;# &quot;Photo taken 2023-07-14 in Split, Croatia&quot;
&lt;/span&gt;    &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;atom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;captured_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# EXIF timestamp as Unix int
&lt;/span&gt;    &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;atom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;location&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;     &lt;span class=&quot;c1&quot;&gt;# {&quot;lat&quot;: 43.5, &quot;lon&quot;: 16.4, &quot;place&quot;: &quot;Split&quot;}
&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Audio — Whisper transcription
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;atom&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;add_media&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;voice_note.mp3&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;passphrase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# PDF — PyMuPDF text extraction
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;atom&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;add_media&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;contract.pdf&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;passphrase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Retrieve raw bytes later (decrypted on demand, not in search results)
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;raw&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get_media&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;atom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;passphrase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The media bytes are never loaded into RAM during search — only the extracted text atom participates in embedding and retrieval. This keeps the privacy model intact: even if you extract metadata, the raw file stays encrypted behind the passphrase.&lt;/p&gt;

&lt;p&gt;Each extractor is a pluggable Protocol, so adding new file types is straightforward:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vaultmem&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MediaExtractor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MediaExtractionResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MediaIngester&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HeicExtractor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;MIME_TYPES&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;image/heic&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;image/heif&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;extract&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mime_type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MediaExtractionResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;content_type&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mime_type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;transcript&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;captured_at&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...,&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;ingester&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MediaIngester&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;extra_extractors&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;HeicExtractor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Install the extras to activate the built-in extractors:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;pip &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;vaultmem[media]&quot;&lt;/span&gt;   &lt;span class=&quot;c&quot;&gt;# Pillow, piexif, openai-whisper, PyMuPDF, ffmpeg-python&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;temporal-search&quot;&gt;Temporal search&lt;/h2&gt;

&lt;p&gt;The second gap I wanted to close: time. A personal agent should be able to answer “what was I doing in 2019?” or “show me everything from last summer” — not just “what’s most semantically similar to this query?”&lt;/p&gt;

&lt;p&gt;This required two new timestamp fields on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MemoryObject&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;created_at&lt;/code&gt; — when the atom was written to the vault (set automatically, immutable)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;captured_at&lt;/code&gt; — when the real-world event happened (set manually or from EXIF/metadata)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are deliberately separate. A photo taken in 2019 but added to the vault today has &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;captured_at&lt;/code&gt; in 2019 and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;created_at&lt;/code&gt; today. The distinction matters for any use case involving retroactive journaling or importing old media.&lt;/p&gt;

&lt;h3 id=&quot;browse-by-time-window&quot;&gt;Browse by time window&lt;/h3&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;datetime&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;datetime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timezone&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;year&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;month&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;day&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;datetime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;year&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;month&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;day&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tzinfo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;timezone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;utc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;VaultSession&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;./vault&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;passphrase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;embedder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;embedder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;memories_2019&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;search_by_time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2019&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2020&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;q3_2021&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;search_by_time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2021&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2021&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# What was written to the vault in the last 24 hours
&lt;/span&gt;    &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;recent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;diff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;86400&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;natural-language-time-queries&quot;&gt;Natural-language time queries&lt;/h3&gt;

&lt;p&gt;I wanted to avoid the experience of asking “what happened summer 2019?” and getting results from all time. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TimeQueryParser&lt;/code&gt; extracts a timestamp range from the query string, strips the time phrase, and passes the remainder to the embedding — all in pure Python with no dependencies:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vaultmem&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TimeQueryParser&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;from_ts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;to_ts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;remainder&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TimeQueryParser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;what happened summer 2019&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# from_ts  → 2019-06-01 UTC
# to_ts    → 2019-09-01 UTC
# remainder → &quot;what happened&quot;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Supported phrases: season + year, month + year, bare year, “last year”, “this month”, “last N days”, “yesterday”.&lt;/p&gt;

&lt;p&gt;Pass &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;parse_time=True&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;search()&lt;/code&gt; and it handles everything automatically:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Strips &quot;in 2019&quot; → timestamp range; embeds &quot;what did I do&quot;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;results&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;what did I do in 2019&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;top_k&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parse_time&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote class=&quot;diagram-text&quot;&gt;&lt;p&gt;&lt;strong&gt;Diagram: How VaultMem splits a natural-language time query&lt;/strong&gt; (&lt;a href=&quot;https://www.avinashgosavi.com/post/vaultmem-part-2-multimodal-temporal-backends/&quot;&gt;see it in the post&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;The query &quot;what happened summer 2019&quot; goes to TimeQueryParser.parse, which runs in pure Python with no dependencies. It splits the query in two. The time phrase, summer 2019, becomes a timestamp range from 2019-06-01 to 2019-09-01 UTC. The remainder, what happened, is what gets embedded. search with parse_time=True uses both, so the results come from summer 2019 instead of from all time.&lt;/p&gt;&lt;p&gt;&lt;em&gt;The time phrase becomes a timestamp range; only what&apos;s left of the query is embedded.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;pluggable-storage-backends&quot;&gt;Pluggable storage backends&lt;/h2&gt;

&lt;p&gt;The default storage model — two files on the local filesystem — is the right starting point. But a production agent deployment might need S3 for durability, Postgres for searchability, or both. I didn’t want to bake those in as optional flags on the session object; I wanted them to be proper extension points.&lt;/p&gt;

&lt;p&gt;v0.2.0 introduced three ABCs:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;ABC&lt;/th&gt;
      &lt;th&gt;Default&lt;/th&gt;
      &lt;th&gt;Production&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BlobStore&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FileBlobStore&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;S3BlobStore&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SearchIndex&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SQLiteSearchIndex&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PostgresSearchIndex&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VectorIndex&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;— (exact O(N) cosine)&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HNSWVectorIndex&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;All three are injected at session creation:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vaultmem&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;VaultSession&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LocalEmbedder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;S3BlobStore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PostgresSearchIndex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HNSWVectorIndex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;VaultSession&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;./vault&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;passphrase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;embedder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;LocalEmbedder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;blob_store&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;S3BlobStore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bucket&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;my-vault-atoms&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;search_index&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;PostgresSearchIndex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;postgresql://user:pass@host/db&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;vector_index&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;HNSWVectorIndex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;results&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;coffee habits&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;top_k&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The important design decision: the vault format stays the same regardless of which backends you use. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FileBlobStore&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;S3BlobStore&lt;/code&gt; store the same encrypted bytes. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SQLiteSearchIndex&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PostgresSearchIndex&lt;/code&gt; store the same metadata. Migrating between backends is O(N) byte-copies, no decryption required:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vaultmem&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;migrate_vault&lt;/span&gt;

&lt;span class=&quot;nf&quot;&gt;migrate_vault&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;src_blob_store&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;FileBlobStore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;./old&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;dst_blob_store&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;S3BlobStore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bucket&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;new-vault&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;passphrase&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;passphrase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;hnsw-and-the-on-problem&quot;&gt;HNSW and the O(N) problem&lt;/h3&gt;

&lt;p&gt;At small scale, exact cosine search over all atoms in RAM is fine. At 10,000 atoms it becomes the bottleneck — the benchmark showed ~148ms per query, all of it in the embedding comparison loop.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HNSWVectorIndex&lt;/code&gt; wraps &lt;a href=&quot;https://github.com/nmslib/hnswlib&quot;&gt;hnswlib&lt;/a&gt; and plugs into the same &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VectorIndex&lt;/code&gt; ABC. The HNSW graph is built incrementally as atoms are added and serialized encrypted to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vault/vector_index.hnsw.enc&lt;/code&gt; on flush. At session open it’s loaded once into RAM. Search then runs in O(log N):&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Atoms&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Exact cosine&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;HNSW&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;1,000&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;~15ms&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;~0.3ms&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;10,000&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;~148ms&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;~0.8ms&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;100,000&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;~1,500ms&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;~1.2ms&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Below 1,000 atoms, the implementation falls back to exact cosine automatically — the HNSW graph has overhead that isn’t worth paying at small N. (For how the HNSW graph itself works, see the &lt;a href=&quot;/post/paper-tldr-hnsw/&quot;&gt;Paper TL;DR on HNSW&lt;/a&gt;.)&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;pip &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;vaultmem[ann]&quot;&lt;/span&gt;   &lt;span class=&quot;c&quot;&gt;# hnswlib&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;query-normalizer&quot;&gt;Query normalizer&lt;/h2&gt;

&lt;p&gt;A subtler problem surfaced while building the &lt;a href=&quot;https://vaultmem-demo.streamlit.app&quot;&gt;interactive demo&lt;/a&gt;: the question framing users naturally type — “What do I know about Sarah Chen?” — dilutes the embedding. A bag-of-words or hash-projection embedder assigns equal weight to “what”, “do”, “i”, “know”, “about” and “Sarah”, “Chen”. The meaningful terms are swamped by question scaffolding.&lt;/p&gt;

&lt;p&gt;Sentence-transformers handle this natively through attention — they learn that “Sarah Chen” carries the semantic weight in the sentence. But not every deployment uses a 22M-parameter ML model.&lt;/p&gt;

&lt;p&gt;v0.2.1 introduced a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;QueryNormalizer&lt;/code&gt; Protocol:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vaultmem&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;QueryNormalizer&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# Protocol — implement normalize(text) -&amp;gt; str
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The built-in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RegexQueryNormalizer&lt;/code&gt; strips common English question preamble:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vaultmem&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;RegexQueryNormalizer&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# &quot;What do I know about Sarah Chen?&quot; → &quot;Sarah Chen&quot;
# &quot;How does Flash Attention work?&quot;   → &quot;Flash Attention work&quot;
# &quot;Am I vegetarian?&quot;                 → &quot;vegetarian&quot;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;More interesting is the custom path — since it’s a Protocol, any object with a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;normalize(self, text: str) -&amp;gt; str&lt;/code&gt; method works. The interactive demo uses a Groq-backed normalizer with a server-side API key, so users get LLM-quality query extraction without needing their own key:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;GroqQueryNormalizer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;api_key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;groq&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Groq&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_client&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Groq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;api_key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;api_key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;normalize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;resp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;chat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;completions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;llama-3.3-70b-versatile&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;max_tokens&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;messages&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;role&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;system&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                 &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Extract key search terms. Return only keywords.&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;role&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;choices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;strip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The normalizer is injected once at session creation and runs on all searches where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;normalize_query=True&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;VaultSession&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;./vault&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;passphrase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                        &lt;span class=&quot;n&quot;&gt;query_normalizer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;GroqQueryNormalizer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;api_key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
                        &lt;span class=&quot;n&quot;&gt;embedder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;embedder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;results&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;What do I know about Sarah?&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;normalize_query&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote class=&quot;diagram-text&quot;&gt;&lt;p&gt;&lt;strong&gt;Diagram: How VaultMem&apos;s query normalizer strips question framing before search&lt;/strong&gt; (&lt;a href=&quot;https://www.avinashgosavi.com/post/vaultmem-part-2-multimodal-temporal-backends/&quot;&gt;see it in the post&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;The question &quot;What do I know about Sarah Chen?&quot; goes to a QueryNormalizer, any object with normalize(text) returning a string. Two kinds plug in: the built-in RegexQueryNormalizer, which strips common English question preamble, or your own class, such as the demo&apos;s Groq-backed normalizer that asks an LLM to return only keywords. Either way the result is &quot;Sarah Chen&quot;; the question scaffolding &quot;What do I know about&quot; is dropped. search with normalize_query=True then embeds only those key terms.&lt;/p&gt;&lt;p&gt;&lt;em&gt;The normalizer runs before embedding, so the question&apos;s scaffolding words don&apos;t drown out the names that matter.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;data-classes&quot;&gt;Data classes&lt;/h2&gt;

&lt;p&gt;One design detail worth calling out: memory doesn’t all decay at the same rate, and not all memory deserves the same cryptographic protection.&lt;/p&gt;

&lt;p&gt;VaultMem has three data classes, set at vault creation:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Class&lt;/th&gt;
      &lt;th&gt;Use case&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Argon2id memory&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Decay λ&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Half-life&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GENERAL&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Standard&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;64 MiB&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0.005&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;~5 months&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MEDICAL&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Health data&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;128 MiB + mlock&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0.002&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;~12 months&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ARCHIVAL&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Long-term records&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;64 MiB&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0.0007&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;~3 years&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MEDICAL&lt;/code&gt; doubles the Argon2id memory cost, making brute-force twice as expensive. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ARCHIVAL&lt;/code&gt; slows the significance decay so memories stay surfaced for years rather than months — appropriate for journals, legal records, or anything you want to last.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;VaultSession&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;./health_vault&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;passphrase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;owner&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;alice&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data_class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;MEDICAL&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-locomo-benchmark&quot;&gt;The LoCoMo benchmark&lt;/h2&gt;

&lt;p&gt;I wanted a more rigorous evaluation than the 18-atom synthetic benchmark I described in Part 1. The &lt;a href=&quot;https://arxiv.org/abs/2402.17753&quot;&gt;LoCoMo dataset&lt;/a&gt; is a collection of long-form conversational memory — 1,982 QA pairs across five categories (single-hop factual, multi-hop reasoning, temporal, adversarial, open-domain). It’s the closest thing to a realistic test of a personal memory system. (For how the benchmark was built, see the &lt;a href=&quot;/post/paper-tldr-locomo-long-term-memory-benchmark/&quot;&gt;LoCoMo Paper TL;DR&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;VaultMem’s &lt;a href=&quot;/post/agent-memory-glossary/#recall-at-k&quot;&gt;Recall@10&lt;/a&gt; and &lt;a href=&quot;/post/agent-memory-glossary/#mrr&quot;&gt;MRR&lt;/a&gt; scores on a random sample:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Category&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Recall@10&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;MRR&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Single-hop factual&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0.91&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0.87&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Temporal&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0.84&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0.79&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Multi-hop&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0.73&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0.68&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Adversarial&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0.78&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0.71&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Multi-hop is the hardest — it requires reasoning across atoms that weren’t individually asked about, which is a retrieval problem before it’s even an LLM problem.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-interactive-demo&quot;&gt;The interactive demo&lt;/h2&gt;

&lt;p&gt;The fastest way to see all of this in action is the &lt;a href=&quot;https://vaultmem-demo.streamlit.app&quot;&gt;live demo&lt;/a&gt;. It runs a pre-seeded vault with 36 memories across all four types — enough variety to demonstrate the retrieval model without you having to type anything first.&lt;/p&gt;

&lt;p&gt;Every retrieved result shows its relevancy score, memory type, and retrieval tier. The right panel shows the raw encrypted bytes of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;current.vmem&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;current.atoms&lt;/code&gt; — what the platform operator actually sees. Each browser tab gets an isolated vault; your data can’t be seen by or contaminated by other visitors.&lt;/p&gt;

&lt;p&gt;Some things worth trying:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Question&lt;/th&gt;
      &lt;th&gt;What it exercises&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;What do I know about Sarah Chen?&lt;/td&gt;
      &lt;td&gt;Groq query normalizer → EPISODIC retrieval&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;How does Flash Attention work?&lt;/td&gt;
      &lt;td&gt;SEMANTIC retrieval&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;What’s my coffee order?&lt;/td&gt;
      &lt;td&gt;PERSONA retrieval&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;How do I debug problems?&lt;/td&gt;
      &lt;td&gt;PROCEDURAL retrieval&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Tell me about my cat&lt;/td&gt;
      &lt;td&gt;Natural language → EPISODIC&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;whats-next&quot;&gt;What’s next&lt;/h2&gt;

&lt;p&gt;The library is at v0.2.1. The things I’m thinking about for v0.3.0:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Composite and Affinity generation&lt;/strong&gt; — right now the tiers exist in the retrieval model but atoms are only created at ATOM granularity. Automated COMPOSITE and AFFINITY synthesis from accumulated atoms is the next big addition.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Cross-agent memory sharing&lt;/strong&gt; — Layer 2 of the Memory-as-Asset framework. A shared encrypted memory space where multiple agents can read without the platform being able to.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;LoCoMo full benchmark&lt;/strong&gt; — running the complete 1,982-pair dataset rather than a sample.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The paper targeting ACM CCS 2026 is in progress.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/post/vaultmem-part-3-pii-sanitizer/&quot;&gt;VaultMem Part 3 — Hiding PII from Your LLM Provider →&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pip install vaultmem&lt;/code&gt; · &lt;a href=&quot;https://github.com/aag1091-alt/vaultmem-sdk&quot;&gt;GitHub&lt;/a&gt; · &lt;a href=&quot;https://doi.org/10.5281/zenodo.19154079&quot;&gt;Preprint&lt;/a&gt; · &lt;a href=&quot;https://vaultmem-demo.streamlit.app&quot;&gt;Live demo&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</content>
  </entry>
  <entry>
    <title type="html">VaultMem — Building Encrypted Memory for AI Agents</title>
    <link href="https://www.avinashgosavi.com/post/vaultmem-encrypted-memory-for-ai-agents/" rel="alternate" type="text/html" title="VaultMem — Building Encrypted Memory for AI Agents"/>
    <id>https://www.avinashgosavi.com/post/vaultmem-encrypted-memory-for-ai-agents/</id>
    <published>2026-03-21T00:00:00+00:00</published>
    <updated>2026-10-25T00:36:18+00:00</updated>
    <category term="Agent memory"/>
    <category term="Privacy"/>
    <summary type="html">Why I built VaultMem, an encrypted memory library for AI agents where the platform running the agent cryptographically cannot read what users store.</summary>
    <content type="html" xml:base="https://www.avinashgosavi.com/post/vaultmem-encrypted-memory-for-ai-agents/">&lt;p&gt;While building &lt;a href=&quot;/post/building-rena-ai-health-companion-gemini-live/&quot;&gt;Rena&lt;/a&gt; (&lt;a href=&quot;https://github.com/aag1091-alt/rena&quot;&gt;code&lt;/a&gt;) — a voice AI health agent I submitted for the Google Gemini Live Agent Challenge — I needed persistent memory. Something that could build a user profile from conversations across sessions and recall it the next time you speak to the agent. Libraries like mem0 or Zep would have worked fine for that use case.&lt;/p&gt;

&lt;p&gt;But the more I thought about it, the more a different question started bothering me: what if I wanted a &lt;em&gt;truly&lt;/em&gt; private agent — one where the platform running it genuinely cannot read my conversations? And beyond that, how do you store raw conversations as structured memory in the first place?&lt;/p&gt;

&lt;p&gt;I couldn’t find anything that answered both. So I spent a few days building it: a memory library for AI agents where the platform developer cryptographically &lt;strong&gt;cannot read what the user stores&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Install:&lt;/strong&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pip install vaultmem&lt;/code&gt; · &lt;strong&gt;Code:&lt;/strong&gt; &lt;a href=&quot;https://github.com/aag1091-alt/vaultmem-sdk&quot;&gt;github.com/aag1091-alt/vaultmem-sdk&lt;/a&gt; · &lt;strong&gt;Preprint:&lt;/strong&gt; &lt;a href=&quot;https://doi.org/10.5281/zenodo.19154079&quot;&gt;doi.org/10.5281/zenodo.19154079&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-problem&quot;&gt;The problem&lt;/h2&gt;

&lt;p&gt;Every AI memory library I looked at — mem0, Zep, LangMem, Letta — stores user memories in plaintext on the platform’s servers. The platform operator can read everything their users have ever told the agent. This isn’t a privacy policy failure; it’s a structural one. When the key lives with the operator, the guarantee lives with the operator.&lt;/p&gt;

&lt;p&gt;This matters more than it sounds. The deepest use case for persistent AI memory isn’t task management — it’s candor. An agent that remembers across sessions only becomes genuinely useful when you can be completely honest with it. A person working through a difficult decision. Someone with early-stage dementia building a longitudinal record of their life. A user who just wants their preferences remembered without those preferences living in a corporate database.&lt;/p&gt;

&lt;p&gt;For that to work, the privacy guarantee has to be mathematical, not a privacy policy.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;where-the-research-led&quot;&gt;Where the research led&lt;/h2&gt;

&lt;p&gt;While digging into how existing systems handle memory, I stumbled on a paper: &lt;a href=&quot;https://arxiv.org/abs/2603.14212&quot;&gt;Memory as Asset&lt;/a&gt; by Pan, Huang &amp;amp; Yang (2026) (&lt;a href=&quot;/post/paper-tldr-memory-as-asset/&quot;&gt;plain-language TL;DR&lt;/a&gt;). It formalizes something I’d been circling around informally — that personal memory isn’t just data to be stored; it’s an asset that belongs to the user, with ownership, permissions, and portability as first-class concerns.&lt;/p&gt;

&lt;p&gt;The paper defines a layered architecture. Layer 1 is personal storage: encrypted, user-owned, portable. Layer 2 is cross-session intelligence. Layer 3 is collaborative memory between agents. No existing library implements even Layer 1 with real cryptographic guarantees.&lt;/p&gt;

&lt;p&gt;What the paper gave me more than anything was vocabulary. Before reading it, I was thinking vaguely about “storing conversations.” After, I was thinking about episodic memory, semantic memory, significance decay, and granularity hierarchies. That shift changed what I built — instead of a simple key-value store with embeddings, VaultMem ended up with a proper memory model grounded in the framework. The paper became the specification.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;what-vaultmem-is&quot;&gt;What VaultMem is&lt;/h2&gt;

&lt;p&gt;VaultMem is an embeddable Python library. A platform developer adds it as a dependency, and their users get encrypted, portable, private memory — without the developer being able to read it.&lt;/p&gt;

&lt;p&gt;The analogy I keep coming back to: VaultMem is to AI memory what 1Password is to credentials. The library doesn’t know what it’s storing. The platform receives opaque ciphertext it can store, back up, and serve back — but cannot decrypt.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vaultmem&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;VaultSession&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;OllamaEmbedder&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;embedder&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OllamaEmbedder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;http://localhost:11434&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Create an encrypted vault
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;VaultSession&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;./vault&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;passphrase&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;s3cr3t&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;owner&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;alice&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                          &lt;span class=&quot;n&quot;&gt;embedder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;embedder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;I met Bob at the AI conference yesterday&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;I prefer concise bullet-point answers&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;My go-to language for data work is Python&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Search across sessions
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;VaultSession&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;./vault&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;passphrase&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;s3cr3t&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                        &lt;span class=&quot;n&quot;&gt;embedder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;embedder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;results&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Bob conference&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tier&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;] &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;score&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;atom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Output:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[ATOM] 0.847  I met Bob at the AI conference yesterday
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;how-the-encryption-works&quot;&gt;How the encryption works&lt;/h2&gt;

&lt;p&gt;The key architecture has two layers:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;User passphrase
  → Argon2id(passphrase, salt)      ← 64 MiB, 3 iterations
  → KEK (Key Encryption Key)         ← in RAM only; zeroed after open
  → AES-GCM-Dec(KEK, wrapped_MEK)
  → MEK (Master Encryption Key)      ← in RAM during session only
  → AES-256-GCM(memory atom)        ← to disk
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The Master Encryption Key (MEK) is generated once at vault creation and stored only as a ciphertext wrapped with the user’s passphrase-derived key. It never touches disk in plaintext. When the session closes, it’s zeroed from RAM.&lt;/p&gt;

&lt;blockquote class=&quot;diagram-text&quot;&gt;&lt;p&gt;&lt;strong&gt;Diagram: VaultMem&apos;s two-layer key architecture&lt;/strong&gt; (&lt;a href=&quot;https://www.avinashgosavi.com/post/vaultmem-encrypted-memory-for-ai-agents/&quot;&gt;see it in the post&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;The user passphrase goes through Argon2id with a salt (64 MiB, 3 iterations) to derive the KEK, the key encryption key. The KEK lives in RAM only and decrypts the wrapped MEK with AES-GCM. The MEK, the master encryption key, stays in RAM during the session and is zeroed when it closes. The MEK encrypts each memory atom with AES-256-GCM, with a 128-bit authentication tag and the atom&apos;s UUID bound as additional authenticated data. On disk there is only the salt with the wrapped MEK, and the encrypted memory atoms.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Only ciphertext ever reaches disk. Because the MEK is wrapped separately from the data, a passphrase change re-wraps one key instead of re-encrypting every atom.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;A platform developer who integrates VaultMem receives encrypted blobs from their users. They can store them, back them up, sync them — but they cannot read them. The math makes this true regardless of what their privacy policy says.&lt;/p&gt;

&lt;p&gt;Two things I’m particularly happy with:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Passphrase changes are O(1).&lt;/strong&gt; Because the MEK is wrapped separately from the data, changing a passphrase means re-wrapping one key — not re-encrypting every memory atom. A vault with 10,000 atoms changes passphrase in milliseconds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Per-atom tamper detection.&lt;/strong&gt; Each atom is encrypted with AES-256-GCM, which includes a 128-bit authentication tag. Each atom also has its UUID bound as Additional Authenticated Data, so you can’t transplant a valid ciphertext from one slot to another. Any tampering is detected before the data surfaces.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-memory-model&quot;&gt;The memory model&lt;/h2&gt;

&lt;p&gt;The Memory-as-Asset paper defines memory as a 5-tuple: content, context, owner, permissions, versioning state. VaultMem’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MemoryObject&lt;/code&gt; implements this directly.&lt;/p&gt;

&lt;p&gt;Four memory types: &lt;strong&gt;EPISODIC&lt;/strong&gt; (things that happened), &lt;strong&gt;SEMANTIC&lt;/strong&gt; (facts), &lt;strong&gt;PERSONA&lt;/strong&gt; (preferences and habits), &lt;strong&gt;PROCEDURAL&lt;/strong&gt; (how-to knowledge). Type is assigned by a deterministic classifier — no LLM call, no network request.&lt;/p&gt;

&lt;p&gt;The more interesting part is the granularity hierarchy:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;ATOM&lt;/strong&gt; — a single memory statement. Immutable once written.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;COMPOSITE&lt;/strong&gt; — a type-homogeneous grouping of related atoms.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;AFFINITY&lt;/strong&gt; — a derived atom representing a recurring pattern. “User drinks coffee every morning” synthesized from twenty individual coffee mentions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AFFINITY atoms carry a significance score:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;σ = (1 − e^(−κ × freq)) × e^(−λ × days_since_last)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Frequency drives the first factor (with diminishing returns), recency drives the second. The parameters vary by data class — medical memories decay slower than general ones.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;three-tier-retrieval--and-why-it-matters&quot;&gt;Three-tier retrieval — and why it matters&lt;/h2&gt;

&lt;p&gt;The retrieval model searches AFFINITY → COMPOSITE → ATOM and re-ranks results globally. On factual queries this behaves identically to flat cosine search — no regression. On pattern queries it’s a different story.&lt;/p&gt;

&lt;p&gt;I ran a benchmark to verify this. Three topic clusters (coffee habits, Python usage, exercise routine), each with one AFFINITY atom and five ATOM-granularity memories including a deliberately tricky &lt;strong&gt;decoy&lt;/strong&gt;: an episodic memory containing the exact query keywords, designed to fool flat cosine.&lt;/p&gt;

&lt;p&gt;For the query &lt;em&gt;“my morning coffee habits”&lt;/em&gt;, flat cosine ranks the decoy atom first (it contains the words “coffee habits” and “morning”) and the AFFINITY second. Three-tier flips this — the AFFINITY’s significance score (~0.92 for a well-established habit) outweighs the cosine advantage of the decoy.&lt;/p&gt;

&lt;blockquote class=&quot;diagram-text&quot;&gt;&lt;p&gt;&lt;strong&gt;Diagram: VaultMem&apos;s three-tier retrieval&lt;/strong&gt; (&lt;a href=&quot;https://www.avinashgosavi.com/post/vaultmem-encrypted-memory-for-ai-agents/&quot;&gt;see it in the post&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;A query is searched against three tiers in order: AFFINITY atoms, which are derived recurring patterns that carry a significance score; COMPOSITE groupings of related atoms; and individual ATOM statements. All results are then re-ranked globally using cosine similarity plus significance. For the query &quot;my morning coffee habits&quot;, flat cosine ranks a decoy atom first and the AFFINITY second, while three-tier retrieval ranks the AFFINITY first.&lt;/p&gt;&lt;p&gt;&lt;em&gt;The decoy repeats the query&apos;s words, so flat cosine puts it first. The AFFINITY&apos;s significance score lifts the real habit to the top; specific queries rank the same as flat cosine.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Metric&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Flat cosine&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Three-tier&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Δ&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;MRR (pattern queries)&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0.50&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;strong&gt;1.00&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;strong&gt;+0.50&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;MRR (specific queries)&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;1.00&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;1.00&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0.00&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Pattern queries: AFFINITY surfaced first every time. Specific queries: no regression. The significance weight isn’t a heuristic — it’s a correction for a systematic bias in pure cosine retrieval.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-vmem-format&quot;&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.vmem&lt;/code&gt; format&lt;/h2&gt;

&lt;p&gt;Vaults are stored as two files:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;my_vault/
├── current.vmem      # encrypted index (48-byte self-describing header)
└── current.atoms     # append-only encrypted atom blocks
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The 48-byte header is the only plaintext in the file. It carries the magic bytes (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VMEM&lt;/code&gt;), format version, encryption algorithm, KDF, and file type — enough for any conforming implementation to decrypt the vault given the passphrase. No prior coordination with the writing implementation needed.&lt;/p&gt;

&lt;p&gt;Atoms are append-only and immutable. Checkpoints update only the index file, atomically via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fsync&lt;/code&gt; + &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;os.rename()&lt;/code&gt;. Crash-safe.&lt;/p&gt;

&lt;p&gt;The format is designed to be the interoperability layer for a future where encrypted memory vaults move between applications and agents — the same way a PDF can be opened by any conforming reader regardless of which app produced it.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;whats-in-the-library&quot;&gt;What’s in the library&lt;/h2&gt;

&lt;p&gt;About 2,250 lines of Python, 39 unit tests, three core dependencies (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cryptography&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;argon2-cffi&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;numpy&lt;/code&gt;). Sentence-transformers or a local Ollama instance for embeddings — or bring your own.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# LocalEmbedder — no network, runs on CPU
&lt;/span&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vaultmem&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LocalEmbedder&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;embedder&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LocalEmbedder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# OllamaEmbedder — GPU-accelerated, localhost or Tailscale
&lt;/span&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vaultmem&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;OllamaEmbedder&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;embedder&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OllamaEmbedder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;http://localhost:11434&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;all-minilm&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Custom — implement .embed(text) -&amp;gt; list[float]
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MyEmbedder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;embed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;...&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@property&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;dimension&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;384&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-research-angle&quot;&gt;The research angle&lt;/h2&gt;

&lt;p&gt;The work turned into a paper. VaultMem is the first implementation of Layer 1 of the &lt;a href=&quot;https://arxiv.org/abs/2603.14212&quot;&gt;Memory-as-Asset framework&lt;/a&gt; — the paper I stumbled on during the initial research that ended up shaping the entire design. The paper formalizes the threat model, scores ten existing AI memory systems against a four-dimension privacy taxonomy (no prior system achieves all four properties simultaneously), and specifies the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.vmem&lt;/code&gt; format as a portable standard.&lt;/p&gt;

&lt;p&gt;The preprint is live on Zenodo and I’m targeting ACM CCS 2026 for the full paper.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;try-it&quot;&gt;Try it&lt;/h2&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;pip &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;vaultmem                   &lt;span class=&quot;c&quot;&gt;# core&lt;/span&gt;
pip &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;vaultmem[local]&quot;&lt;/span&gt;          &lt;span class=&quot;c&quot;&gt;# + local embeddings (sentence-transformers)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The library is intentionally small — the code is the specification. If you’re building an AI agent and want to give your users memory that’s actually theirs, this is the starting point.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/aag1091-alt/vaultmem-sdk&quot;&gt;github.com/aag1091-alt/vaultmem-sdk&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;strong&gt;Part 2&lt;/strong&gt; covers what was built on top of this foundation in v0.2.0 and v0.2.1: multi-modal memory ingestion, temporal search, pluggable storage backends, HNSW vector indexing (&lt;a href=&quot;/post/paper-tldr-hnsw/&quot;&gt;how HNSW works&lt;/a&gt;), and a query normalizer Protocol — including a live interactive demo.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/post/vaultmem-part-2-multimodal-temporal-backends/&quot;&gt;VaultMem Part 2 — Multi-Modal, Temporal Search, and Pluggable Backends →&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;New to terms like zero-knowledge storage, client-side encryption or episodic memory? The &lt;a href=&quot;/post/agent-memory-glossary/&quot;&gt;agent memory glossary&lt;/a&gt; explains them in plain language. For the non-technical case behind all of this, see &lt;a href=&quot;/post/vaultmem-your-ai-agent-shouldnt-read-your-diary/&quot;&gt;Your AI Agent Shouldn’t Be Able to Read Your Diary&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</content>
  </entry>
</feed>
