DataPoints: Atomic Units of Knowledge
DataPoints are the smallest building blocks in Cognee.They represent atomic units of knowledge — carrying both your actual content and the context needed to process, index, and connect it. They’re the reason Cognee can turn raw documents into something that’s both searchable (via vectors) and connected (via graphs).
What are DataPoints
- Atomic — each DataPoint represents one concept or unit of information.
- Structured — implemented as Pydantic models for validation and serialization.
- Contextual — carry provenance, versioning, and indexing hints so every step downstream knows where data came from and how to use it.
Core Structure
A DataPoint is just a Pydantic model with a set of standard fields.See example class definition
See example class definition
id— unique identifier (shared across all three stores, linking vector, graph, and relational records for the same DataPoint)created_at,updated_at— timestamps (ms since epoch)version— for tracking changes and schema evolutiontopological_rank— an integer indicating the DataPoint’s position in a dependency hierarchy. Lower ranks mean fewer dependencies. For example, anEntitythat other DataPoints reference would have a lower rank than aTextSummarythat depends on it. Defaults to0.valid_to— bi-temporal validity stamp (int | None, defaultNone): the ms-epoch moment at which this fact was superseded.Nonemeans the fact is still current. You normally do not set it by hand —close_node()stamps it on the stored node when a fact is replaced, so the old node stays in the graph instead of being deleted. It has no effect on node identity (dedup is still driven byid/identity_fields), and it is distinct from thetime_toonIntervalnodes (the time range anEventpoints to viaduring), which records when an event occurred rather than when a fact stopped being true.metadata.index_fields— critical: determines which fields are embedded for vector searchtype— the Python class name of the DataPoint subclass (e.g.,"Person","Book")ontology_uri— optional external ontology IRI (str | None, defaultNone). When an entity or type is matched against an ontology — or ingested from RDF — Cognee preserves the stable external IRI here instead of flattening it to a local label, so the node can be linked out to other domains and exported back to RDF. It defaults toNonefor ungrounded nodes and has no effect on node identity (dedup is still driven byid/identity_fields). See Ontologies → RDF read/write surface.belongs_to_set— groups related DataPoints
Indexing & Embeddings
Themetadata.index_fields tells Cognee which fields to embed into the vector store.
This is the mechanism behind semantic search.
- Fields in
index_fields→ converted into embeddings - Each indexed field → its own vector collection named
Class_field(e.g., aPersonDataPoint withindex_fields=["name"]creates aPerson_namevector collection). TheClasspart comes from the Python class name of your DataPoint subclass. - Non-indexed fields → stay as regular properties in the graph and relational stores
- Choosing what to index controls search granularity
index_fields to the single field that collection embeds — so each collection holds that field’s own embedding, and the list you declared on your instance is left untouched by indexing.
Cross-store retrieval: When a vector search finds a match, Cognee uses the shared
id to retrieve the full DataPoint from the graph store, which holds all properties (not just the indexed field). This is how Cognee returns complete results from a semantic search.From DataPoints to the Graph
When you calladd_data_points(), Cognee automatically:
- Embeds the indexed fields into vectors
- Converts the object into nodes and edges in the knowledge graph
- Stores provenance in the relational store
Examples and details
Example: indexing only one field
Example: indexing only one field
"name" is semantically searchableRelationship syntax options
Relationship syntax options
Edge[Source, Target] and FromIdentity() instead address nodes by their identity value; both come from cognee.low_level and are Python-SDK-only. See Custom Graph Model.Built-in DataPoint types
Built-in DataPoint types
Cognee ships with several built-in DataPoint types:
- Documents — wrappers for source files (Text, PDF, Audio, Image)
Document(metadata.index_fields=["name"])
- Chunks — segmented portions of documents
DocumentChunk(metadata.index_fields=["text"])
- Summaries — generated text or code summaries
TextSummary/CodeSummary/GlobalContextSummary(metadata.index_fields=["text"])GlobalContextSummarypowers the Global Context Index
- Entities — named objects (people, places, concepts)
Entity,EntityType(metadata.index_fields=["name"])
- Edges — relationships between DataPoints
Edge— links between DataPoints
Example: custom DataPoint with best practices
Example: custom DataPoint with best practices
- Keep it small — one concept per DataPoint
- Index carefully — only fields that matter for semantic search
- Use built-in types first — extend with custom subclasses when needed
- Version deliberately — track changes with
version - Group related points — with
belongs_to_set
Updating DataPoints
Updating DataPoints
To update a custom DataPoint, mutate its fields, call For documents and files remembered via
update_version() to record the change, then re-add it with add_data_points(). The upsert replaces the existing node in all three stores.cognee.remember(), use cognee.update() instead — it replaces the existing item in the target dataset under the same data_id, re-extracting only the chunks the edit touched and falling back to a full delete, re-add and graph rebuild for that dataset when the chunk-level preconditions are not met.How versioning worksChanging a field on a DataPoint does not automatically create a new revision or persist anything by itself.
In other words, versioning is manual:- Edit the DataPoint fields
- Call
update_version()if this should count as a new revision - Re-add the DataPoint with
add_data_points()to persist the updated state
update_version(), you mark your in-memory object as a new revision before writing it back with add_data_points(). It does two things:- Increments
versionby 1. New DataPoints start atversion=1. - Sets
updated_atto the current UTC timestamp in milliseconds.
Deleting DataPoints
Deleting DataPoints
Use the
cognee.datasets API:Dataset routing: which dataset receives add_data_points output?
Dataset routing: which dataset receives add_data_points output?
add_data_points() does not accept a dataset parameter directly. Dataset assignment is carried by the PipelineContext (ctx) that Cognee injects automatically when your task runs inside run_pipeline.ctx.dataset holds the resolved dataset object. add_data_points uses it to write provenance records (user, dataset, data item) to the relational store.If you call add_data_points outside a pipeline (without a ctx), nodes and edges are still written to the graph and vector stores, but no dataset-level provenance is recorded — the data is not associated with any named dataset.embed_triplets: graph-structure embeddings
embed_triplets: graph-structure embeddings
add_data_points accepts an embed_triplets: bool = False parameter. When set to True, Cognee derives (subject → predicate → object) triplets from the graph edges and indexes each one as a Triplet DataPoint embedding.embed_triplets=True when:- Your queries describe relationships (e.g., “products made by company X”)
- You want to retrieve graph edges via semantic similarity, not just individual nodes
False (the default) for standard node-level retrieval.For the same idea applied after graph creation, see Triplet Embeddings.Deduplication: preventing duplicate entities
Deduplication: preventing duplicate entities
By default, each DataPoint receives a random UUID4 on instantiation. To make identical entities share the same node — and avoid duplicates — mark one or more fields as the deduplication key.Cognee automatically populates How it worksWhen one or more identity fields are defined and all of them resolve on the instance, Cognee generates a deterministic UUID5 from the class name and the field values instead of a random UUID4. Concretely, the id is Re-adding an object with the same
- Option 1:
Dedup() - Option 2:
identity_fields
identity_fields from the Dedup() annotations. No explicit metadata declaration is needed.uuid5(NAMESPACE_OID, "ClassName:value1|value2|..."), with each value lower-cased and its spaces normalized to _ — so the class name namespaces the id and two different node types never collide on the same input. Two Product instances with the same sku produce the same id, so add_data_points upserts them onto the same graph node rather than creating a duplicate. If an identity field is missing and has no default, Cognee falls back to a random UUID4.You can recompute this id at any time without building an instance via Product.id_for(sku_value) — it returns the same UUID5 the instance would get, which is useful for lookups.Using an external system’s IDIf your records already have a stable identifier from an external system (for example a CMS entry UUID), pass it directly as id. An explicit id always takes precedence over Dedup() / identity_fields generation, so that external identifier is what sits on the graph node:id upserts onto the existing node, so syncing an update from the source system overwrites the node in place instead of creating a duplicate. (id must be a UUID — if your external key is not already a UUID, either normalize it into one or feed it through Dedup() / identity_fields so Cognee derives a deterministic UUID5 from it.)Checking whether a DataPoint already existsBecause the ID is deterministic, you can check existence by constructing the instance (which generates the identity ID) and querying the graph engine:Typing the metadata field (avoiding type-checker warnings)
Typing the metadata field (avoiding type-checker warnings)
The base
metadata field is typed as MetaData, a TypedDict — not a plain dict. Annotating a subclass’s metadata as dict (e.g. metadata: dict = {...}) narrows the type incompatibly, which makes type checkers like Pylance warn that the override conflicts with the base field. Annotate it with MetaData instead:MetaData is defined in cognee.infrastructure.engine.models.DataPoint and must be imported from that path — it is not re-exported from cognee.low_level or cognee.infrastructure.engine, so from cognee.low_level import MetaData fails.The TypedDict accepts four keys:index_fields: list[str]— required; fields embedded for vector searchtype: str— optionalidentity_fields: list[str]— optional; the deduplication keytransparent: bool— optional; marks the class as a container that is never stored
Embeddable() / Dedup() annotations when you can — Cognee derives metadata from them automatically, so you never declare the field (or its type) by hand.Defining a DataPoint type at runtime (instead of writing a class)
Defining a DataPoint type at runtime (instead of writing a class)
A DataPoint type is always a Pydantic class, and its fields must be declared. Such a class behaves exactly like a written one:
add_data_points rejects anything that is not a DataPoint instance, so a plain dict of values raises InvalidDataPointsInAddDataPointsError, and a value passed for a field the class does not declare is silently dropped — Cognee reads only declared fields when it converts an object into nodes and edges, so that value never reaches the stores.You do not have to write that class by hand, though. Because DataPoint is an ordinary Pydantic model, you can build the type at runtime with Pydantic’s create_model and __base__=DataPoint:Embeddable() and Dedup() are picked up, Product.id_for("ABC-123") recomputes the same deterministic id, and a field typed as another DataPoint becomes an edge.Two things to watch:- The name you pass is the class name. It plays that role everywhere this page mentions it — the node
type, theClass_fieldvector collections, and the deduplication id namespace. Build each shape once and reuse the class — a new name means new collections and new ids for the same records. index_fieldsmust be a class-level default. If you declaremetadataexplicitly instead of using the annotations, pass it as a field definition ("metadata": (MetaData, {"index_fields": ["name"]}), importingMetaDataas described above). Settingmetadataon an instance does not reach storage: Cognee rebuilds the node from the class field defaults, so instance-level index fields are lost and nothing is embedded.
Transparent containers: nodes that group rather than describe
Transparent containers: nodes that group rather than describe
Some DataPoint classes exist only to hold other DataPoints — a wrapper you pass as Wherever a transparent node appears, it is replaced by its DataPoint children:Fields inherited from
graph_model so the LLM returns a list of entities, for example. Set metadata["transparent"] = True to declare that the class is structure rather than content:- The wrapper itself is never stored — no node, and never an edge endpoint. Without
transparent,PeopleGraphbecomes a node with apeopleedge to eachPerson. - Its DataPoint children are promoted to top-level roots, so one extraction can produce several top-level nodes, or none at all if the wrapper came back empty.
- Nesting works: a transparent child of a transparent wrapper resolves too, and a field pointing at a transparent node gets edges to that node’s children instead.
belongs_to_setis not inherited by the children — a wrapper’s NodeSets are not its content.
DataPoint itself and empty values are exempt, so an unset optional relationship never warns.Transparency is a property of the model. How widely a chunk links into the graph extracted from it is a property of the run — see
cognify(chunk_attachment=...). Under "direct" (the default), a chunk whose extracted root is transparent links to the children that replaced it.Tasks
Learn how DataPoints are created and processed
Pipelines
See how DataPoints flow through processing workflows
Main Operations
Understand how DataPoints are used in Remember, Improve, and Recall