> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cognee.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Rust SDK Cognify Configuration

> cognee-rust settings for chunking, graph extraction, ontologies, cognify failure handling and incremental loading.

Every value here resolves as `defaults < config.json < .env < env < code`
([precedence](https://docs.cognee.ai/rust/configuration.md#precedence)), and can be set from code with
the [config API](https://docs.cognee.ai/rust/configuration.md#runtime-configuration-api) or
[`cognee-cli config`](https://docs.cognee.ai/rust/configuration#cli-config-subcommand).

## Chunking & tokenizer

Read by [`crates/chunking/src/config.rs`](https://github.com/topoteretes/cognee-rs/blob/main/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:

| Env var | `Settings` field | Default |
| - | - | - |
| `COGNEE_CHUNK_SIZE` / `CHUNK_SIZE` | `chunk_size` | *(unset — auto)* |
| `COGNEE_CHUNK_OVERLAP` / `CHUNK_OVERLAP` | `chunk_overlap` | `10` |

`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):

| Env var | Key | Purpose | Default |
| - | - | - | - |
| `COGNEE_TOKEN_COUNTER` | `cognify.token_counter` | `tiktoken` / `word` / `huggingface`(`hf`) | auto from embedding provider |
| `HUGGINGFACE_TOKENIZER` | `embedding.ollama.huggingface_tokenizer` | model id when counter = `huggingface` | *(empty)* |
| `EMBEDDING_TOKENIZER_PATH` | `embedding.onnx.tokenizer_path` | tokenizer file for the `onnx` provider's counter, used when it exists | `<cache_root_directory>/models/bge-small-tokenizer.json` |
| `COGNEE_LEGACY_PER_WORD_TOKEN_COUNT` | `cognify.legacy_per_word_token_count` | restore the legacy per-word token count (`1`/`true`/`yes`/`on`) | `false` |

`Settings::cognify_config` also applies the batch sizes, the graph-extraction
prompt, the extraction strategy and the summary schema:

| Env var | Key (alias) | Purpose | Default |
| - | - | - | - |
| `CHUNKS_PER_BATCH` | `cognify.chunks_per_batch` (`chunks_per_batch`) | chunks per graph-extraction batch (Python's `CognifyConfig.chunks_per_batch`); `0` keeps the default | `2000` |
| `COGNEE_DATA_PER_BATCH` | `cognify.data_per_batch` (`data_per_batch`) | chunks per temporal event-extraction batch (temporal cognify runs one entity-enrichment LLM call per batch). Named after Python's `data_per_batch` parameter, which has no env var and in Python limits how many data items are processed concurrently; `0` keeps the default | `20` |
| `COGNEE_COGNIFY_EXTRACTION_STRATEGY` | `cognify.extraction_strategy` | `single_pass` (one LLM call per chunk) or `cascade` (Python's multi-round `extract_graph_from_data_v2`: nodes → relationship names → edge triplets, `3 × rounds` calls per chunk, no date hints; ignores a custom prompt). Case-insensitive. An unrecognised value keeps the default, with a warning | `single_pass` |
| `COGNEE_COGNIFY_CASCADE_ROUNDS` | `cognify.cascade_rounds` | rounds per cascade stage (Python's `n_rounds`); `0` keeps the default | `2` |
| `GRAPH_PROMPT_PATH` | `graph_prompt_path` | graph-extraction system prompt. The default and an empty value use the compiled-in prompt. The names of the other prompts Python ships in its prompts directory — `generate_graph_prompt_strict.txt`, `generate_graph_prompt_simple.txt`, `generate_graph_prompt_guided.txt`, `generate_graph_prompt_oneshot.txt` — select those prompts, compiled in, as Python resolves a relative name against that directory. Any other value is a file read as the prompt text, a relative path against the working directory. An unreadable file keeps the built-in prompt, with a warning. Unlike Python, a file is sent verbatim, not rendered as a template (the built-in prompts contain no template syntax; the default prompt's date-hint block is appended in code, and only to the default prompt). | `generate_graph_prompt.txt` |
| — | `summarization_schema` (no env var: `ConfigManager::set_summarization_schema`, `set`, or a loaded config / settings object; not persisted) | JSON schema of the summary output (Python's `summarization_model`); must have a string `summary` | *(unset — the built-in `SummarizedContent` shape)* |

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](https://github.com/topoteretes/cognee-rs/blob/main/docs/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.

| Env var | Key (alias) | Default |
| - | - | - |
| `GRAPH_EXTRACTOR` | `graph_extraction.backend` (`graph_extractor`) | `llm` |

`llm` is cognify's built-in LLM extractor. Other kinds (GLiNER, …) are
`GraphExtractorFactory`s 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:

| Entry point | Graph schema | Chunk attachment |
| - | - | - |
| Rust (`cognee` / `cognee-cognify`) | `CognifyConfig::with_graph_schema(value)` (`parse_graph_schema` reads an object or a JSON string; `graph_spec_to_json_schema` compiles a spec) | `with_chunk_attachment(ChunkAttachment::…)` (`ChunkAttachment::parse`) |
| CLI `cognify`, `add-and-cognify` | `--graph-schema <FILE>` or `--graph-spec <FILE>` | `--chunk-attachment direct\|all` |
| Bindings (Python handle, TS, C, Java) | `graphSchema` opt (alias `graphModel`), or `graphSpec`: an object or a JSON string | `chunkAttachment` opt: `"direct"` or `"all"` |
| Python compat `cognify()` / `add_and_cognify()` | `graph_model=`: a Pydantic model class, a dict or a JSON string | `chunk_attachment=` |

`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](https://docs.cognee.ai/rust/guides/custom-graph-schema.md#entry-points).

## 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.

| Env var | Key (alias) | Default |
| - | - | - |
| `ONTOLOGY_FILE_PATH` | `ontology.file_path` (`ontology_file_path`) | *(empty)* |
| `ONTOLOGY_RESOLVER` | `ontology.resolver` (`ontology_resolver`) | `rdflib` |
| `ONTOLOGY_MATCHING_STRATEGY` | `ontology.matching_strategy` (`ontology_matching_strategy`) | `fuzzy` |

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`](https://github.com/topoteretes/cognee-rs/blob/main/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.

| Builder | Field | Env var | Key | Default |
| - | - | - | - | - |
| `with_failure_stop` | `failure_stop` | `RAISE_INCREMENTAL_LOADING_ERRORS` / `COGNEE_RAISE_INCREMENTAL_LOADING_ERRORS` | `cognify.raise_incremental_loading_errors` (`raise_incremental_loading_errors`) | `FailFast` (unset) |
| `with_rollback_scope` | `rollback_scope` | `COGNEE_COGNIFY_ROLLBACK_SCOPE` (`whole_run` / `failed_items` only) | `cognify.rollback_scope` | `WholeRun` |
| `with_summarization_failure_tolerance` | `tolerate_summarization_failures` | `COGNEE_COGNIFY_TOLERATE_SUMMARIZATION_FAILURES` | `cognify.tolerate_summarization_failures` | `false` |
| `with_chunk_failure_ratio_threshold` | `chunk_failure_ratio_threshold` | `COGNEE_COGNIFY_MAX_CHUNK_FAILURE_RATIO` | `cognify.max_chunk_failure_ratio` | `0.05` |
| `with_failure_report_cap` | `failure_report_cap` | `COGNEE_COGNIFY_FAILURE_REPORT_CAP` | `cognify.failure_report_cap` | `100` |

`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:

| Stage | Stops after | Set by | Default |
| - | - | - | - |
| Chunking | the file that failed | — | one file |
| Graph extraction (standard and custom-graph) | the batch that contained the failure | `chunks_per_batch` | **2000 chunks** |
| Temporal extraction | the batch that contained the failure | `data_per_batch` | **20 files** |
| Summarization | the calls already in flight when the failure came back | `max_parallel_extractions` | **1000 calls** (128 on Android/iOS) |

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.

| Combination | End state |
| - | - |
| `FailFast` + `WholeRun` *(default)* | Stops at the first failed batch, removes everything the run created, marks the run `ERRORED`, returns `Err`. Earlier runs are untouched. Python's default |
| `FailFast` + `FailedItems` | *Below the ratio:* stops at the first failed batch; fully completed files are kept, indexed and marked; failed and never-reached ones are removed and left unmarked. *Above it, or with no survivor:* fatal — everything the run created goes, completed files included |
| `RunToEnd` + `WholeRun` | Pays for the whole run to produce the complete failure list, then removes everything the run created |
| `RunToEnd` + `FailedItems` | *Below the ratio:* every good file is cognified and marked; only failed files are removed and reported. *Above it:* the same escalation — good files are removed too and the call returns `Err` |
| *either* + `Nothing` *(code only)* | Nothing is removed; partially-cognified files are left in place permanently |

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.**

| Stage | Failure unit | Behaviour |
| - | - | - |
| Chunking | data item | Collected; the item fails and contributes neither chunks nor a `Document` |
| Graph extraction | chunk | Collected; the item fails. Ratio-checked under `FailedItems` |
| Summarization | chunk | **Configurable.** Default: the item fails (Python parity). With tolerance on: recorded separately, never fails the item or the run, never counts toward the ratio |
| Temporal event extraction | chunk | Collected; the item fails. Ratio-checked under `FailedItems` |
| Temporal entity enrichment | chunk | Collected. One LLM call covers a whole batch, so the failure is recorded against every chunk that fed the batch and the batch's events are dropped |
| Persistence — graph, vector or ledger writes, and embeddings | run | Always fatal, under every combination |

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)`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.