Skip to main content

How Cognee handles changing facts

Memory goes stale: a person changes jobs, a price changes, a newer document contradicts an older one. Cognee handles this with three mechanisms. None of them delete the old fact: the graph keeps the history and marks what changed. Contradiction detection and supersede-by-recency are off by default. The rest of this page is a minimal guide to closing a fact manually, for example when “Alice works at Acme” becomes “Alice works at Globex”.

Before You Start

  • Complete Quickstart to understand basic operations
  • Ensure you have LLM Providers configured
  • Read DataPoints for the valid_to field and the rest of the node schema
  • Use the default Ladybug graph store — currently the only backend where close_node() can persist the valid_to stamp (see Backend Support)

Code in Action

The complete runnable script is on GitHub: examples/guides/fact_validity.py.

What Just Happened

Step 1: Remember the Original Fact

forget(everything=True) starts from a clean slate, then remember() builds the fact into the knowledge graph the same way all your data gets in — no low-level ingestion needed. Every node it creates carries a valid_to stamp (int | None, ms epoch) that defaults to None, meaning the fact is still current.

Step 2: Derive the Node Id from the Entity Name

remember() turned the sentence into entity nodes, and entity node ids are deterministic: Entity.id_for(name) applies the same normalization the ingestion pipeline uses (lowercase, spaces to underscores, apostrophes stripped) and returns the id the entity was stored under. No graph scan needed — this works the same on ten nodes or a hundred thousand.

Step 3: Close the Superseded Fact

close_node() stamps valid_to on the stored node — “now” by default — marking the fact superseded without deleting it. It returns True only if the node existed and was patched, and False otherwise (for example when the id is not in the graph). Note the import path: from cognee.tasks.storage.close_node import close_node, is_valid.

Step 4: Remember the Replacement Fact

The new fact flows in through remember() like any other data and becomes its own nodes. Supersede instead of delete: the old node stays in the graph with valid_to set, so your memory keeps the history of what used to be true.

Step 5: Check Staleness with is_valid()

is_valid(node, at_ms=None) returns True while valid_to is None (never closed) or lies strictly in the future relative to at_ms (default: now). It accepts either a DataPoint instance (reads the attribute) or a plain graph-node dict (reads the key), so it works on records read back from the graph engine, as here.

Behavior to Know About

  • Node-level granularity. valid_to lives on nodes: closing marks the whole node stale, and the edges attached to it are not stamped.
  • Backdating. close_node(node_id, at_ms=...) stamps a specific ms-epoch timestamp instead of “now”, and is_valid(node, at_ms=some_past_ms) asks whether the fact was still current at that moment.
  • Not idempotent. Closing is last-write-wins: re-closing an already-closed node overwrites valid_to with the new timestamp (earlier or later). Guard with is_valid() first if you need the first close to stick.
  • Two time axes. valid_to records when a fact stopped being true. It is not the time_at / time_until period on a Timestamp node, which records when a fact happened — that axis belongs to Temporal Knowledge Graph.
  • Retrieval is not filtered yet. Search and graph completion neither filter nor down-weight closed nodes, so a superseded fact can still surface in results. Applying is_valid() to what you retrieve is currently the caller’s job; retrieval-side consumption is planned as a follow-up.

Backend Support

close_node() persists valid_to through the graph adapter’s optional update_node method — see Adding a new graph database for the adapter contract.
Only the default Ladybug store implements update_node today. On every other backend (Neo4j, Kuzu, Postgres, Neptune, Turso), close_node() logs a warning and returns False — nothing is persisted, and no exception is raised. Check the return value rather than assuming the close landed.

Temporal Knowledge Graph

The other time axis: dated facts become Timestamp nodes that time-aware queries rank by.

DataPoints

The building block that carries valid_to and the rest of the node schema.