defaults < config.json < .env < env < code
(precedence), and can be set from code with
the config API or
cognee-cli config.
Chunking & tokenizer
Read bycrates/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
Thegraph_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
Theontology 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 bycrates/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
Okwithalready_completed = trueand 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_cacheon.
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).