Skip to main content
Every value here resolves as defaults < config.json < .env < env < code (precedence), and can be set from code with the config API or cognee-cli config.

Chunking & tokenizer

Read by crates/chunking/src/config.rs. chunk_strategy (default PARAGRAPH) and chunk_engine are Settings / CognifyConfig fields without env bindings. Chunk size and overlap are env-bindable: chunk_size unset means “auto-calculate at pipeline time”, mirroring Python’s chunk_size=None: min(<embedding engine limit>, llm_max_completion_tokens / 2). The engine limit is EMBEDDING_MAX_COMPLETION_TOKENS (default 8191) for the OpenAI-compatible and Ollama engines and EMBEDDING_MAX_SEQUENCE_LENGTH (default 512) for the local ONNX engine. With OpenAI-compatible embeddings at their defaults that is min(8191, 8192) = 8191 tokens; with the local ONNX/BGE engine it is its 512-token sequence limit. Set it explicitly (env, cognee-cli config set chunk_size, or --chunk-size) to override — an explicit value is always honoured. cognee-cli config set chunk_size null restores the auto-calculation. The token counter is chosen by the cognify engine section (registered with the kind sections), resolved when cognee lowers Settings into a CognifyConfig (Settings::cognify_config; CognifyConfig::default() itself reads nothing): Settings::cognify_config also applies the batch sizes, the graph-extraction prompt, the extraction strategy and the summary schema: The cascade is Rust-only as a cognify option: Python reaches it only from its eval framework. A CognifyConfig built in code with ExtractionStrategy::Cascade { rounds: 0 } fails validation. The strategy is ignored when a non-llm graph backend replaces LLM extraction. COGNEE_LEGACY_PER_WORD_TOKEN_COUNT is a deliberate divergence from Python. By default a chunk’s size is the tokenizer’s count of the chunk itself. Before SDK-632 it was the sum of the counts of its words, each tokenized in isolation, which over-states the span for sub-word tokenizers (measured 1.78x on Alice in Wonderland: a chunk configured at 8191 tokens held 4,669, and the run made ~1.8x the LLM calls). The default now fills that budget to 8,188 of 8,191. Python 1.5.x still counts per word, so the Rust default is knowingly not byte-for-byte with Python’s chunk boundaries; setting COGNEE_LEGACY_PER_WORD_TOKEN_COUNT=1 restores that parity at the cost of the over-count. Only 1/true/yes/on enable it. Full measurement record: performance/chunk-token-overcount.md.

Graph extraction

The graph_extraction section is registered by the graph-extraction kind. It picks the extractor cognify uses to turn chunks into graph facts. llm is cognify’s built-in LLM extractor. Other kinds (GLiNER, …) are GraphExtractorFactorys that an external crate registers with register_graph_extractor, together with their own config sections. A binding’s per-call graphBackend cognify option (a kind string, or {"kind": …, …tuning}) overrides the default. A kind nothing is registered under is an error; cognify never falls back to the LLM extractor silently. Every operation that cognifies honours the setting: cognify and add-and-cognify in the CLI and the bindings, and the operations that cognify as a sub-step (remember, improve, and the bindings’ update). Each one resolves the extractor before any side effect, so an unregistered kind fails before data is ingested or deleted. Library callers that build a CognifyConfig themselves get the configured backend from ComponentManager::configured_graph_extractor and attach it with with_graph_backend.

Custom graph schema (per call)

A custom graph model is a per-call option, not a setting: there is no env var for it. Python exposes it the same way: graph_model is an argument of cognify() (and a field of the HTTP cognify request), never an environment variable. The same goes for chunk_attachment. The graph_model setting does not select it. The key registry does carry a graph_model row (ConfigManager::set_graph_model, ConfigManager::set("graph_model", …), cognee-cli config set graph_model …, the bindings’ config setters), but nothing reads it: setting it succeeds and has no effect on cognify. That mirrors Python, whose config.set_graph_model stores a value cognify() does not read. Pass the schema per call instead: null and {} mean the default KnowledgeGraph, the same way Python’s HTTP route treats an omitted or empty graph_model. The CLI and the bindings run CognifyConfig::validate before any side effect; a Rust caller runs it itself. An invalid value fails with a validation error: an unknown attachment (rejected when it is parsed), an attachment without a schema, a schema that does not compile, or a schema combined with temporal_cognify, a graph_backend or the cascade strategy. The bindings type-check the whole options object: a wrongly-typed value for any known cognify option is an error, and an unknown key is logged as a warning. Python, by contrast, forwards unknown cognify() keywords into the LLM call instead of raising. See the custom graph schema guide.

Ontology

The ontology section is registered by the ontology-resolver kind. Resolvers are factories (OntologyResolverFactory) on the ComponentRegistry, selected by ontology.resolver; an external crate adds one with register_ontology_resolver. With no file path the no-op resolver is used, whatever the resolver id. Only rdflib is built in; an unregistered resolver id with a file path set is an error. rdflib implements only fuzzy matching and warns on any other strategy. The CLI’s --ontology-file overrides ontology.file_path for one run.

Cognify failure handling

Read by crates/cognify/src/failure.rs. When a stage cannot process one chunk or one file it records the failure — which stage, which file, which chunk, and why — instead of aborting the batch. The report rides every stage output to CognifyResult.failures (on success) or to CognifyError::RunFailed (when the run failed). Whether the run fails is decided once, at the end, from two independent axes. Each knob is a CognifyConfig builder, and each is also a key of the cognify engine section, which Settings::cognify_config applies. They live in the section, not as Settings fields, so GET /api/v1/settings keeps Python’s shape. RAISE_INCREMENTAL_LOADING_ERRORS is Python’s own flag (default true), so one .env configures both SDKs. Its polarity is Python’s: true (raise on the first error) is FailFast, false is RunToEnd. It is a strict boolean: true/1/yes/on is FailFast, false/0/no/off is RunToEnd, and any other value is ignored with a warning, so a typo such as ture keeps the FailFast default rather than silently switching to the costlier run-to-end mode. Python raises only on a literal true (case-insensitive), so in a shared .env use true or false: 1/yes/on (or a typo) mean FailFast in Rust but run-to-end in Python. The other four are Rust-only (COGNEE_COGNIFY_ prefix). An unset value leaves the default in place, and out-of-range values are ignored rather than failing the process: a ratio outside (0, 1] or an unknown rollback scope logs a warning, and a zero cap is dropped silently. Axis 1 — when to stop. FailFast stops the failing stage from scheduling further work once a failure is recorded; files that already completed still travel down the rest of the pipeline. RunToEnd continues past failures and decides at the end (a full run’s LLM cost for the complete failure list). Each stage stops at its own loop boundary: So any dataset under 2000 chunks is one extraction batch, and FailFast saves nothing there; lowering chunks_per_batch tightens the boundary at the cost of serialising the run into waves. In summarization, the first failing chunk closes the gate: undispatched chunks are skipped (their files recorded as unreached), while in-flight calls complete and keep their summaries. A tolerated summarization failure does not close the gate. Axis 2 — what to sweep. WholeRun removes everything the run created; FailedItems removes only the failed files’ contributions and keeps the rest — but only while the run as a whole is survivable (below the failure ratio, with at least one surviving item). Once a FailedItems run is judged failed it escalates to a whole-run sweep that deletes the completed files and their completion markers too. A third variant, Nothing, removes nothing and is not selectable from configuration (nothing/none are rejected like any unknown value, from the env var or the cognify section key); it is reachable only via CognifyConfig::with_rollback_scope(RollbackScope::Nothing), because a run that wrote graph edges and died before the vector stage leaves orphans a retry never repairs. Any combination that sweeps and ends ERRORED returns Err (matching Python, which re-raises after rolling back). A FailedItems run that completes below the ratio returns Ok with the failed-file list in CognifyResult.failures. A sweep removes an artifact only when nothing outside the swept scope still claims it (another run, another dataset, a pre-ownership row, or a surviving file in the same run). Cancellation is swept too — a deliberate divergence from Python — though there is no caller-facing cancel handle yet, and dropping the cognify() future runs no sweep. Per-stage policy. The failure ratio is counted in chunks, evaluated per run, excludes summarization failures, and applies only under FailedItems; if no item survived, the run failed regardless of the ratio. The report cap bounds the individual entries listed (and the error message); totals, the failed-file set and counters are never capped. A run that dies in a persistence stage loses the report of failures collected earlier (persistence errors keep their own CognifyError variants).

Completion markers and incremental loading

incremental_loading (default on) is live. After a successful run, every data item the run finished is marked complete on its Data row, under pipeline_status["cognify_pipeline"][<dataset id>] = "DATA_ITEM_PROCESSING_COMPLETED" — Python’s format byte for byte, so the two SDKs can share a database. The next run reads those markers before it builds a pipeline and skips items already carrying one: they are not classified, chunked, or sent to an LLM.
  • Re-cognifying an already-complete dataset is a no-op. The call returns Ok with already_completed = true and empty payloads. (Previously every cognify re-processed everything.)
  • A failed run marks nothing, and a sweep clears the markers of every item it rolled back, so the next run redoes exactly the work that was lost.
  • A run that completed with failures outstanding is not a pipeline-cache hit, even with use_pipeline_cache on.
A fully skipped Rust run writes no pipeline_runs rows (nothing appears under GET /api/v1/activity/pipeline-runs), whereas Python logs a complete run trail for a repeat cognify — the two SDKs agree on what a repeat run did (nothing) but not on whether it is visible as a run. Temporal runs share the markers. Both branches write and read the one cognify_pipeline key (as in Python), so a cognify() followed by a temporal cognify() over the same dataset is a no-op, and vice versa. To build both graphs over one dataset, or to reprocess everything on every run, set with_incremental_loading(false).