> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cognee.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Memory Versioning and History

> How Cognee versions memories for AI agents: version counters and timestamps on every node, superseded facts that stay in the graph, and an opt-in audit ledger with a full revision history.

Cognee keeps the **current state** of memory and records how it changed. Every node carries a `version` counter and `created_at` / `updated_at` timestamps. Conflict review keeps outdated facts in the graph and marks them `superseded` instead of deleting them. An opt-in provenance ledger records when and from which source each entity was written. Cognee does not keep old copies of nodes or documents, so to roll back to an earlier state, export a snapshot first.

## What Cognee keeps

| Layer | What is kept | How you read it | On by default |
| - | - | - | - |
| Every node | `version` (starts at 1), `created_at`, `updated_at` | The node's properties | Yes |
| Every fact `cognify()` extracts | Which document, chunk and pipeline run produced it | The `provenance_edge_evidence` table in the relational database | Yes |
| Conflicting facts | Old and new facts side by side, marked `current`, `superseded` or `conflicting`, with an as-of date | Labels in `recall()` context, `FactConflict` nodes | No, run `improve(review_conflicts=True)` |
| Entity revisions | A record of each write `cognify()` makes, with source, `valid_from` and `valid_until`, but not its property values beyond the name | `revision_history()` on the provenance ledger | No, set `PROVENANCE_TRACKING=true` |
| Operations | One record per `remember`, `recall`, `forget` and other operation, with user, outcome and timestamps | `GET /api/v1/activity/pipeline-runs` | Yes |

## Version a memory you write yourself

A [DataPoint](/core-concepts/building-blocks/datapoints) has a `version` field that starts at 1. Cognee never increments it on its own. Call `update_version()` when a change should count as a new revision, then write the DataPoint back. `update_version()` adds 1 to `version` and sets `updated_at` to now.

Writing a DataPoint with the same id updates the stored node in place. Here `identity_fields` derives the id from `user` and `key`, so a new value for the same preference updates the same node:

```python theme={null}
import asyncio

from cognee.infrastructure.databases.graph import get_graph_engine
from cognee.low_level import DataPoint, setup
from cognee.tasks.storage import add_data_points


class Preference(DataPoint):
    user: str
    key: str
    value: str
    # Same (user, key) gives the same node id, so a rewrite updates one node.
    metadata: dict = {"index_fields": [], "identity_fields": ["user", "key"]}


async def main():
    await setup()
    graph = await get_graph_engine()

    pref = Preference(user="alice", key="editor", value="vim")
    await add_data_points([pref], graph_only=True)

    stored = await graph.get_node(str(pref.id))
    print("v1:", stored["value"], "version", stored["version"])

    pref.value = "helix"
    pref.update_version()  # version += 1, updated_at = now
    await add_data_points([pref], graph_only=True)

    stored = await graph.get_node(str(pref.id))
    print("v2:", stored["value"], "version", stored["version"])


asyncio.run(main())
```

The example needs no LLM or embedding key, because `graph_only=True` skips embeddings. It prints:

```
v1: vim version 1
v2: helix version 2
```

After the second write, the node reads `helix` at version 2. The earlier value `vim` is gone: the version counter tells you the node changed, not what it was before. The [provenance ledger](#keep-an-audit-trail-of-writes) wouldn't keep it either: it records when and from where `cognify()` wrote an entity and its name, not its other property values, and it doesn't see writes from `add_data_points()`. To keep earlier values, [export a COGX snapshot](/core-concepts/further-concepts/cogx) before changing them.

## Update a document

When a document you added with `remember()` changes, use [`cognee.update()`](/python-api/update). It replaces the document under the same `data_id`, re-extracts what the edit touched and removes relationships that no longer hold.

Adding the changed file with `remember()` again doesn't create a second copy. If the dataset already holds a file from the same path (or an upload with the same filename) with different content, `remember()` raises `DocumentUpdateRequiredError` and points you to `update()`. Raw text has no file name, so changed text is always added as a new document, with no link to the old one. Adding the exact same content again is a no-op, since Cognee deduplicates documents by content hash.

## Keep outdated facts instead of overwriting them

When new data contradicts what Cognee already knows, conflict review keeps both facts and records which one is current. Run it with `improve(review_conflicts=True)`, or set `IMPROVE_REVIEW_CONFLICTS=true` to run it after every `remember()`. The date that decides which fact is newer comes from the `effective_date` in the document's metadata.

Superseded facts stay in the graph and in `recall()` results. They are sorted after current facts and labelled, for example `[superseded; as of 2025-03-01]`, so the LLM sees what changed and when. See [Review Conflicting Facts](/guides/review-conflicts) for a walkthrough.

## Keep an audit trail of writes

For an audit trail of what was written, when and from which source, enable the provenance ledger before the first `cognify()` or `remember()` call:

```bash theme={null}
PROVENANCE_TRACKING=true
```

Cognee then records every document, chunk, entity and relationship that `cognify()` (and so `remember()`) writes in an append-only, hash-chained ledger. Each entry holds the entity's id, type, name, source and timestamps, not its other property values. Writes from `add_data_points()` in your own code, `improve()` and chunk-level `update()` are not recorded. When an entity is written again, the ledger archives the previous entry and links the new one to it, instead of overwriting it. You can call `invalidate()` to retire an entity with a tombstone entry; `forget()` doesn't do this, and ledger entries stay after the data is deleted. `revision_history()` returns an entity's versions oldest first, each with `version`, `valid_from`, `valid_until` and `recorded_at`, and `verify_chain()` checks that no entry was edited or removed:

```python theme={null}
from cognee.modules.provenance import get_provenance_manager

manager = get_provenance_manager()

history = await manager.revision_history(f"{dataset_id}:{entity_id}")
for entry in history:
    print(entry["version"], entry["valid_from"], entry["valid_until"])

print((await manager.verify_chain())["valid"])
```

Ledger ids are prefixed with the dataset id, so the same entity keeps a separate chain in each dataset. The ledger is read through `cognee.modules.provenance` and has no HTTP endpoint. See [Provenance ledger](/python-api/cognify#provenance-ledger) for setup and the full `ProvenanceManager` API.

## What Cognee doesn't keep

* **Previous node values.** Writing a node again overwrites its properties in the graph and vector stores. The ledger records that earlier versions existed, but not their values.
* **Previous document content.** `update()` replaces the document's content and file.
* **Point-in-time queries.** `recall()` reads the current graph. There is no "as of" filter.
* **Deleted data.** [`forget()`](/core-concepts/main-operations/forget) deletes permanently. There is no soft delete. Provenance ledger entries for the deleted data stay in place.

To keep a restorable copy of a dataset at a point in time, export it as a [COGX archive](/core-concepts/further-concepts/cogx) before making changes:

```python theme={null}
await cognee.export("main_dataset", format="cogx", destination="snapshots/2026-10-14")
```

## Next steps

> **[DataPoints](/core-concepts/building-blocks/datapoints)**
>
> Fields, identity and updating custom DataPoints

> **[Review Conflicting Facts](/guides/review-conflicts)**
>
> Mark outdated facts as superseded

> **[Update](/python-api/update)**
>
> Replace a document under the same id

> **[Memory Provenance](/guides/memory-provenance)**
>
> See where each fact came from


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.