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
- GitHub: https://github.com/Rouche01/voltmem
- PyPI: https://pypi.org/project/voltmem/
- Research & repro: https://github.com/Rouche01/voltmem/blob/main/docs/RESEARCH.md
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.