# xmemory vs LangMem and the LangGraph Store

> LangGraph's Store is a namespaced JSON document store with semantic search; LangMem adds model-driven extraction and consolidation on top. xmemory is a memory engine with a schema the instance owns, so the merge logic lives in the service rather than in the model's judgement on each turn.

Canonical page: https://xmemory.ai/compare/langmem/

If you build on LangGraph you already have long-term memory primitives: a `Store` that keeps JSON
documents under namespaces and keys with semantic search, and LangMem, which adds tools and
managers that extract memories from conversations and keep them consolidated. We like this stack
and xmemory [integrates with it](https://xmemory.ai/langchain/). The question this page answers is a narrower one:
when the memory has to behave as state, where should the logic that decides what to insert,
update or delete actually live? In LangMem that logic is the model's, prompted well, on every
turn. In xmemory it is a schema the instance owns, executed by the engine.

Documentation reviewed 23 October 2026: LangGraph
[long-term memory](https://docs.langchain.com/oss/python/langgraph/memory), LangMem
[conceptual guide](https://langchain-ai.github.io/langmem/concepts/conceptual_guide/) and
[quickstart](https://langchain-ai.github.io/langmem/); on our side
[How xmemory works](https://xmemory.ai/integration-overview/), [XMD](https://xmemory.ai/xmd/), the [API](https://xmemory.ai/api/) and the
[LangChain integration](https://xmemory.ai/langchain/).

## What LangGraph and LangMem give you

The Store is deliberately simple: `put` a JSON document under a namespace tuple such as
`("acme", user_id, "preferences")` and a key, `get` it back, or `search` a namespace with filters
and a natural-language query ranked by embedding similarity. Namespaces carry tenancy; the
backend is in-memory for development and database-backed in production.

LangMem builds the memory behaviour on top. You describe memories as Pydantic models, and a
*memory manager* runs a model over conversation turns to extract instances, either in the hot path
or in the background. For a **profile** it keeps one document per user and updates it in place.
For a **collection** it keeps many narrow documents and must, in LangChain's own words,
"reconcile new information with previous beliefs", inserting, updating or removing existing items.
`create_manage_memory_tool` and `create_search_memory_tool` let the agent do the same by hand.
Prompt optimisers turn feedback into revised instructions, which LangMem calls procedural memory.

The design is honest about its hard part. The LangGraph memory guide notes that profiles get
error-prone to regenerate as they grow, and that with collections "models may over-insert or
over-update", and recommends evaluation and constrained decoding to manage it.

## Where xmemory is stronger

**The merge logic is a contract, not a prompt.** LangMem's schemas are Pydantic classes: they
give an extraction its shape and field types. Whether a new fact updates an existing document,
creates a sibling or removes an old one is decided by the model each time it runs the manager,
guided by prompts and the `enable_inserts` / `enable_updates` / `enable_deletes` switches. An
[XMD schema](https://xmemory.ai/xmd/) makes that decision structural: a primary key says which record a mention
belongs to, enums say which values a field may take, relation keys say how many of a thing may
exist, and the engine applies those rules on every write, from every writer, before anything is
stored. A declared key removes one particular discretion the guide warns about: whether to create
another record for a fact the extractor has already attributed to an existing key. It does not
make extraction infallible; an omitted or wrongly attributed key still lands in the wrong place,
which is why the [XMD reference](https://xmemory.ai/xmd/#primary-keys-and-identity) is strict about choosing keys
the text reliably states.

**Records, not documents.** A Store value is a JSON document, retrieved by key or by similarity.
A collection of them can be searched, and a profile can be read whole; neither can be joined,
counted or checked for missing values without pulling documents into the model or into your
code. xmemory holds records in typed tables with SQL underneath, so "how many open incidents does
this customer have" and "which accounts have no owner" are queries over all matching stored
records, with rows returned for code. That is the argument of
[Schema as the Core of Reliability](https://xmemory.ai/schema-as-the-core-of-reliability/), and the
[Why page](https://xmemory.ai/why/) shows it on worked examples.

**Shared enforcement is part of the service, not something you assemble.** LangMem's functions
are storage-independent and can be used from any application, so a team can put a memory manager
behind a shared service and route every writer through it. The point is that the team has to:
the manager's prompts, schemas and switches are the application owner's to build, deploy and keep
consistent across writers. In xmemory that enforcement is the instance's contract. The same
resolution runs whether the write comes from a LangGraph node over MCP, a CLI, a batch job or
another team's service, because the rules live with the data rather than with any one caller.

**The schema evolves without re-prompting.** Changing what a LangMem profile holds means changing
the Pydantic model and the extraction behaviour together, then dealing with the documents already
written. xmemory treats the schema as a versioned artefact: validate a candidate, preview the
migration, apply it, with the [limits](https://xmemory.ai/api/#limits) on backfill and key changes stated once.
[Schema Evolution](https://xmemory.ai/schema-evolution/) is the long version.

**On evidence.** We have not run LangMem or a Store-based memory on our benchmarks and will not
quote numbers we do not have. Our four-domain and Splitwise tasks test updates, deletions,
relation changes, aggregation and arithmetic over stored facts
([white paper](https://arxiv.org/abs/2604.27906); method and numbers in
[Chasing AI memory SOTA](https://xmemory.ai/chasing-sota-in-ai-memory/)), which is the territory where a declared
key and a SQL-backed store should matter most. If you evaluate the two side by side, run the
same write sequences through both and count duplicate inserts, incorrect updates, missed updates
and incomplete query answers; then, as a separate design check, confirm that xmemory's keys match
what your workflow means by "the same fact".

## Choosing

Choose the LangGraph Store and LangMem when you want memory behaviour assembled inside your own
stack, the agent itself should decide what to remember, and you are comfortable owning the
extraction prompts, the reconciliation behaviour and the evaluation that keeps them honest. Choose xmemory when the rules
for what a memory is should be declared once and enforced for every writer, when code reads the
memory as state, or when the memory outlives the graph that first wrote it. The integration guide
shows a LangGraph agent doing exactly that.

## A LangMem profile and collection as an XMD schema

LangMem's guide uses a user profile (one document, updated in place) and an episodic collection
(many documents, each a situation, the reasoning, and the outcome). Both map directly; the profile
becomes a keyed object and the episodes become a keyless one.

```yaml
xmd_version: v1
title: Assistant memory
description: What the assistant knows about each user, and the episodes it has learned from.
objects:
  UserProfile:
    description: >-
      The current profile of one user, identified by the user id supplied with the
      input. New information updates this record; there is never a second profile
      for the same user.
    fields:
      user_id: { type: str, required: true, description: "Supplied with the input; do not invent one." }
      preferred_name: { type: str, required: false }
      response_style:
        type: str
        required: false
        enum: [concise, detailed, step_by_step]
        description: >-
          How the user wants answers shaped. "Just give me the short version" is
          concise; "walk me through it" is step_by_step.
      language: { type: str, required: false, description: "BCP 47 tag such as en-GB, when the user states or clearly uses a language." }
    primary_key: [user_id]
  Episode:
    description: >-
      One past interaction worth learning from: the situation, what the assistant
      did and why, and how it went. Each episode is its own record; nothing is
      updated here, only added.
    fields:
      user_id: { type: str, required: true }
      situation: { type: str, required: true, description: "The user's request and relevant context, in two or three sentences." }
      approach: { type: str, required: true, description: "What the assistant did and the reasoning behind it." }
      outcome:
        type: str
        required: true
        enum: [succeeded, partially_succeeded, failed]
        description: Judged from the user's reaction or an explicit statement, not assumed.
      lesson: { type: str, required: false, description: "What to repeat or avoid next time, if the text supports one." }
    primary_key: []
relations:
  episode_of:
    description: Connect an Episode to the UserProfile it belongs to.
    objects:
      episode: { type: Episode, on_delete: cascade }
      profile: { type: UserProfile, on_delete: cascade }
    keys:
      one_profile_per_episode: [episode]
```

The profile pattern is implemented by the key: each supplied user id gets one profile, and
subsequent mentions update it. The collection pattern is `primary_key: []`: every episode is its
own record, which also means replaying the same interaction adds another episode, since nothing
here deduplicates. `response_style` and `outcome` are enums; LangMem's Pydantic schemas can
constrain values too, so that is parity rather than a difference. What is different is that the
identity rule, the relation to the profile and the persistence are one declaration the engine
enforces, instead of a schema, a manager configuration and a store assembled per application. A
LangGraph node writes to this over MCP with the sentence it would have given the memory manager,
and reads it back with a question.

## Why we think this matters, and for whom

xmemory works well for short-running agents, and it is at its best for repeated tasks, where each run adds to a body of knowledge the next run can use. Long-horizon agents deserve a special mention, because there is very little objective evidence yet about what memory design works for them. Our working belief, and the reason xmemory exists: an agent running a long, multi-step task does better on structured state than on accumulated context. A schema gives it an explicit feedback loop at write time (what was accepted, what was rejected, what changed), so memory is updated rather than bloated and the trajectory stays on course: fewer re-reads, faster steps, more reliable outcomes.

If that is your workload, try xmemory on it. We are building objective measurements for this claim. The [Toloka evaluation](https://xmemory.ai/toloka-agent-memory-evaluation/) is our first independent measurement of the benefits of structured memory; evaluating changing working state over long-horizon tasks is next, and we would welcome collaborators on it. Write to [info@xmemory.ai](mailto:info@xmemory.ai).
