I Built a Memory Layer for LLM Agents That Knows Which Facts Go Stale
DEV Community

I Built a Memory Layer for LLM Agents That Knows Which Facts Go Stale

The Core Insight

Think about how different types of facts actually behave over time:

  • Your personality traits - barely change over decades
  • Core preferences (communication style, aesthetic sensibilities) - stable for years
  • Your job - changes every few years
  • What you're currently working on - changes weekly
  • Your mood right now - changes hourly

An LLM memory system that treats all of these with the same protection strength makes systematic errors in a predictable direction: it holds volatile facts too long (stale knowledge), or overwrites stable facts on thin evidence (corrupted knowledge). You can't fix both with one dial. What you need is domain-aware protection - and, at search time, down-ranking of stale volatile memories even when they're semantically close.

The Math (stay with me, it's not that bad)

Protection weight (per domain):

w_d = 1 / V_d^γ

Write / audit decision - escalate (audit + update) when E_t > θ_t:

E_t = [C^α * M_t * R_t] * V_d * G_t
θ_t = V_d * θ_0 * L_t

Where:

  • M_t - how strongly the new observation contradicts what's stored (0 to 1)
  • R_t - how reliable the source is (explicit user statement vs. weak inference)
  • C - how many times this memory has been confirmed (repetition count)
  • α - entrenchment exponent (how hard history fights back)
  • V_d - domain volatility prior (how fast does this type of fact change?)
  • G_t - goal-attainment delta (is updating this memory actually useful?)
  • L_t - cognitive load

High-volatility domain → low threshold → easy to update. Low-volatility domain → high threshold → hard to update.

Retrieval - down-rank stale volatile memories:

score = similarity * (1 - V_d * staleness)

The practical effect: a confident blip like "the user seemed extroverted today" won't overwrite a deeply confirmed "user is introverted" - but "user moved to Paris" will cleanly supersede "user lives in Berlin" because location is a volatile domain where a single explicit statement clears the update bar easily.

On Split-MNIST, this isn't a free-lunch accuracy booster. It's a validated control knob: run the same pipeline with volatility priors shuffled or inverted, and the ordering breaks (REAL > SHUFFLE > SWAP). Pre-arXiv draft: volatility_ewc_portfolio.pdf. Full reproduction: docs/RESEARCH.md.

What it Looks Like in Practice

I compared VoltMem against Mem0 (open-source LLM memory) on three concrete scenarios - a case study, not a leaderboard claim:

Scenario 1: Location update - User says they moved from Berlin to Paris.

Mem0 VoltMem
Result Stale "Berlin" stored, 2 conflicting facts Updated to "Paris", 1 clean fact

Scenario 2: Stable preference blip - User says they "really like short replies" in one session, contradicting an established preference for thorough explanations.

Mem0 VoltMem
Result Adopts the blip Keeps original (resists weak contradicting evidence)

Scenario 3: Volatile mood - User's mood shifts from "great" to "stressed".

Mem0 VoltMem
Result Stale "great" persists Updates to "stressed"

VoltMem: 3/3 current top answer on these scripted scenarios. Challenge the scripts:

python experiments/mem0_side_by_side.py

Retrieval haystack (same chunks, different ranker): cosine-only returns the stale fact first 20% of the time; VoltMem 0% stale@1.

python experiments/retrieval_haystack_bench.py

LongMemEval-S (n=60): 70% answer@5 - ties cosine, does not beat it. If your only metric is public benchmark SOTA, this isn't the pitch. The pitch is update policy + retrieval freshness on mixed-volatility personal memory.

python experiments/longmemeval.py --split s --per-type 10

Using It

pip install voltmem[embeddings]

Core library: zero required dependencies. Embeddings optional (sentence-transformers).

from voltmem import create_memory

mem = create_memory("app.db", user_id="alice")
mem.add("I live in Berlin")
mem.add("I prefer concise, direct answers")
mem.add("Actually I moved to Paris last month")  # updates location, not prefs

hits = mem.search("where does the user live?", limit=3)
print(hits[0]["memory"])  # "Actually I moved to Paris last month"

Inject into any LLM system:

memories = mem.search(user_message, limit=5)
context = "\n".join(f"- {m['memory']}" for m in memories)
system = f"What you know about this user:\n{context}"

Built-in Domain Volatility Priors

VoltMem ships with sensible defaults you can override:

Domain Volatility Behavior
personality_trait 0.05 Strongly protected
core_preference 0.08 Strongly protected
biographical 0.10 High protection
professional_context 0.30 Medium - changes every few years
current_project 0.55 Updates readily
emotional_context 0.80 Fast-moving
current_task 0.90 Minimal protection

Custom domains:

from voltmem import create_memory, DomainRegistry

domains = DomainRegistry()
domains.register("client_relationship", 0.35)
domains.register("active_deal_stage", 0.70)

mem = create_memory("crm.db", user_id="rep_01", domains=domains)

The priors are hand-tuned today - that's an open gap, and one of the places I'd most like real-world feedback.

LangChain Integration

pip install voltmem[langchain]
from voltmem.integrations.langchain import VoltMemMemory

memory = VoltMemMemory(session_id="user-42", db_path="app.db")
memory.load_memory_variables({"input": "Where do I live?"})
memory.save_context({"input": "I moved to Paris"}, {"output": "Noted."})

Where This Came From

This grew out of a philosophical conversation about how human minds handle stale beliefs - when to trust an old habit and when to question it. The observation: animals mostly rely on impulses and simple reinforcement to build routines. Human minds add a monitoring layer on top - but that layer can go wrong when calibrated by social contexts that no longer apply. An old rule, reinforced enough times in a specific environment, can feel like an unquestionable fact even when the environment has fundamentally changed.

That maps almost exactly onto the LLM memory problem. A memory system calibrated by early conversation data can become rigid in the same way - protecting old "truths" that are now stale because they were confirmed enough times in the past. The escalation equations above formalize the same idea: use historical reinforcement as one input, but also factor in domain volatility, source reliability, and actual mismatch - rather than letting any one factor dominate. The continual-learning experiments validated that as a causal control knob; VoltMem is the engineering artifact applied to agent context memory.

What's Next

  • More benchmark scenarios - expanding beyond the 3-scenario Mem0 comparison, including cases where VoltMem loses
  • Smarter domain classification - heuristics and optional Ollama LLM work today, but priors are hand-tuned and new domains still need manual registration; next up is better defaults, cloud LLM support, and inferring domain + volatility from context
  • Async support - sync store today; most production LLM apps are async
  • arXiv preprint - theoretical foundations written up; submission in progress

Try It

pip install voltmem[embeddings]
python examples/contradiction_demo.py
python -m examples.chat_app

If you're building anything with persistent LLM memory, I'd genuinely like to hear how the stale-knowledge problem shows up in practice - the use cases I haven't thought of are usually the most interesting ones. Check out VoltMem on GitHub and leave a Star!

Comments

No comments yet. Start the discussion.