remember, recall, improve, forget — four high-level
operations that compose the lower-level pipeline (add → cognify → search). Every operation is reachable from each interface (CLI, language
bindings, and the HTTP server, which is not part of the cognee-rs
repository) — see Tools. API/type detail lives in
rustdoc (cargo doc --no-deps --open); this page is the conceptual map.
The memory API
Cognee’s primary surface is four operations that turn raw input into queryable, self-improving memory. They live in thecognee api
module (cognee::api::{remember, recall, improve, forget}) and surface as
the always-built cognee-cli verbs remember / recall / improve / forget.
remember
Stores input as memory: it runs add + cognify and then, by default, the improve enrichment pass. Accepts inline text and/or file paths.- Session memory — pass a
--session-idto scope the turn to a session (session-backed QA history). The improve that bridges the session into the graph runs as a detached background task, as in Python:rememberreturns once the entry is stored. It is skipped whenimprove.auto_enabledis off (IMPROVE_AUTO_ENABLED=false) or animprove.*setting is invalid, and debounced byimprove.debounce_entries/improve.debounce_seconds(state kept in the session as an internalauto_improve_debouncerow; see Configuration: improve). The CLI waits for it before exiting and a binding’sclose()before closing the handle (cognee::wait_for_background_tasks; bindings-commonops::lifecycle::wait_for_background_tasks). A detached task that panics is logged aterror. - Permanent graph memory — omit
--session-idand the input is persisted as permanent, graph-backed memory. Its self-improvement pass runsimprove()on the dataset (it used to run memify), as Python’srememberdoes, and follows the same gate: off whenimprove.auto_enabledis off or animprove.*setting is invalid, as Python’sself_improvement and auto_improve_enabled().
selfImprovement option now defaults to true, as Python’s
self_improvement=True; pass false to skip the improve. The returned
RememberResult reports the automatic improve as Python does: improve (the
ImproveResult), improve_error (the error improve() returned, or its
errored stages; the remember itself succeeds and error stays unset),
after_run_error (always unset: Rust has no such step) and improve_skipped,
each serialized only when set. A session-mode improve runs detached, so
improve stays unset there; the Rust-only improve_launched flag, which is not
serialized, says it was started. A host can decline the automatic improve with a reason through
register_auto_improve_admission / clear_auto_improve_admission; the reason
is reported as improve_skipped.
remember ≈ add + cognify + improve. rustdoc: api::remember.
recall
Queries memory with auto-routing: when no query type is given,recall
picks the retrieval strategy by the first matching rule, as Python does: a
quoted phrase → CHUNKS_LEXICAL, coding-rules wording → CODING_RULES, a time
preposition before an absolute date → TEMPORAL, anything else →
HYBRID_COMPLETION. A routed type that the backend rejects, or a lexical or
rules search that finds nothing, is retried as HYBRID_COMPLETION (a lexical search returns its top-ranked chunks even when none contains the phrase, so it comes back empty only for a phrase made of stop words).
Routing also honours what the deployment can run. With no usable LLM (the
provider needs an LLM_API_KEY that is not set; see
Supported LLM providers),
recall still routes: CHUNKS_LEXICAL and CODING_RULES run as they are, and
any other routed type falls back to HYBRID_COMPLETION. The fallback, and an
explicit HYBRID_COMPLETION, GRAPH_COMPLETION or RAG_COMPLETION, then
returns the prompt it would have sent the LLM, built
from the retrieved context, instead of an answer (only_context). An explicit
query type is never rerouted. No rule routes to CYPHER or NATURAL_LANGUAGE;
requested explicitly with
ALLOW_CYPHER_QUERY=false, they are refused.
The CLI and the bindings build their search orchestrator with
SearchBuilder, which sets these constraints from the LLM and
search.allow_cypher_query. A SearchOrchestrator built by hand allows
everything unless given with_routing_constraints. This is a breaking change
for callers of cognee_search::route_query, which now takes
&RoutingConstraints (pass &RoutingConstraints::default() for the previous
behaviour); RouteDecision gains an only_context field.
When no pipeline has run on the datasets, or no build there ever completed and
the latest one failed, a graph-only recall returns a system note saying so instead of searching
(configuration).
Recall is session-aware (reads
session history when given a --session-id; a completion recall without one
uses the default session) and graph-backed. Results are
returned to the caller (printed to stdout by the CLI).
The CLI auto-routes whenever --query-type is omitted, but the bindings do so
only with autoRoute: true (default false), and the Rust api::recall only
with auto_route = true. Without it, a recall with no query type runs
HYBRID_COMPLETION (it ran GRAPH_COMPLETION before).
recall ≈ auto-routed search. rustdoc: api::recall.
improve
Runs the self-improvement loop over a dataset as nine gated stages, in Python’s registry order:feedback_weights, persist_session_qa,
persist_agent_traces, extract_agent_context, distill_sessions,
update_user_preferences, build_truth_subspace, review_conflicts,
global_context_index. The first seven read the given sessions and are
skipped with no_session_ids without them; the last two work on the graph
alone and are opt-in. Each stage reports a StageResult (status, reason,
error, counts, duration_ms) on the returned ImproveResult (dataset_id,
dataset_name, session_ids, stages, background, finished, error,
memify_run), whose status summarises them (completed, errored,
skipped, running). The earlier memify (triplet enrichment) and
sync_graph_to_session stages are removed, and the old counters
(stages_run, feedback_entries_processed, sessions_persisted, …) are now
stage counts. Stages named in improve.stages_disabled
(IMPROVE_STAGES_DISABLED, a comma-separated list) are skipped first, with
disabled_by_config; an unknown name, or the fatal persist_session_qa, makes
the configuration invalid and the run fails before any stage. feedback_alpha
defaults to improve.feedback_alpha (0.1). The options that only scoped or
fed the removed memify stage are gone too, as in Python
(node_name and the CLI --node-name flag, extraction_tasks,
enrichment_tasks, data, and the bindings’ nodeName). ImproveResult still serializes Python’s deprecated memify_run key,
as {}. The bindings’ improve options accept datasetId (a UUID string), as
Python’s improve(dataset=<UUID>): the improve targets that dataset, possibly
one shared with the caller, instead of looking datasetName up among the
caller’s own datasets.
The run fails open — a failed stage is recorded and the next one runs —
except that a failure in persist_session_qa (losing session Q&A would be data
loss) stops the run with ApiError::ImproveAborted carrying the partial
result, and an exhausted LLM budget skips every later stage with
budget_exhausted. In the bindings that abort is SdkError::ImproveAborted
(code IMPROVE_ABORTED): Python’s CogneeImproveAbortedError.improve_result,
JavaScript’s ImproveAbortedError.improveResult; C (CG_ERR_RUNTIME) and Java
append improve_result: <json> to the message. The stages that draft text with
an LLM are skipped with no_llm_configured when none is configured.
review_conflicts (opt-in via improve.review_conflicts) is not ported yet
and always reports a skip reason.
extract_agent_context runs while automatic session context is on
(auto_feedback with session caching; otherwise auto_feedback_disabled)
and an LLM is configured. Per session it reads the trace steps newer than the
session’s extraction watermark (plus 3 already-processed ones, at most 40),
shows the model the session’s existing agent lessons with Python’s prompt,
and stores at most 5 new lessons as agent-profile session-context entries
(sections tool_rules, workflow_state, success_patterns,
failure_lessons, environment_facts). A lesson is stored only with
confidence ≥ 0.75, cut to 280 characters; one whose normalized text an
entry of the same section already has is not stored again. The watermark
advances only after the session’s extraction succeeded. Counts: lessons,
sessions_failed; the stage is errored when any session failed, and the
other sessions still run. Error texts shown to the model are redacted
(tokens, secrets, UUIDs, long numbers).
update_user_preferences runs while personalization_enabled is on. It folds
the sessions’ turns into one UserPreference node per (user, dataset), an
internal node (never embedded, never returned by a search) in the
user_preferences node set, and weighted prefers edges from it to the graph
nodes the turns’ answers used (used_graph_element_ids.node_ids). A turn’s
rating is its feedback_score, else the rating a feedback row inferred for
it; a rating r pulls each node’s weight toward (r - 1) / 4 by
preference_alpha, from its decayed value
0.5 + (w - 0.5) * (1 - preference_beta)^(idle turns). Every turn advances
the node’s turn counter once; an edge that decayed to within 0.01 of neutral
is retired. The node’s text collects the usable preferences-section
session-context entries newer than its watermark, newest first, capped at
2000 characters on line boundaries. Turns are marked in memify_metadata
(preference_turn_counted, preference_weights_applied), so a re-run
counts and applies each once. Counts: turns_applied, edges_written,
edges_pruned, text_lines_added; nothing to fold is completed with the
reason no_changes. In Rust, preferences-section entries come from a session
search’s turn analysis (while auto_feedback is on with session caching), which
also records the rating a feedback row infers for the previous answer.
With personalization on, searches read these back for the requesting user in
the search’s single dataset: the decayed prefers weights scale the matched
nodes’ distances in graph triplet ranking (every graph completion type) and
re-rank RAG completion’s chunks (fetching wide_search_top_k candidates
first, then trimming to top_k), by at most personalization_influence;
graph and RAG completion also append the preference text (under
## What this user prefers) to the question and context. The hybrid search
uses them too: the weights re-rank its chunk lane, and its completion and
only_context preview carry the same preference guidance. On a session search
the preference lines instead join the Preferences section of the one
## Active session guidance block (budgets of 400 characters per section and
1200 in total), served after the question and context together with the
session’s stored guidance entries; there is no second block. An answered turn
records the ids of the guidance entries it served (used_session_context_ids),
so the next turn’s ratings of that guidance apply. Session history now goes
into the user prompt, ahead of the rendered template, rather than into the
system prompt. A RAG completion turn now records its chunks’ ids, so
ratings can weight chunks.
A run holds its dataset’s lock for its whole length, then a claim on every
session it targets (session:{user_id}:{session_id}), and releases both once
it ends. Both wait, with no timeout: improves that share a dataset or a session
run one after the other, as pipeline runs on one dataset do, instead of the
later one skipping; runs on different datasets and sessions do not wait. The
dataset lock is Python’s one dataset_lock registry: cognee_core::DatasetLocks::global()
is the default of add (AddPipeline::new_with_algorithm; AddPipeline::new
takes no lock unless one is set), dataset-scoped deletes
(DeleteService) and improve (ImproveParams::dataset_locks, else the add
pipeline’s registry, else the global one), and cognify always
holds it for its run, so in one process those writers queue per dataset. A
host with its own registry wires it into the add pipeline, the delete service
and improve. The stages’ own adds and
cognifies re-enter the improve’s hold. A dataset that does not exist yet is
held by the id an add of it would create.
global_context_index (opt-in, build_global_context_index; only from the
Rust improve() API, since the CLI and the bindings always pass false) builds the
dataset’s global context index with Python’s default from_entities method,
which makes no LLM call: one GlobalContextSummary bucket per document
naming the entities it is distinctively about (tf·idf over the dataset’s
chunks, up to 20) and up to 40 relation lines among them, plus a root of
three lines (Topics:, Entities:, Documents:). Ids are Python’s uuid5
ids, so the text and ids match Python’s byte for byte. Each run computes the
whole index, writes only the nodes whose text changed (graph node, vector in
GlobalContextSummary_text, summarized_in edge to the root and
summarizes edge to the document) and deletes the index nodes it did not
compute; the nodes’ ownership rows name the dataset, so deleting the
dataset removes them. Its count nodes_written is the nodes it wrote. The
opt-in LLM method (vector-placed buckets of TextSummary nodes, levels, LLM
bucket and root summaries with Python’s prompts) is
cognee_cognify::build_global_context_index with
GlobalContextIndexMethod::Llm; improve() does not use it, as Python’s stage
does not. A graph or hybrid completion search with include_global_context_index
prepends the root and the global_context_index_top_k (3) nearest bucket
summaries to its context (World summary: / Relevant areas:). A
dataset-scoped search reads only its datasets’ root and summaries (their
dataset_id), so datasets sharing one graph and vector store never see each
other’s index. With no triplet found, the prelude alone is the context, and an
only_context search previews the prompt built from it, as Python’s
get_context_from_objects does.
global_context_index reports already_completed (no_writes_since_last_improve)
when no pipeline_runs row other than a search, a recall or a host’s
improve_pipeline record was written since it last ran. A delete records a
forget row on every dataset it touched, and an improve run whose stage may
have written to the graph (anything but the index itself, unless its write
counts say it wrote nothing) records one improve row, so both count as
changes, as in Python. A run that did not end errored stamps the stage’s start in the run_info of
its improve operation row ({"global_context_index": {"status", "started_at"}}),
where Python keeps it; the newest such row is the watermark, and the row itself
is not a change. Without a relational database, a stamp, or on any error
reading them, the stage runs. (The graph_sync_checkpoints table that held the
stamps before is gone, and with it CheckpointStore and
ImproveParams::checkpoint_store; a migration moves existing stamps into improve rows, so
no stage reruns because of the upgrade.)
What is not ported yet is tracked in
not-implemented.md.
rustdoc: api::improve.
forget
Removes memory: a whole dataset, a specific data item, or everything. Cascades across the relational, graph, and vector backends and file storage. rustdoc:api::forget.
Lower-level pipeline
The memory API composes these building blocks. They remain available directly when you need fine-grained control over each stage. The classic flow is:add (ingest)
Streams input, computes a content hash, deduplicates, and persists the data plus metadata. Accepts text, file paths, and HTTP(S) URLs (fetched and routed by MIME type). URL fetches refuse private, loopback, link-local, carrier-grade NAT (100.64.0.0/10), multicast and unspecified addresses, including IPv4-mapped
IPv6 forms. This applies to IP-literal URLs, to hostnames that resolve only to
such addresses, and to every redirect hop. A blocked URL fails at once with an
invalid-URL error and is not retried. HTTP_PROXY / HTTPS_PROXY / ALL_PROXY
(and NO_PROXY) are honoured; when a proxy is used, the proxy resolves the
target hostname, so only IP-literal targets are checked. As in Python, data
rows are dataset-scoped: a new row gets a random uuid4 id, and re-adding
content dedups only within the same dataset, owner and tenant (by content
hash), so the same content in two datasets is two rows, and a re-add after
delete gets a new id. Dataset ids stay deterministic (UUID5 of name, owner and
tenant). Upgrading runs a migration that splits a row shared by several
datasets into one row per dataset (the copies record the original id as
legacy_id, and the bindings’ data records gain dataset_id and legacy_id);
stop every writer before upgrading.
Files are routed to a loader by their sniffed content type, as in Python:
the data row stores the sniffed original_extension / original_mime_type (a local .html file keeps its path’s),
non-UTF-8 bytes routed to the text loader (for example a PNG named .txt) fail
the add, and a file no loader claims (for example a .zip) fails with
NoLoaderForFile instead of falling back to the text loader.
AddParams::preferred_loaders is now honoured: its Python loader names
(text_loader, pypdf_loader, image_loader, …) are tried in order before
the default priority, and a name that is not registered is skipped. Images and audio
are accepted: the image loader (vision LLM) and audio loader (Whisper, when the
LLM provider has a transcriber) run at add, which stores the description or
transcript and keeps the raw file in original_data_location. A fetched HTML
page (URL add) is stored raw and read by text_loader, as in Python; a local
.html file is still text-extracted at add time, which Python does not do.
Every file or URL add writes external_metadata
{"_cognee": {"source_uri": <url or file:// URI>}} merged over the item’s own
metadata. Pipeline: cognee-ingestion (AddPipeline).
cognify (knowledge-graph extraction)
Turns stored data into a knowledge graph in five stages: classify documents → chunk text → extract entities/relationships (LLM, batched) and summarize (conditional) — one fused stage running both concurrently over the same chunks, matching Python’sextract_graph_and_summarize → add data
points (six vector collections, plus DltRow_text for DLT rows, and provenance to the relational DB) →
extract DLT FK edges. A DLT row (a document whose external_metadata has
source: "dlt") is one DltRow chunk indexed into DltRow_text, not
DocumentChunk_text; the last step adds its SchemaTable, SchemaRelationship
and DltColumn nodes (vector collections SchemaTable_name,
SchemaTable_description, SchemaRelationship_name,
SchemaRelationship_description and DltColumn_properties) and the row edges.
Datasets cognified before this layout need a re-cognify after deleting their
old graph and vector rows. Configurable via CognifyConfig
(chunk strategy, custom prompts/schemas). Pipeline:
cognee-cognify (cognify() / cognify_datasets()).
The add data points stage embeds and writes each vector collection in
windows of about 16 MiB of vector data (cognee_vector::write_window_points,
around 2,700 points at 1536 dimensions), so memory on the write path does not
grow with the corpus. By default the generated vectors are not kept in the
result: CognifyResult.embeddings is empty unless
CognifyConfig::with_retained_embeddings(true) (field retain_embeddings,
default false) is set. The vectors are in the vector store either way, and
CognifyResult.embedding_count reports how many chunk, entity and summary
embeddings the run generated. Code that read embeddings.len() should read
embedding_count instead.
Dates come out of the default extraction, with no separate temporal pipeline
(the event-graph temporal cognify was removed; see
Temporal Cognify): a node the LLM types Timestamp whose name is an absolute time
(1867, 1969-07, 23 March 1947, from 1803 to 1815) becomes a shared
Timestamp graph node — one per normalized time string, carrying
time_at/time_until (ms since the epoch, UTC, half-open) and its
precision — that *_at edges point at and every chunk naming it contains.
Timestamps are not embedded. A name that does not parse stays an ordinary
entity. Year-less dates in the text (“27 April”, “four weeks later”) are
resolved against the last stated year earlier in the document and handed to the
default prompt as normalization hints; a custom prompt receives none.
An extracted entity’s id derives from its name (Entity.id_for(name)), as in
Python, so the same name from two chunks or documents is one node; several nodes
sharing a name inside one chunk’s graph get deterministic ordinal ids instead.
This is a breaking change: earlier Rust releases keyed entities on the LLM’s
node id, so re-cognifying a dataset built before it adds the name-based entities
beside the old ones. Rebuild such a dataset from scratch to avoid the duplicates.
An extracted node with an empty type gets no EntityType and no is_a edge.
COGNEE_COGNIFY_EXTRACTION_STRATEGY=cascade swaps the single extraction call
for Python’s multi-round cascade (see
configuration).
When a cognify run fails
A chunk or a file the pipeline cannot process is not a bare error: the chunking, graph-extraction and summarization stages collect their failures, and the run result carries a report naming which file, which chunk, which stage and what went wrong.CognifyResult.failures carries it on success;
CognifyError::RunFailed carries the same report when the run failed.
What happens to what the run already wrote is decided at the end, from two
independent settings — when to stop (FailFast, the default, or RunToEnd)
and what to sweep (WholeRun, the default, FailedItems, or Nothing). The
full matrix is in
Cognify failure handling; the end
states are:
- Default (
FailFast+WholeRun). The run stops at the first failed batch — not the first failed file, see below — everything it created is removed, the run is recordedERROREDand the call returnsErr. The store converges to its pre-run state — what earlier runs completed is untouched, because the sweep selects only rows naming this run. Python’s default, in execution and in end state. FailedItemsbelow the failure ratio. The run completes. Only the failed files’ contributions are removed; the files that finished are kept, indexed and marked complete; and the call returnsOkwith the failed-file list in the report, so the next run knows exactly what to redo.FailedItemsover the ratio escalates to a whole-run sweep and an errored run — a large enough share of failed chunks means something systemic, not one bad file.Nothingremoves nothing. The escape hatch (code only — not selectable from the environment).
FailFast a run partitions its files three ways at the abort point:
complete (every chunk extracted — persisted and marked), failed (at least
one chunk failed) and unreached (never attempted). Only complete files are
persisted, which is what keeps a data item all-or-nothing; failed and unreached
files are indistinguishable to the next run and are simply redone.
The abort point is a batch boundary, not a file boundary, and how much work
FailFast actually saves depends on how big that batch is. Graph extraction
stops after the batch that contained the failure — chunks_per_batch, default
2000, so a dataset under 2000 chunks is one batch and every extraction call is
dispatched before the failure is noticed. Summarization has no batch loop: it stops
dispatching as soon as a fatal failure comes back, but the calls already in
flight (up to max_parallel_extractions) are neither cancelled nor discarded.
FailFast is an upper bound on wasted spend, not a promise of none.
A sweep deliberately keeps anything still claimed from outside its scope: an
entity a surviving file also produced, an artifact another run or another
dataset also names, or a row written before ownership tracking existed. The
worst case is a surplus artifact that keeps an owner, never an artifact with no
owner at all.
That claim is recorded even when the artifact is not written. Graph extraction
filters out edges the graph already holds, so a file cognified in a later run
adds no second copy of an edge an earlier file created — but it still records
ownership of it. Without that row the earlier file would be the edge’s only
owner, and deleting it would take the edge’s vectors away from a file that still
references it. Ownership is per (dataset, data item): an edge shared across two
datasets is still governed by each dataset’s own exclusivity check, the same
limitation entity ownership has.
One caveat. Cancellation is swept, unlike Python — though there is no
caller-facing cancel handle on cognify() today, and simply dropping the future
runs no sweep at all.
For a run that was killed (not failed in-process), see
Serialization and recovery of pipeline runs
and cognee-cli edge-reindex in the CLI Reference.
Re-cognifying is now incremental
incremental_loading is on by default and now does what it says. A successful
run marks each data item it finished, in Python’s own pipeline_status format,
and the next run skips the marked items before it builds a pipeline — no
classification, no chunking, no LLM calls.
A failed run marks nothing, and a sweep clears the markers of whatever it rolled
back, so the next run redoes exactly the work that was lost.
The markers live under Python’s one cognify_pipeline key. See
Completion markers and incremental loading.
memify (removed)
memify was removed. The standalone triplet-enrichment pipeline
(
cognee-cli memify, memify() in every binding, POST /api/v1/memify) and
cognify’s triplet embedding are gone, as Python deprecated them; cognify no
longer writes the Triplet_text collection. Deleting data still removes the
legacy Triplet_text rows an earlier version wrote, counting only the points
that exist. Remove memify calls; cognify and improve() cover what they did
for search. See Memify Node Filtering.search (retrieval)
Unified orchestration across 17 retrieval strategies selected bySearchType
(crates/search/src/types/search_type.rs):
GraphCompletion, GraphCompletionCot, GraphCompletionContextExtension,
GraphCompletionDecomposition, GraphSummaryCompletion, GraphReport,
RagCompletion, Chunks,
Summaries, Temporal, Cypher, NaturalLanguage, FeelingLucky, Feedback,
CodingRules, ChunksLexical, HybridCompletion (default; combines vector search over
chunks/summaries/entities/edge-facts and 1-hop graph-neighborhood expansion
around matched entities, then answers via LLM completion; tuning knobs in
Configuration: hybrid retriever knobs).
TRIPLET_COMPLETION and its TripletRetriever were removed with no alias:
"TRIPLET_COMPLETION" is now an unknown search type in serde, the bindings
(validation error) and the CLI; use GRAPH_COMPLETION or HYBRID_COMPLETION
instead. A database that still holds a Triplet_text collection keeps serving
every other type.
A hybrid search falls back to GraphCompletion (with a warning) when it sets
node_type (other than NodeSet), neighborhood_depth or a positive
feedback_influence (an unset one takes DEFAULT_FEEDBACK_INFLUENCE, so a
positive default falls back too), or when neither the DocumentChunk_text nor the
DltRow_text collection exists (checked only on a non-empty graph);
setting wide_search_top_k or triplet_distance_penalty with hybrid is an
error. The CLI’s -t/--query-type exposes only a subset — see
CLI Reference. Entry: cognee-search
(SearchBuilder / SearchOrchestrator).
Two results changed shape, a breaking change. With only_context, the
completion types return the prompt the completion would have sent — the user
prompt as the Text result, with user_prompt / system_prompt on the
response (and prompts, one per dataset, for a multi-dataset search that does
not combine context) — while other types still return their bare context
(GraphReport returns the bare report text, as Python’s does).
GraphCompletion and GraphSummaryCompletion over an empty graph, and
ChunksLexical with no chunks, now fail with SearchError::NotFound, as
Python raises NoDataError. The bindings also forward node_name_filter_operator
(nodeNameFilterOperator in TypeScript and Java) to combine node_name filters
with "OR" (default) or "AND".
The graph retrievers (GraphCompletion and its CoT, context-extension and
summary variants) rank triplets around the vector-search hits. A triplet’s
score, as in Python, sums its source node’s, target node’s and edge’s cosine
distances, each scaled by 2 - importance_weight (0.5 when absent) and, with
feedback_influence > 0, blended with its feedback_weight (higher feedback
ranks better). An element with no vector match takes triplet_distance_penalty
(6.5) before that scaling — 9.75 at the default weight — and is not blended.
importance_weight is not clamped, as in Python: above about 1.69 the scaled
penalty is blended like a real match, and above 2 it turns negative and ranks
first. Both are breaking changes to the ranking.
By default the graph retrievers rank only the edges touching a hit.
neighborhood_depth (a positive integer; the CLI’s --neighborhood-depth, the
bindings’ neighborhoodDepth search and recall option, neighborhood_depth in
Python) switches to Python’s neighborhood projection: load the depth-hop
neighborhood of the nearest neighborhood_seed_top_k hits
(--neighborhood-seed-top-k / neighborhoodSeedTopK; all of them when unset),
score the nodes the expansion found against the query (VectorDB::score_by_ids;
an adapter without it leaves them at the distance penalty), and rank every
loaded edge. Larger depths load more of the graph. Combined with node_name,
which disables the seed search, it returns no triplets, as in Python. The graph
retrievers used to ignore neighborhood_depth, so a request that sets it now
ranks differently, and a non-positive neighborhood_depth or
neighborhood_seed_top_k is rejected for every search type.
brute_force_triplet_search_batch (in cognee_search::graph_retrieval) is
Python’s batch mode (query_batch=): several queries share one graph load and
come back as one ranked list each. As in Python it searches every vector
collection without a limit and loads the whole graph (or the NodeSet subgraph),
so it costs far more than a single search on a large store —
search.triplet_neighbor_cap does not bound it, wide_search_top_k and
node_name (in the vector search) do not apply, and with neighborhood_depth
set it returns no triplets. Unlike Python it skips Triplet_text, whose ids are
never graph nodes. It runs batch mode even for one query; call
brute_force_triplet_search for a single query.
GraphCompletionDecomposition (GraphCompletionDecompositionRetriever, Python’s
retriever of the same name) is the batch search’s main caller. One structured LLM
call splits the question into at most five sub-questions (falling back to the
question itself when the call fails), one batch search fetches their triplets, and
the batches are merged without duplicates. With
retriever_specific_config.decomposition_mode "answer_per_subquery" (the
default) each sub-question is answered over its own triplets, concurrently, and
the question is answered over those answers; "combined_triplets_context" answers
once over the merged triplets. Each context item carries a decomposition entry
(the sub-questions and the triplet’s rank in each) so the completion can rebuild
the per-sub-question contexts. An empty graph is NotFound, and a batch of
queries is rejected.
GraphReport (GraphReportRetriever, Python’s retriever of the same name)
ignores the query and reports on the whole graph from get_graph_data: the
top-top_k hubs (default 10) by normalised degree plus normalised PageRank
(networkx’s defaults, computed here), entity-to-entity edges across different node
sets, and EXTRACTED vs DERIVED edge counts, followed by LLM-suggested questions.
NodeSet containers and belongs_to_set edges are left out and parallel edges
count once, as in Python’s nx.DiGraph. Its context is one item holding the
Markdown report and the structured hubs, connections and counts. An empty graph
gets a fixed report, not NotFound. Python computes PageRank only when scipy is
installed (it is not a cognee dependency) and otherwise ranks by degree alone, so
its PageRank column reads 0.0000 on a default install; Rust always computes it,
falling back to degree alone only if it does not converge.
Temporal is HybridCompletion reranked by the time window the question asks
about (TemporalHybridRetriever, Python’s retriever of the same name; it
replaces the Event-based TemporalRetriever, a breaking change). The hybrid
candidate fetch — chunks and entities at candidate_top_k, default 4 × top_k,
settable as retriever_specific_config.candidate_top_k — runs concurrently with
an LLM call that extracts the window (UTC, start inclusive, end exclusive;
relative dates are not resolved, so no clock is read). The chunks attached to the
Timestamp nodes inside the window join the candidates, the candidates attached
to one — directly, or for a chunk through an entity it contains — move to the
front, tightest date first (for “18 March 1965” a chunk dated that day beats one
that only says “1965”; dates at least as precise as the question keep their
similarity order), and the same cut plain hybrid uses keeps top_k. With nothing
to rerank by it answers with the plain hybrid result and records why:
empty_graph, no_time_constraint / invalid_interval (no window),
no_temporal_match, no_candidate_overlap (TemporalHybridRetriever::retrieve
returns these diagnostics with the context). An LLM or transport failure while
extracting the window fails the search instead of falling back, an empty graph
returns an empty context rather than NotFound, batch queries are rejected, and
search.triplet_neighbor_cap no longer applies to Temporal (search.hybrid
does). The Timestamp nodes are default cognify’s. A graph an older build
wrote with the removed event-graph temporal cognify
pipeline holds Event nodes nothing reads, so it gets plain hybrid answers, as
in Python. Under an attached
AclDb, Temporal results are no longer filtered by dataset membership: with
per-dataset storage it runs in each
dataset’s own databases, and over the shared store it runs unscoped like every
other type.
Additional operations
These live in thecognee api module (and DatasetManager):
Operation → interface map
CLI flags and feature gates: tools/cli. HTTP request/response
shapes: documented with the server, which is not part of the
cognee-rs
repository (see HTTP Server Tools). Binding method
names per language: tools/bindings.