Skip to main content
What cognee-rust does. The primary surface is the memory API — 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 the cognee 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-id to 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: remember returns once the entry is stored. It is skipped when improve.auto_enabled is off (IMPROVE_AUTO_ENABLED=false) or an improve.* setting is invalid, and debounced by improve.debounce_entries / improve.debounce_seconds (state kept in the session as an internal auto_improve_debounce row; see Configuration: improve). The CLI waits for it before exiting and a binding’s close() before closing the handle (cognee::wait_for_background_tasks; bindings-common ops::lifecycle::wait_for_background_tasks). A detached task that panics is logged at error.
  • Permanent graph memory — omit --session-id and the input is persisted as permanent, graph-backed memory. Its self-improvement pass runs improve() on the dataset (it used to run memify), as Python’s remember does, and follows the same gate: off when improve.auto_enabled is off or an improve.* setting is invalid, as Python’s self_improvement and auto_improve_enabled().
The bindings’ 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’s extract_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 recorded ERRORED and the call returns Err. 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.
  • FailedItems below 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 returns Ok with the failed-file list in the report, so the next run knows exactly what to redo.
  • FailedItems over 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.
  • Nothing removes nothing. The escape hatch (code only — not selectable from the environment).
Under 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.
Behaviour change: re-cognifying an already-complete dataset is a no-op, returning Ok with already_completed = true. Deployments that relied on cognify re-processing everything on every call will see it “stop doing anything”; set with_incremental_loading(false) to restore the previous behaviour.
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 by SearchType (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 the cognee 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.