Skip to main content
A minimal guide to closing superseded facts. When a fact changes (“Alice works at Acme” becomes “Alice works at Globex”), close the old node instead of deleting it — the graph keeps the history, and is_valid() tells you whether any node is still current.

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

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_to on Interval nodes (the time range an Event points to via during), which records when an event occurred — that axis belongs to Time Awareness.
  • 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.

Time Awareness

The other time axis: extract events and timestamps and run time-aware queries.

DataPoints

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