# xmemory vs Cognee

> Cognee turns documents into a knowledge graph and answers over it; its custom graph models are the nearest thing to a schema among memory tools. xmemory owns the schema at the instance and resolves every natural-language write against stored state, so memory behaves as state rather than as a corpus.

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

Cognee is the competitor whose design we respect most on the schema question. Its custom graph
models are Pydantic classes handed to the LLM as the output shape, with identity fields that give
nodes deterministic ids and let a re-added object upsert onto the one already stored. That is a
real step toward typed memory. The difference is where the schema lives and who runs the update.
In Cognee the model configures extraction, and keeping records current is a workflow your
application drives: version, re-add, review conflicts. In xmemory the schema is owned by the
instance, and resolving every natural-language write against stored state is the engine's job.

Documentation reviewed 23 October 2026: Cognee
[overview](https://docs.cognee.ai/core-concepts/overview),
[graph generation](https://docs.cognee.ai/core-concepts/graph-generation),
[custom graph model](https://docs.cognee.ai/guides/custom-graph-model),
[DataPoints](https://docs.cognee.ai/core-concepts/building-blocks/datapoints),
[update](https://docs.cognee.ai/python-api/update),
[improve](https://docs.cognee.ai/core-concepts/main-operations/improve) and the
[Cognee paper](https://arxiv.org/abs/2505.24478); on our side [How xmemory works](https://xmemory.ai/integration-overview/),
[XMD](https://xmemory.ai/xmd/) and the [API](https://xmemory.ai/api/).

## What Cognee does

Cognee is an open-source knowledge engine. `remember` ingests text or files, chunks them, asks an
LLM for entities and relationships, summarises each chunk, and writes the result into three stores:
relational for documents and provenance, vector for embeddings, graph for nodes and edges, with a
long list of swappable backends. `recall` searches the graph and vector stores and, in its
graph-completion mode, turns retrieved triplets into context for an answer. `improve` enriches the
graph and can review conflicting facts across dated sources; `forget` deletes by document, dataset
or user. Version 1.0 added session memory that can be promoted into the permanent graph.

By default the schema emerges from the data: nodes carry `name`, `type` and `description`, edges a
free-text relationship name, and identity is the normalised name. With a custom graph model you
replace that with your own Pydantic `DataPoint` classes, edge names constrained by a `Literal`, and
`identity_fields` for deterministic node ids. Cognee's own [paper](https://arxiv.org/abs/2505.24478)
is about tuning this pipeline for multi-hop QA on HotPotQA, TwoWikiMultiHop and MuSiQue, which
tells you what it is built for: connecting a corpus and answering questions across it.

## Where xmemory is stronger

**The schema is owned by the instance, and the engine runs the mutation.** Cognee's custom model
is the structured-output schema you pass to a `remember` call. Identity fields give nodes
deterministic ids, and re-adding an object with the same identity upserts onto the existing node;
but getting there is application work: mutate the fields, call `update_version()`, re-add. An
[XMD schema](https://xmemory.ai/xmd/) belongs to the instance, not the call, and the resolution is the write path
itself. Every natural-language write, from any source, is extracted against the stored records,
matched by primary key, applied, and kept as lineage. The schema is the same whether you write
over MCP, CLI, REST or an SDK; Cognee's typed `Edge` generics and `FromIdentity` markers work
only through the Python SDK and are lost over HTTP, by their own documentation.

**Corrections are the write path, not a review pass.** In Cognee v1.0, finding facts that
disagree is the opt-in `review_conflicts` stage of
[`improve`](https://docs.cognee.ai/core-concepts/main-operations/improve), after ingestion.
Replacing a source is explicit too: `cognee.update()` swaps a document under its existing id,
while simply remembering a newer, conflicting text adds a second source beside the first.
Conflicting sources can coexist until an enabled conflict-review stage reconciles them. That
review is separate from extraction, even when configured to run automatically after ingestion.
For a corpus that is the right design. For agent state it means "the customer moved to Pro" can
sit next to "the customer is on Basic" until that stage runs and decides. In xmemory the key does
the reconciling inside the write, and "which plan is the customer on" has one answer. This is the
argument of [Schema as the Core of Reliability](https://xmemory.ai/schema-as-the-core-of-reliability/) and the
worked examples on the [Why page](https://xmemory.ai/why/).

**Reads are queries, not graph completion.** Cognee's answer path retrieves triplets and chunks
and has a model compose them. xmemory's answer path translates the question into SQL over typed
records and, if you want, hands you the rows instead of prose. Counts, sums, joins and "which
required facts are missing" are therefore first-class, and the answer is over every matching
record rather than the retrieved ones. When the memory feeds code rather than a chat reply, that is
the capability that matters.

**Schema evolution is one workflow, not several.** Cognee handles an added field per store:
the graph store needs no migration because it has no fixed schema, the default vector store
migrates a collection on the next write (and aborts on a new required field without a default),
stored nodes are not backfilled, and changing identity fields creates duplicates; applying a
changed extraction model to existing source content requires reprocessing that content. xmemory treats the schema as one versioned
artefact: validate a candidate, preview the migration, apply it, with the
[documented limits](https://xmemory.ai/api/#limits) on backfill and key changes stated once, up front. The
[Schema Evolution](https://xmemory.ai/schema-evolution/) post is the long version.

**The measurements.** On our four-domain end-to-end benchmark (updates, deletions, relation
changes, aggregation, negative queries) Cognee scored 86.18 F1, the best of the graph-based
systems we ran and 1.06 points behind Mem0 without graph, against xmemory's 97.10. On the Splitwise task, where
every read needs arithmetic, Cognee reached 68.0% to xmemory's 95.2%
([white paper](https://arxiv.org/abs/2604.27906); numbers also in
[Chasing AI memory SOTA](https://xmemory.ai/chasing-sota-in-ai-memory/)). The paper's
[setup](https://arxiv.org/html/2604.27906v1#S8.SS3): Cognee self-hosted, SDK 1.0.3, Anthropic
Sonnet 4.6 as the LLM, `text-embedding-3-large` for embeddings, temporal and graph-completion
search enabled. These results measure the tested configuration. They show a substantial gap on
these workloads, but do not isolate its cause or establish the best result achievable with a
custom graph model.

## Choosing

Choose Cognee when the job is a corpus: ingest documents, build a graph across them, answer
multi-hop questions, and you want the pipeline in your own Python with your own backends. Choose
xmemory when the job is state: the memory is written continuously by agents, corrected often, read
by code as much as by models, and has to stay consistent without your application driving the
updates. Cognee's custom graph model is a good first draft of the XMD; the difference is that in
xmemory the instance owns it and the engine applies it on every write.

## Cognee's custom graph model as an XMD schema

Cognee's own example is people, groups and roles, with `Role` as a node referenced through
`FromIdentity()`. The XMD below adapts it rather than translating it: the role moves onto a keyed
membership record, which is the habit these pages keep returning to, because a relationship that
carries data should be something you can update.

```yaml
xmd_version: v1
title: People and groups
description: People, the groups they belong to, and the role they hold in each, as current state.
objects:
  Person:
    description: An individual named in the text.
    fields:
      name: { type: str, required: true }
    primary_key: [name]
  Group:
    description: A team, committee or organisation that people belong to.
    fields:
      name: { type: str, required: true }
    primary_key: [name]
  Membership:
    description: >-
      One person's current standing in one group. A stated change of role updates
      the existing record and leaves status unchanged; leaving the group sets status
      to left; only an explicit statement that they rejoined sets it back to active.
    fields:
      person_name: { type: str, required: true }
      group_name: { type: str, required: true }
      role: { type: str, required: false, enum: [member, lead, advisor] }
      status: { type: str, required: true, enum: [active, left], default: active }
    primary_key: [person_name, group_name]
relations:
  member:
    description: Connect a Membership to its Person and Group.
    objects:
      membership: { type: Membership, on_delete: cascade }
      person: { type: Person, on_delete: cascade }
      group: { type: Group, on_delete: cascade }
    keys:
      one_pair_per_membership: [membership]
```

Cognee's `members: list[Person]` field becomes the `member` relation, and the `Role` node becomes
a `role` enum on the membership. "Maya now leads the platform group" matches `(Maya, platform)`
and rewrites `role`; "Maya left the platform group" sets `status` to `left`. The update is
extracted from the new input and applied to the existing record; the original corpus does not
need to be reprocessed. One sequence worth testing before relying on it: a later role change
alone should not flip a `left` membership back to `active`, so the description makes
reactivation an explicit statement rather than a side effect of `default: active`.

## 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).
