Documents
Home>Documents>AI>Agent

AI Agent Memory Systems in Practice: 2026 Landscape

20 min readApr 30, 2026Apr 30, 2026

Harness Engineering: Mechanical Memory Methods - 2

Intro: "So What Should I Actually Use?"

Part 1 covered the theory.

MemoryOS said hierarchy matters.
AgeMem said you can learn memory policies with RL.
Episodic memory was described as the missing piece.

But open the codebase and reality is waiting for you.

"Got it. But what do I actually use?"

Honestly — through 2025, there was no clear answer. 2026 changed that a little. Real-world data accumulated, serious benchmarks appeared, and people started comparing systems on actual performance instead of GitHub stars.

That's what this post is about.

Less paper, more practice. Some memes too.


1. The 2026 Memory Wars: Three Philosophies

The AI agent memory market currently has three camps. Each holds a fundamentally different view of what memory even is.

┌──────────────────────────────────────────────────────┐
│               2026 Memory Wars                       │
│                                                      │
│  Mem0          Letta            Zep                  │
│  "Remember      "LLM manages     "Time is part       │
│   the facts"     it directly"     of memory too"     │
│                                                      │
│  Vector+Graph    OS-layer arch    Bi-temporal KG      │
└──────────────────────────────────────────────────────┘

1.1 Mem0: "Remember the User"

GitHub: mem0ai/mem0 — ⭐ 48,000+, Series A $24M

Mem0's philosophy is simple: extract, update, and retrieve user facts and preferences.

Internally it uses a graph + vector hybrid. When a user says "I'm vegetarian," Mem0 extracts this as a structured fact and writes it to the graph. When a food-related question comes in later, it pulls the fact and injects it into context.

from mem0 import Memory

m = Memory()

# Auto-extract and store facts from conversation
m.add("I'm vegetarian and I live in Mapo-gu, Seoul", user_id="alice")

# Auto-retrieved on relevant queries
results = m.search("Recommend something for lunch", user_id="alice")
# → vegetarian + Mapo options are automatically factored in

Benchmark: 67% on LoCoMo (~10% improvement over RAG). Worth noting this is a Mem0-published number.

Best for: Consumer apps that need to remember a user — chatbots, personalized assistants. The most widely deployed memory layer as of 2026.


1.2 Letta: "The LLM Edits Memory Directly"

GitHub: letta-ai/letta — Production framework from the MemGPT team

If Part 1's MemoryOS paper proposed the idea of managing memory like an OS, Letta is that idea built into an actual framework.

┌─────────────────────────────────┐
│         core_memory             │ ← Always in context
│   (persona definition +         │   LLM reads/writes directly
│    user profile)                │
└───────────────┬─────────────────┘
                │
┌───────────────▼─────────────────┐
│         recall_memory           │ ← Recent conversation DB (searchable)
│       (conversation history)    │
└───────────────┬─────────────────┘
                │
┌───────────────▼─────────────────┐
│        archival_memory          │ ← Unlimited long-term storage
│    (vector DB, external         │   No capacity limit
│     storage)                    │
└─────────────────────────────────┘

Letta's differentiator: the agent calls memory as a tool directly. Tools like core_memory_append and core_memory_replace are given to the LLM, which then uses in-context reasoning to decide "this is important, I should put it in core memory." No hardcoded rules — it delegates the judgment to the LLM.

There's one interesting result in Letta's published benchmarks.

A plain filesystem scored 74%.

Benchmark results (Letta):
- Plain filesystem:           74%  ← what
- Specialized vector memory A: 61%
- Specialized vector memory B: 58%

A filesystem isn't better in every dimension — it doesn't scale and it has no structure. But it's a useful reminder that on simple tasks, an overengineered system can lose to a simple one.

"Make it simple first. Make it sophisticated second." — Memory is no exception.

Best for: Autonomous agents with long-running sessions. Cases where the agent needs to manage and evolve its own memory.


1.3 Zep/Graphiti: "Time Is Part of Memory"

GitHub: getzep/graphiti

Zep takes it one step further: memory has a time dimension.

Instead of storing "Alice is vegetarian," it stores "Alice has been vegetarian since March 2025" — temporal information is first-class.

This is implemented as Graphiti, a bi-temporal knowledge graph. It tracks two separate timelines simultaneously:

  • Transaction time: when the system recorded the information
  • Valid time: the period during which the information is true in the real world
# Graphiti bi-temporal fact management
{
    "entity": "Alice",
    "attribute": "residence",
    "value": "Seoul",
    "valid_from": "2024-01-01",
    "valid_until": "2026-03-01",   # moved to Busan at this point
    "recorded_at": "2024-01-15"    # when the system recorded it
}

{
    "entity": "Alice",
    "attribute": "residence",
    "value": "Busan",
    "valid_from": "2026-03-01",
    "valid_until": None,           # still valid
    "recorded_at": "2026-03-02"
}

At retrieval time, only the currently valid facts are returned. The Seoul record is automatically treated as historical. You maintain "current state" without deleting anything.

Benchmark: 71.2% on LongMemEval (GPT-4o)

Best for: Domains where user state changes over time. Healthcare, legal, project management — anywhere that "what changed and when" is business-critical.


1.4 So Which One Do I Use?

Mem0LettaZep/Graphiti
Core philosophyFact/preference memoryLLM-autonomous managementTemporal knowledge graph
Best forConsumer apps, chatbotsAutonomous agentsEnterprise, changing state
DeploymentCloud APISelf-hostedCloud / self-hosted
Benchmark67% (LoCoMo)~83% (third-party)71.2% (LongMemEval)
StrengthFast onboardingLLM delegationTemporal tracking

All three are well-built. And all three are good at different things.

  • "Just building a chatbot?" → Mem0
  • "Building an autonomous agent?" → Letta
  • "State changes are critical in your domain?" → Zep

2. MAGMA: Four Graphs Simultaneously

The systems above mostly use a single storage structure — vector DB or knowledge graph.

MAGMA (arXiv:2601.03236) takes a different view: memory has multiple types of relationships coexisting, and the relationships that matter depend on the query.

2.1 Four Orthogonal Graphs

MAGMA indexes every memory item simultaneously across four separate graphs:

One memory item → represented across 4 graphs at once

┌─────────────────┐   ┌─────────────────┐
│  Semantic Graph │   │  Temporal Graph  │
│  (semantic      │   │  (chronological  │
│   similarity)   │   │   ordering)      │
└─────────────────┘   └─────────────────┘

┌─────────────────┐   ┌─────────────────┐
│  Causal Graph   │   │  Entity Graph   │
│  (causal        │   │  (entity        │
│   relations)    │   │   relations)    │
└─────────────────┘   └─────────────────┘

Example: "In today's meeting, Manager Kim extended Project A's deadline by two weeks."

  • Semantic: linked to other memories about project deadlines
  • Temporal: linked chronologically to previous and subsequent meeting records
  • Causal: linked to downstream changes caused by this decision (team schedule adjustments, customer notifications, etc.)
  • Entity: linked to all memories involving "Manager Kim" and "Project A"

2.2 Graph Selection Is Query-Driven

When a query arrives, MAGMA uses a policy to decide which graphs to traverse:

# MAGMA retrieval (conceptual structure)
def retrieve(query: str) -> list[Memory]:
    intent = classify_query_intent(query)
    # "When?" → prioritize temporal graph
    # "Why?"  → prioritize causal graph
    # "Who?"  → prioritize entity graph
    # "What's similar?" → prioritize semantic graph

    subgraphs = []
    for graph_type in intent.relevant_graphs:
        subgraph = graphs[graph_type].traverse(
            query=query,
            policy=intent.traversal_policy
        )
        subgraphs.append(subgraph)

    # Merge multi-graph results
    return fuse_subgraphs(subgraphs)

2.3 Dual-Stream Memory Updates

MAGMA separates memory updates into two streams:

  • Fast stream: new events are written immediately (minimal latency)
  • Slow stream: graph structure is cleaned up asynchronously (relationship reorganization, deduplication)

Results: improvements over prior SOTA on both LoCoMo and LongMemEval, with simultaneous reductions in retrieval latency and token usage.

If you want to go deeper on graph-based memory, Awesome-GraphMemory is a well-organized reference.


3. SimpleMem: "Why Is It This Fast?"

There's something that gets overlooked in memory system discussions: speed.

If an agent updates memory on every response, a slow update blocks the entire pipeline. The actual numbers are striking.

SystemMemory construction time (per sample)
A-Mem5,140 s
Mem01,351 s
SimpleMem93 s

While A-Mem processes one sample, SimpleMem processes 55.

SimpleMem (arXiv:2601.02553) attacks this speed problem head-on.

3.1 Core Idea: Semantic Density Gating

SimpleMem's core principle is simple: only remember what matters. What's different is that this is integrated directly into the LLM generation process.

Semantic Density Gating: reads the conversation stream with a sliding window and evaluates the "information density" of each window. Low density → discard.

Sliding window over the conversation stream:

["Hey~ How have you been?"]                  → low density  → discard ✗
["I was actually diagnosed with diabetes"]    → high density → keep   ✓
["Nice weather today lol"]                   → low density  → discard ✗
["I got prescribed metformin"]               → high density → keep   ✓

3.2 Three-Stage Pipeline

1. Semantic Structured Compression
   Compress high-density interactions into multi-view indexed memory units

2. Online Semantic Synthesis
   Integrate related context within the session immediately, deduplicate

3. Adaptive Retrieval
   Retrieve memory units contextually matched to the query

3.3 Performance

SystemF1 (Qwen3-8b)Speed
A-Mem~305,140 s/sample
Mem025.801,351 s/sample
**SimpleMem33.4593 s/sample**

Better performance, 14–55× faster. And it's open source.

Bonus: Omni-SimpleMem handles not just text but also images, audio, and video — one of the first frameworks to practically address memory for multimodal agents.


4. Forgetting Is a Feature

Wait — is more memory, retained longer, always better?

No.

MemoryAgentBench (ICLR 2026) defines four core capabilities for evaluating memory agents:

  1. Accurate Retrieval: Does the agent fetch exactly the information it needs?
  2. Test-time Learning: Does it incorporate new information immediately?
  3. Long-range Understanding: Does it make use of context from far back in the conversation?
  4. Selective Forgetting: Does it discard information that is no longer valid?

Number 4 is the focus of this section.

4.1 An Agent That Remembers vs. an Agent That Remembers Correctly

An example makes this immediate:

[January 2025]  User: "I live in Gangnam, Seoul."
                      → Memory: residence = Gangnam (stored)

[March 2026]    User: "I moved to Busan!"
                      → Memory: residence = Busan (should be updated)

[April 2026]    Agent recommends restaurants in Gangnam?

A more serious case: medical information, occupation, relationship status, or financial situation changes — and the agent keeps acting on the old data.

This isn't just a bug. It's a trust problem.

4.2 Reality: What Current Agents Are Worst At

Common failure patterns from MemoryAgentBench results:

Performance by capability (current agents):

Accurate Retrieval        ████████░░  (pretty good)
Test-time Learning        ██████░░░░  (decent)
Long-range Understanding  ████░░░░░░  (lacking)
Selective Forgetting      ██░░░░░░░░  (genuinely bad)

Adding memories is easy. Removing them is hard.

To measure this directly, MemoryAgentBench introduces a new dataset called FactConsolidation. It measures whether an agent correctly maintains the latest information when conflicting facts arrive at different points in time.

4.3 Implementing Selective Forgetting

This is where Zep's bi-temporal model shines. The valid_until timestamp lets you manage "no-longer-valid facts" without any deletes.

# Zep-style: update a fact (invalidate without deleting)
graphiti.update_fact(
    entity="user_kim_cheolsu",
    attribute="residence",
    old_value="Seoul",
    new_value="Busan",
    valid_from="2026-03-01"  # Busan is valid from this point forward
)
# → Seoul is now treated as a past residence only
# → Queries return only the currently valid value, "Busan"

# Minimum requirement for a simple implementation: upsert, not append
memory_store.upsert(
    key="user_alice_residence",
    value="Busan",
    updated_at=now()
    # Same key overwrites the old value — this alone fixes a lot of problems
)

The key is upsert. When a new fact arrives, it should overwrite the old one. Systems that only append are the primary cause of selective forgetting failures.


5. MemoryAgentBench: Actually Measuring This

There's no shortage of claims that a memory system "works well." But how do you measure that?

MemoryAgentBench (ICLR 2026) is a systematic attempt to answer that question.

5.1 The Problem with Existing Benchmarks

Existing long-context benchmarks share a common flaw: they hand the model a long document all at once and ask whether it understood it. Real agents accumulate information incrementally through conversation. That difference matters.

MemoryAgentBench reflects this. Information arrives in small pieces across many turns. The agent must integrate it in real time.

Turn  1: "I have a team meeting tomorrow."
Turn  7: "Oh, it's at 3 PM."
Turn 12: "I need to prepare materials for that meeting..."
Turn 18: "Actually, that meeting got cancelled."
Turn 24: "What time did you say the meeting was?"

→ The agent should answer: "It was cancelled."
→ Most current agents answer: "3 PM." ✗

5.2 Two New Datasets

  • EventQA: Measures Accurate Retrieval. How precisely can the agent retrieve details about a specific event?
  • FactConsolidation: Measures Selective Forgetting. How correctly does the agent reflect updated facts?

The results are sobering. Systems that advertise long-term memory still drop off noticeably on FactConsolidation. Without a benchmark like this, these failures would go unnoticed.


6. A Practical Decision Guide

If you're reading this because you need to build something:

What kind of agent am I building?
│
├─ Personalized chatbot / user memory is the core feature
│   └─ → Mem0 (fast to get started, cloud API)
│
├─ Domain where user state changes over time
│   (healthcare, CRM, legal, complex project management)
│   └─ → Zep/Graphiti (temporal tracking)
│
├─ Autonomous agent, long-running sessions
│   (the agent needs to manage its own memory)
│   └─ → Letta (LLM directly edits memory)
│
├─ Real-time system requiring fast memory updates
│   └─ → SimpleMem (14–55× faster, open source)
│
└─ Heavy relational queries ("why?", "when?", "who?")
    └─ → MAGMA (parallel search across 4 graphs)

If you're considering a custom implementation, at minimum this structure is worth having:

class MinimalProductionMemory:
    """
    Before over-engineering this,
    the following is often enough.
    (Don't forget the lesson: a filesystem hit 74%.)
    """
    def __init__(self, user_id: str):
        self.user_id = user_id
        self.facts = FactStore(user_id)       # structured facts
        self.recents = RecentContext(n=20)    # recent conversation summary
        self.episodes = EpisodeStore(user_id) # significant events + context

    def inject_to_context(self, query: str) -> str:
        """Inject only what's needed, only what's relevant to this query."""
        return format_memory_block(
            facts=self.facts.search(query, top_k=5),
            episodes=self.episodes.recall(query, top_k=3),
            recent=self.recents.get_summary()
        )

    def update_after_turn(self, turn: dict):
        """Update selectively — only what needs updating."""
        if has_new_fact(turn):
            # upsert, not append — overwrite the existing entry
            self.facts.upsert(extract_facts(turn))

        if is_significant_episode(turn):
            self.episodes.save(turn)

        self.recents.append(turn)

Three-line summary:

  1. Don't store everything.
  2. Don't put everything in the context.
  3. Use upsert, not append.

Closing: The Real Frontier of Memory Research

At the end of Part 1, I left off with this question:

"Will my agent remember today, tomorrow?"

Wrapping up Part 2, here's one more to add:

"Will my agent forget what's wrong?"

Forgetting well matters just as much as remembering well. And it's the one thing current memory systems are worst at.

This is where memory research in 2026 is actually contested:

  • Mem0, Zep, and Letta have made real progress on storage and retrieval.
  • MAGMA showed how to search across multiple relationship types simultaneously.
  • SimpleMem solved the speed and efficiency problem.
  • But MemoryAgentBench says: "Selective forgetting is still unsolved."

Maybe the most human-like memory capability turns out to be the hardest engineering problem. After all, forgetting is what people find hardest too.


References

Tags
AgentLLMMemoryAgentHarness EngineeringMem0LettaMAGMA