cargo doc --no-deps --open). For what the system does see
operations; for how it fits together see
architecture.
Architecture: three stores
Cognee keeps memory in three complementary backends. Every cognify run writes to all three; search reads across them.
Backend selection and connection settings are covered in
tools/backends and
configuration; the layering is in
architecture.
Building blocks
DataPoints
A DataPoint is the base storage-layer unit: a structured record that carries a stable UUID, timestamps, atype discriminator, free-form metadata, and
provenance fields (source_pipeline, source_task, source_node_set,
source_content_hash). Typed graph nodes — Entity, EntityType, EdgeType,
DocumentChunk, etc. — embed a DataPoint as their base, exposed through the
HasDataPoint trait so provenance stamping can walk any node uniformly. When a
DataPoint is indexed, its vector-store payload has the shape of Python’s
IndexSchema (index_schema_payload()): the indexed text, a few reference
scalars (document_id, chunk_index, source_chunk_id, …) and the base fields
at their IndexSchema defaults, plus the Rust-only dataset_id that
dataset-scoped search filters on. LanceDB stores it without the metadata key,
as Python’s LanceDB adapter does; pgvector keeps it. (This replaces
vector_metadata(); rows written by earlier versions stay readable.)
Python’s serialization and versioning helpers are mirrored as to_json_string()
/ from_json(), to_dict() / from_dict() and update_version() (bumps
version and refreshes updated_at).
Rust: DataPoint / HasDataPoint in cognee-models.
Tasks
A Task is one reusable unit of work that transforms data — classify, chunk, extract, summarize, embed. Tasks come in eight execution flavours (sync/async × single/iterator/stream × single-value/batch) so a step can stream, fan out, or process whole batches. They are composed with optional per-task config (TaskInfo: name, batch size, weight, rate limiter) and the pipeline executor
routes values between them. A stream task that fails after it has started
yielding fails the run: the failure is not retried, because the stream is
already partly consumed, and items already passed downstream are not rolled
back.
Rust: Task / TypedTask / TaskInfo in cognee-core.
Pipelines
A Pipeline is an orchestrated sequence of Tasks with shared context (database, graph, vector, cancellation, progress) and a watcher for status events. The concrete pipelines are:
Pipeline orchestration primitives (
PipelineWatcher, ExecStatusManager,
thread pool) live in cognee-core. The end-to-end flow is
described in operations.
Key concepts
Datasets
A Dataset is the organizational scope for memory operations: named, owned by a user, optionally tenant-scoped. As in Python, adata row belongs to exactly
one dataset (data.dataset_id) and gets a random uuid4 id, so the same
content added to two datasets is two rows. add / cognify / search / delete all
operate within a dataset scope. Dataset IDs are deterministic (UUID5 of name,
owner and tenant) for cross-SDK reproducibility.
Rust: Dataset in cognee-models; lifecycle helpers in
DatasetManager (cognee api::datasets).
Sessions
A Session is a temporary memory context — search/answer history and feedback for a single conversational thread — distinct from permanent, graph-backed storage. Passing a--session-id to remember / recall scopes a turn to that
session and lets retrieval reuse prior context; omitting it on remember
persists input as permanent graph memory (see the memory API in
operations). A completion search / recall (a retriever
that prepares session turns; not chunks, summaries, lexical, Cypher, …) that
omits it now uses the default session when the caller is known and session
memory is on (session.caching); before, such a search was stateless. As in
Python’s resolve_session_id, the id is default_session_<dataset_id> when one
distinct dataset is searched, so separate single-dataset searches never share a
session, else default_session. A search over several datasets, or naming none
(unless per-dataset storage leaves the caller exactly one readable dataset,
which scopes it), is one logical turn in the unscoped default session. This
diverges from Python in two cases: Python’s access-control path uses one
session per dataset, and on a shared store Python scopes a search naming no
dataset to the user’s only dataset. Such a search reads the session’s history
and, while AUTO_FEEDBACK is on, its stored guidance entries, analyzes the
turn (also only while AUTO_FEEDBACK is on) and saves its Q&A; pass your own
session id to keep it out of the default session, or turn session.caching off
to keep it stateless.
The store backend is pluggable.
Rust: SessionStore trait in cognee-session, with
FsSessionStore (crate feature fs), RedisSessionStore (crate feature redis),
and RelationalSessionStore backends. Setting session.store = "fs" or "redis"
requires a cognee built with its session-fs / session-redis feature. Those
features register the store factory and its session.fs / session.redis config
section; enabling cognee-session/fs or cognee-session/redis alone does neither.
Next to its Q&A entries and agent trace steps, a session keeps a list of
session-context rows (Python’s session context): JSON objects with a "kind"
field. "context" rows are active guidance entries (SessionContextEntry:
goals, rules, preferences, lessons); other kinds are feedback records or
internal state rows, such as the StateRowWatermark rows the improve stages use
to remember how far they got. Only "context" rows are ever rendered
(coerce_active_context_entries). SessionManager::{get,create,update}_session_context_entry
read and write them on every store (session_context_entries table,
{session_id}.context.json file, session_context:{user}:{session} Redis list);
deleting or pruning a session removes them. The table holds one row per
(user_id, session_id, entry_id) (unique index uq_session_context_entry, as
Python’s uq_cache_session_context_entry), so on the relational store a create of
an existing entry id replaces it in place (the file and Redis stores append); a session without a user is the "default" user’s, as on
the file and Redis stores. apply_candidate_updates is the one
writer of guidance entries (confidence ≥ 0.75, at most 280 characters, an exact
duplicate links its new source instead of a second entry); improve’s
extract_agent_context stage feeds it the agent-profile lessons it draws
from the session’s trace steps.
The session graph-context snapshot is removed: SessionStore /
SessionManager::{get,set}_graph_context, cognee::session::{get,set}_graph_context
and the bindings’ graph-context ops are gone, a migration drops the
session_graph_context table, and session history no longer starts with a
“Background knowledge from the knowledge graph” prefix. The recall scope
"graph_context" is a deprecated alias of "graph" (it logs a warning), and
"all" means graph, session and trace.
With personalization on (personalization_enabled), improve’s
update_user_preferences stage keeps one internal UserPreference node per
user and dataset, with weighted prefers edges to the graph nodes the user’s
rated answers used and the user’s stated preferences as text; graph, RAG, and
hybrid searches by that user rank by those weights and carry that text (see
Configuration: feedback & personalization).
Node Sets
A node set is a tag attached to ingested data and the DataPoints derived from it, used to categorize and later scope the knowledge base. It is partially realized in Rust today:- Tagging at ingest —
addaccepts anode_set, stored onData.node_setand underexternal_metadata["node_set"], where cognify reads it and propagates it to derived DataPoints assource_node_set. Earlier versions wrote only the column, so data added with a node set was never tagged andnode_namesearches missed it; cognify now falls back to the column for those rows. Adding the same content again with a node set replaces its stored tags (without one, they are kept). Every name must be non-empty, and an item’s ownexternal_metadata["node_set"]must be a list of names ornull; otherwise the wholeaddfails withIngestionError::InvalidNodeSetbefore anything is stored. The pipeline executor can attachnode_setprovenance to task outputs (Tagged/TaggedMetaincognee-core). - Scoping — a graph subset can be selected by node type and node name
(the graph trait’s
get_nodeset_subgraph, used by search’s node-name filter). The internalpersist_sessionsstep tags cached session data with a fixed node set. Memify’s--node-type/--node-namescoping went away with memify.
Ontologies
An ontology grounds extracted entities in external, structured knowledge. cognee-rust loads RDF/OWL ontologies (Turtle, RDF/XML, N-Triples, JSON-LD) and uses them for fuzzy entity matching and subgraph enrichment during cognify. The default is a no-op resolver (no grounding), matching Python’sontology_file=None.
Rust: OntologyResolver trait in cognee-ontology, with
NoOpOntologyResolver (default) and RdfLibOntologyResolver. Enabled per run
via cognify’s --ontology-file; see configuration.
Loaders & Chunkers
Loaders handle file-format reading at ingest: a loader registry dispatches by MIME type / extension to per-format loaders (text, PDF, CSV, HTML, image, audio, and theunstructured office formats), most behind feature flags. As in Python,
they run at add, which stores the extracted text (an image’s vision-LLM
description, an audio file’s transcript) and keeps the source file in
original_data_location. Chunkers
then segment a document into token-bounded pieces through a word → sentence →
paragraph hierarchy, sizing chunks with a pluggable TokenCounter
(WordCounter, or the feature-gated HuggingFace / tiktoken counters).
Rust: loaders (LoaderRegistry, DocumentLoader) in
cognee-ingestion; chunking (text_chunker,
TokenCounter) in cognee-chunking. Token-counter
selection is configured in configuration.
See also
- operations — what the pipelines and memory API actually do
- configuration — env vars and runtime config for every concept above
- architecture — crate layering and design patterns
- tools/backends — choosing relational / vector / graph backends
- roadmap/README.md — what is partial or not yet implemented