<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  <title>Privacy · Avinash Gosavi</title>
  <subtitle>Posts tagged “Privacy” by Avinash Gosavi.</subtitle>
  <id>https://www.avinashgosavi.com/feed/topic/privacy.xml</id>
  <link href="https://www.avinashgosavi.com/feed/topic/privacy.xml" rel="self" type="application/atom+xml"/>
  <link href="https://www.avinashgosavi.com/blog/#privacy" rel="alternate" type="text/html"/>
  <updated>2026-10-24T05:39:10+00:00</updated>
  <author><name>Avinash Gosavi</name><uri>https://www.avinashgosavi.com/</uri></author>
  <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">How daylog Keeps Your Diary Private (Even from Itself)</title>
    <link href="https://www.avinashgosavi.com/post/daylog-security-architecture/" rel="alternate" type="text/html" title="How daylog Keeps Your Diary Private (Even from Itself)"/>
    <id>https://www.avinashgosavi.com/post/daylog-security-architecture/</id>
    <published>2026-04-12T00:00:00+00:00</published>
    <updated>2026-10-23T17:37:26+00:00</updated>
    <category term="Privacy"/>
    <category term="Local-first"/>
    <summary type="html">How daylog, a voice diary, keeps entries and its semantic search index encrypted, with nothing leaving the device to answer a query.</summary>
    <content type="html" xml:base="https://www.avinashgosavi.com/post/daylog-security-architecture/">&lt;p&gt;daylog is a voice diary. You open it, tap the mic, say what’s on your mind, and it saves. You can search your entries semantically — type “frustrated” and it surfaces the day you wrote &lt;em&gt;“couldn’t make progress, everything felt blocked”&lt;/em&gt; even though that exact word never appears.&lt;/p&gt;

&lt;p&gt;That search working at all, while keeping your diary private, requires a few things to be true simultaneously: the content must be encrypted at rest, the search index must also be encrypted, and nothing should leave the device to answer a query. This post explains how each of those holds.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;how-your-words-travel&quot;&gt;How your words travel&lt;/h2&gt;

&lt;p&gt;The diagram traces a single entry from your mouth to disk, and back to your eyes.&lt;/p&gt;

&lt;blockquote class=&quot;diagram-text&quot;&gt;&lt;p&gt;&lt;strong&gt;Diagram: How a daylog entry travels to disk and back&lt;/strong&gt; (&lt;a href=&quot;https://www.avinashgosavi.com/post/daylog-security-architecture/&quot;&gt;see it in the post&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;Saving goes down five layers and reading comes back up the same layers. You speak, for example &quot;went for a run today&quot;. The browser transcribes it on-device, so nothing is sent yet. HTTPS carries it encrypted in transit by TLS. The vault embeds the entry and encrypts text and vector together with AES-256-GCM using your key; when reading, it decrypts with your key only, in memory, not logged. On disk the entry is stored as ciphertext such as 7f3a a4c8 e12d, unreadable. Reading reverses the path: the vault decrypts, HTTPS carries it back, the browser shows plain text and you see it.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Readable only at the top two layers. In transit and on disk it&apos;s ciphertext, and only the vault, with your key, turns it back into words.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;Your words are readable on your device and readable back in your browser. Everything in between — transit, storage — is ciphertext. Notice the saving path says &lt;em&gt;“text + vector encrypted”&lt;/em&gt;: the search index is encrypted too, not just the content. More on that below.&lt;/p&gt;

&lt;hr /&gt;

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

&lt;p&gt;The vault service uses &lt;a href=&quot;/post/vaultmem-encrypted-memory-for-ai-agents/&quot;&gt;vaultmem&lt;/a&gt; — a library I built for giving AI agents a private, searchable long-term memory. In daylog it does three things every time you save an entry:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Classifies the entry automatically.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every entry is run through a fast, rule-based classifier (no LLM involved) that assigns a memory type:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;EPISODIC&lt;/strong&gt; — a time-anchored personal experience. &lt;em&gt;“Yesterday I went for a run.”&lt;/em&gt; Has a temporal marker + first-person reference.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;PERSONA&lt;/strong&gt; — a habitual trait or preference. &lt;em&gt;“I usually start work before 8am.”&lt;/em&gt; Has a frequency word + first-person framing.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;PROCEDURAL&lt;/strong&gt; — a how-to or sequence. &lt;em&gt;“First compile, then run tests.”&lt;/em&gt; Has sequential markers.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;SEMANTIC&lt;/strong&gt; — a timeless fact. Everything else.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most diary entries classify as EPISODIC. The type determines how the memory is weighted and how it ages — episodic memories have a natural recency decay, semantic ones don’t.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Embeds the entry as a 384-dimensional vector.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before the entry is encrypted, it is run through &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sentence-transformers/all-MiniLM-L6-v2&lt;/code&gt; — a 22M parameter model running locally inside the vault container. The model converts your text into a 384-float vector that captures the semantic meaning of what you wrote. (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sentence-transformers&lt;/code&gt; is the library that came out of the &lt;a href=&quot;/post/paper-tldr-sentence-bert/&quot;&gt;Sentence-BERT paper&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“went for a run today”&lt;/em&gt; → &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[0.312, -0.087, 0.441, ..., 0.198]&lt;/code&gt; (384 values)&lt;/p&gt;

&lt;p&gt;This vector is how search finds relevant entries later. It is stored alongside the text.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Encrypts text + vector together and writes to disk.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The entry object — text, classification, timestamp, and embedding vector — is serialised to JSON, compressed with zlib, and encrypted with AES-256-GCM using the master encryption key (MEK). The result is appended to a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.vmem&lt;/code&gt; file on disk as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IV + length + ciphertext + GCM tag&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The embedding is encrypted.&lt;/strong&gt; The search index — the thing that makes semantic search work — is not sitting in a plaintext index somewhere. It is inside the same ciphertext block as your diary entry. Nobody can search your vault without your key.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;how-search-works-end-to-end&quot;&gt;How search works end to end&lt;/h2&gt;

&lt;p&gt;When you type “frustrated” into the search bar:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The vault service embeds your query.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;“frustrated” → &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[0.289, -0.041, 0.509, ...]&lt;/code&gt; (the same 384-dim model, same vector space as your stored entries).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. It decrypts all stored embeddings from disk.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.vmem&lt;/code&gt; entry is decrypted in RAM. The vault service now holds a matrix of shape &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(N entries) × 384&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. It runs a batched dot product.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Since both vectors are unit-normalised, dot product = cosine similarity. One matrix multiply gives a relevance score for every entry simultaneously.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. It filters, ranks, and returns.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Entries with score below 0.3 are dropped. The rest are sorted and the top 8 returned — with their decrypted text.&lt;/p&gt;

&lt;p&gt;The decrypted entries exist only in RAM during this step. They are never written back to disk.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this finds meaning, not just keywords.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Two sentences with similar meaning will have similar vectors even if they share no words. “couldn’t make any progress today, everything felt blocked” scores ~0.62 against “frustrated” because the model has learned that these concepts occupy the same region of the vector space.&lt;/p&gt;

&lt;p&gt;This is the same reason “angry” might surface an entry about a deploy failing at midnight, or “energised” might surface one about a flow state — the words you actually wrote don’t have to match the words you search for.&lt;/p&gt;

&lt;hr /&gt;

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

&lt;p&gt;vaultmem stores entries at three granularity levels:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;ATOM&lt;/strong&gt; — a single diary entry. This is what you write.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;COMPOSITE&lt;/strong&gt; — an auto-generated aggregation of related atoms. A weekly summary, or a cluster of entries about the same topic. Not yet used in daylog but the foundation is there.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;AFFINITY&lt;/strong&gt; — a detected standing pattern. If the vault notices you mention sleep problems every week, it could surface that as a persistent affinity. This is where it starts to feel less like a diary and more like a memory that knows you.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Search queries all three tiers in priority order — AFFINITY first, then COMPOSITE, then ATOM — so a standing pattern surfaces above any individual entry that mentions the same thing.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-one-trade-off-ai-summarise&quot;&gt;The one trade-off: AI summarise&lt;/h2&gt;

&lt;p&gt;When you tap &lt;strong&gt;Summarise this day&lt;/strong&gt;, the browser’s already-decrypted entries for that day are sent to &lt;a href=&quot;https://groq.com&quot;&gt;Groq&lt;/a&gt; (Llama 3.3 70B) via the Rails API. What Groq 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;09:10 AM: Went for a run today.
08:30 PM: Good session, finally fixed that bug.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Time + text for one day. No name, no metadata, nothing from other days. The summary is returned and displayed — it’s never saved back into the vault.&lt;/p&gt;

&lt;p&gt;Everything else — adding entries, browsing, searching — is vault-only. No external API. Summarise is the one feature that trades privacy for convenience, and it’s a button you tap deliberately.&lt;/p&gt;

&lt;p&gt;If that trade-off isn’t right for you, remove &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GROK_API_KEY&lt;/code&gt; from the Rails API config and everything else works unchanged.&lt;/p&gt;

&lt;hr /&gt;

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

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Where&lt;/th&gt;
      &lt;th&gt;What&lt;/th&gt;
      &lt;th&gt;Status&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Fly.io volume (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/data/vaults/&lt;/code&gt;)&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.vmem&lt;/code&gt; — text + embedding vectors, all encrypted&lt;/td&gt;
      &lt;td&gt;AES-256-GCM&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Postgres (Neon)&lt;/td&gt;
      &lt;td&gt;email, bcrypt password hash&lt;/td&gt;
      &lt;td&gt;Passwords hashed; DB encrypted at rest&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Browser sessionStorage&lt;/td&gt;
      &lt;td&gt;session_id, derived passphrase&lt;/td&gt;
      &lt;td&gt;Cleared on tab close&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Browser localStorage&lt;/td&gt;
      &lt;td&gt;auth JWT&lt;/td&gt;
      &lt;td&gt;Cleared on sign out&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Vault service RAM&lt;/td&gt;
      &lt;td&gt;MEK, decrypted atoms during active session&lt;/td&gt;
      &lt;td&gt;Never written to disk&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;hr /&gt;

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

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Voice input&lt;/strong&gt;: transcribed on-device by Apple’s neural engine. Nothing leaves your phone at this step.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Saving&lt;/strong&gt;: entry is classified, embedded, then encrypted. Text and search vector hit disk together, both as ciphertext.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Searching&lt;/strong&gt;: vault decrypts embeddings in RAM, runs cosine similarity, returns matching plaintext. No external API.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Summarising&lt;/strong&gt;: sends that day’s entries to Groq. Optional, explicit, one day at a time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The encrypted search index is the part most people don’t expect. Normally if you want full-text or semantic search, you need a plaintext index somewhere — Elasticsearch, pgvector, whatever. vaultmem stores the vectors encrypted alongside the content, so you get semantic search without giving the index to anyone.&lt;/p&gt;

&lt;hr /&gt;

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

&lt;p&gt;The app is live. You can log in with the demo account and poke around — all entries are pre-seeded across the last two months.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://daylog-ds3.pages.dev&quot;&gt;daylog-ds3.pages.dev&lt;/a&gt;&lt;/strong&gt;&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;email:    demo@daylog.app
password: demo1234
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Things worth trying:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Search&lt;/strong&gt; (bottom nav → Search tab): type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;frustrated&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;flow state&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;couldn&apos;t sleep&lt;/code&gt;, or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;good run&lt;/code&gt; — these find entries by meaning, not exact words&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Summarise&lt;/strong&gt;: navigate to any day that has entries, scroll past the list, tap &lt;strong&gt;Summarize this day&lt;/strong&gt; — it reads everything you logged that day and reflects back themes and mood&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Voice&lt;/strong&gt;: tap the mic, say something, watch the live transcription appear word by word&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;The rest of the stack (hosting, transcription, costs) is in &lt;a href=&quot;/post/hobby-project-stack/&quot;&gt;my hobby project stack&lt;/a&gt;, and the open-source Whisper transcription service I extracted from daylog has &lt;a href=&quot;/post/live-audio-transcription-whisper/&quot;&gt;its own post&lt;/a&gt;. &lt;a href=&quot;/post/agent-memory-glossary/#episodic-memory&quot;&gt;Episodic&lt;/a&gt;, &lt;a href=&quot;/post/agent-memory-glossary/#semantic-memory&quot;&gt;semantic&lt;/a&gt;, &lt;a href=&quot;/post/agent-memory-glossary/#procedural-memory&quot;&gt;procedural&lt;/a&gt; and &lt;a href=&quot;/post/agent-memory-glossary/#persona-memory&quot;&gt;persona&lt;/a&gt; memory, &lt;a href=&quot;/post/agent-memory-glossary/#embedding&quot;&gt;embeddings&lt;/a&gt;, &lt;a href=&quot;/post/agent-memory-glossary/#semantic-search&quot;&gt;semantic search&lt;/a&gt; and &lt;a href=&quot;/post/agent-memory-glossary/#decay&quot;&gt;decay&lt;/a&gt; each get a short definition 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>
