Skip to main content
Canonical reference for configuring cognee-rust. The complete, field-level source of truth is the Settings struct and the ConfigManager runtime API — build the rustdoc with cargo doc -p cognee --no-deps --open to browse every field and setter with its type. This page groups those fields by subsystem and gives the env-var name and default for each.

Configuration model

Every behaviour knob is a field on a typed config struct. The environment is a source of defaults for those fields, not the only way to set them: a variable bound to a Settings field is also settable from code (the Rust API, the language bindings, and the CLI). Env vars are read only at a configuration boundary, the one place that turns the environment into a config struct. Adapters, retrievers and pipelines receive resolved values through constructors and BackendBuildContext, and never read the process environment themselves.

Precedence

Lowest precedence first:
  1. Defaults — Settings::default() in crates/lib/src/config.rs.
  2. Persisted config file (CLI only) — JSON at ~/.config/cognee-rust/config.json ($XDG_CONFIG_HOME/cognee-rust/config.json), managed by cognee-cli config. See crates/cli/src/config_store.rs.
  3. .env file — loaded into the process environment by Settings::overlay_from_env() (via cognee_config_core::load_dotenv). The file is found in the working directory or any ancestor. Variables already set in the process environment win over .env.
  4. Process environment — bound by Settings::overlay_from_env().
  5. Explicit code — ConfigManager setters (below), a binding’s constructor settings object, the binding config APIs, and CLI --flags.
So: defaults < config.json < .env < env < code. Parsing notes:
  • Booleans accept true|1|yes|on and false|0|no (cognee_utils::parse_env_bool).
  • Empty env values are treated as unset.
  • Numeric vars that fail to parse are ignored.

Two scopes

Component config sections

Settings that belong to one component are declared by that component, not in Settings. A section is a typed struct declared with cognee_config_core::config_section!. Each field becomes a setting with:
  • a canonical, namespaced key: "<section>.<field>", e.g. graph.ladybug.max_db_size;
  • its env vars, in priority order;
  • optional plain aliases, typically the Python SDK’s flat name, e.g. graph_max_db_size;
  • a secret flag.
The component’s factory returns the section from config_sections(). Registering the factory registers its settings. ComponentRegistry::config_schema() collects the sections of every registered factory, plus any added with register_config_section (for settings that no factory owns, e.g. a server module’s). So the settings a build accepts follow the components it contains:
  • a factory compiled out by a disabled feature brings no keys: its env vars are not read, and setting its keys is an unknown-key condition;
  • a downstream build that registers its own factory (register_llm, register_vector, register_storage, register_ontology_resolver, register_graph_extractor, register_relational, register_session_store, …) brings that factory’s keys, settable through the same ConfigManager::set, binding set and config files as the built-in ones.
ComponentManager::with_registry adds the registry’s sections to its ConfigManager. cognee::config::builtin_schema() is the schema of a default build. A process built on a custom registry must load the environment with the full schema, so the env vars of the registry’s own sections are read too:
  • ConfigManager::from_env_with_schema(&registry.config_schema()?);
  • HandleState::from_env_with_registry(registry) in the bindings;
  • Settings::load_from_env_with_schema(&cognee::config::schema_with(…)?), or the CLI’s load_settings_with_schema.
Adding the sections afterwards makes their keys settable, but cannot read their env vars. Section values travel inside Settings, in its flattened sections map, so config files and binding settings objects carry them unchanged. A factory reads its typed section from the build context with ctx.section::<T>(). A section key, its canonical key and its aliases are all accepted by ConfigManager::set / get; set and unset also clear any alias spelling of the setting. When a loaded config carries one setting under both its canonical key and an alias, the canonical value wins and the alias is dropped with a warning, so a stale flat key (llm_api_key from an older config file) never overrides what an env var, a CLI flag or set wrote under llm.api_key. cognee-cli rewrites those flat keys to canonical ones when it loads a pre-v4 config file. The TS and Java constructors also accept a section key with its dots flattened to underscores (llm_debug_requests), so the camelCase keys a TS config.get() returns can be passed back to new Cognee(...): a section key with a Python alias reads back under the alias’s camelCase (llm.auto_rate_limit -> autoRateLimit), one without an alias with its dots as word breaks (llm.debug_requests -> llmDebugRequests). Two sections claiming the same key, alias or primary env var is a registration error. So is a section claiming a Settings key.

Unknown keys and strict mode

A key can be unknown because it is misspelled, or because the component that owns it is not compiled into, or registered with, this build. The two look the same, so the policy is a setting: cognee-cli config unset <key> removes a stale entry even in strict mode, so a config can always be repaired. cognee-cli config set / unset see strict mode the way every other command does: the saved strict_config with COGNEE_STRICT_CONFIG overlaid. A value of the wrong type for a known key is always an error. From set it fails the call; in a loaded config it is dropped (the setting keeps its default) and the error is raised at first use, as for an unknown key in strict mode, until the key is set again. A loaded config may also carry keys under a name serde does not know: a section key’s flat alias (such as llm_args for llm.args, or embedding_model for embedding.model) or the in-memory summarization_schema setting. Those are applied as the setting they name, not treated as unknown. A setting marked secret (a registry row or a section key) is masked as ***REDACTED*** in every binding’s config.get() output.

Resolving from something other than the process environment

The boundary loaders read through an EnvSource (from the cognee-config-core crate), so you can hand them a MapEnv — an in-memory set of variables — instead of mutating the shared process environment:
  • Settings::overlay_from(&dyn EnvSource) — same rules as overlay_from_env() (only set, non-empty variables change a field), but it reads only the source it is given and does not load .env. EnvSource and MapEnv are re-exported as cognee::config::{EnvSource, MapEnv}.
  • LoggingConfig::from_env_source(&dyn EnvSource)
  • EnvSettingsView::from_env_source(&dyn EnvSource) (observability; and overlay)
  • TelemetryConfig::from_env_source(&dyn EnvSource) (product analytics)
The from_env() / overlay_from_env() entry points are thin boundaries over these. A few conventional variables are deliberately read straight from the environment and are not part of any config struct: proxy variables, HOME, the AWS default credential chain, COGNEE_CONFIG_HOME (it locates the config file itself), COGNEE_BINDING_SUPPRESS_LOGS (read at module load, before user code runs) and LOG_FILE_NAME (hands the log file to child processes). Inside cognee-rs, the rule is enforced by clippy::disallowed_methods in the root clippy.toml and the scripts/check_env_boundary.sh ratchet. Contributors adding a setting should follow the checklist in upstream docs/configuration.md § Adding a setting.

LLM

The llm section, registered by the LLM kind and shared by every LLM adapter (see Component config sections). Each key also answers to its Python flat name, the alias shown in brackets. The deep reference is CLI: LLM retries for retries and the cognee-llm rustdoc for the adapter. Adapter sections, registered by their factory and present only when it is: LLM_MAX_COMPLETION_TOKENS is a completion-token budget, and it does double duty: besides capping the option-less generate call, it is the LLM term in cognify’s chunk-size auto-calculation (min(<embedding engine limit>, llm_max_completion_tokens / 2) — Python’s get_max_chunk_tokens()). Lowering it for a small-context endpoint therefore shrinks the prompts cognify sends. On adapters that know their model’s documented output cap (Anthropic, Bedrock) the configured value is clamped to it first, as Python does via litellm’s model_cost.
Structured-output calls do not apply this ceiling — option-less ones send the built-in 16384 default. Cognify’s extraction calls send no cap at all (Python parity).
LLM_ARGS is the escape hatch for that last case. It takes a JSON object whose keys are merged into each chat and structured-output request body — the port of Python’s llm_config.llm_args and its {**self.llm_args, **kwargs} merge, so an explicitly-passed option always wins over an LLM_ARGS key of the same name. Its practical use is giving the extraction path more output headroom: because those calls send no max_tokens, the provider’s own default applies, and a low one (Baseten’s is 4096) truncates the structured JSON mid-object. The adapter detects that case (finish_reason: "length") and re-asks at a raised budget, but starting from a sufficient budget avoids the wasted first attempt.
Three details worth knowing. A bare max_tokens is folded into max_completion_tokens for reasoning models, and sampling params those models reject (temperature, top_p, frequency_penalty, presence_penalty) are dropped rather than passed through. A malformed value, or valid JSON that is not an object, is ignored with a warn! instead of aborting startup. And the vision/transcription path does not see it at all: it bypasses the merge. A fallback LLM (llm.fallback_provider/_model/_endpoint/_api_key, aliases llm_fallback_*) is configurable programmatically (no env binding) but not used yet. MOCK_LLM + cassettes power the offline benchmark — see performance/mock-benchmark.md.

Supported LLM providers

Several providers are OpenAI-compatible HTTP endpoints, so they route through the same adapter — differing only in base URL and litellm-style model-prefix stripping. LLM_API_KEY is required for every provider except bedrock (matching the Python SDK; for a local Ollama any non-empty value works). On bedrock an unset or empty LLM_API_KEY is a supported configuration — it falls through to SigV4 signing rather than erroring (see Bedrock below). The OpenAI-only request quirks are gated on the api.openai.com host, so they never fire against another endpoint. LLM_ENDPOINT always overrides the default when set on the OpenAI-compatible providers. Audio transcription (Whisper) is wired only for openai and custom/openai_compatible (which may expose /audio/transcriptions); ollama/mistral/gemini/bedrock get graceful no-audio. anthropic uses a native Messages-API adapter (not the OpenAI-compatible factory): it authenticates with x-api-key, hoists the system prompt into the top-level system field, and produces structured output via a forced tool_use. It requires LLM_API_KEY, strips an anthropic/ model prefix, honors LLM_ARGS, and supports image description (vision) but not audio transcription. The endpoint defaults to https://api.anthropic.com/v1 and deliberately ignores LLM_ENDPOINT/OPENAI_URL so a stray OPENAI_URL cannot misroute Anthropic traffic (matching Python, which passes no base URL). To route Anthropic traffic through a proxy / gateway, set the dedicated ANTHROPIC_BASE_URL (alias ANTHROPIC_API_BASE); other providers ignore it. azure reuses the OpenAI request path with Azure’s auth and URL conventions: it authenticates with the api-key header and appends an ?api-version=<v> query. Set LLM_PROVIDER=azure, LLM_API_KEY, LLM_API_VERSION (e.g. 2024-12-01-preview), and LLM_ENDPOINT to the deployment URL (https://<resource>.openai.azure.com/openai/deployments/<deployment>). Azure ignores the model in the request body for routing, but LLM_MODEL still selects the reasoning-model parameter shape, so for an o-series/gpt-5 deployment set LLM_MODEL to the underlying model name (a .../deployments/o3 style segment is also treated as reasoning), or pin it with LLM_REASONING.

Bedrock

LLM_PROVIDER=bedrock is a native adapter over Amazon Bedrock’s Converse API (POST {endpoint}/model/{modelId}/converse), not an OpenAI-compatible endpoint. Its AWS settings live in the aws config section (registered by the Bedrock LLM and embedding factories), so they can be set like any other section key: with set, in a config file or through the bindings, as aws.access_key_id or its flat alias aws_access_key_id, and so on. A field left unset there falls back to the AWS_* environment variable of the same name inside the adapter. The region has no env binding in the section (aws.region, alias aws_region_name); the adapter applies the env rungs of the region chain itself. The env names mirror the ones litellm reads, and several of them are not the boto3-standard spellings: Auth ladder. LLM_API_KEY, when set, is sent verbatim as Authorization: Bearer <key> and the request is not signed — no credential lookup runs, so a bearer-only deployment needs no AWS credentials. Otherwise AWS_BEARER_TOKEN_BEDROCK takes the same short-circuit. Only when both are absent does the request fall through to SigV4 (signing service bedrock), whose credentials resolve in this order:
  1. web-identity token + role + session name → STS AssumeRoleWithWebIdentity;
  2. role ARN → STS AssumeRole — skipped when the process is already running as that role, so an IRSA pod that already holds it does not assume itself;
  3. profile name → the shared-config profile;
  4. static access key + secret (plus session token when present);
  5. otherwise the default chain (env / shared config / SSO / ECS / IMDS).
Region and endpoint. The region resolves as: an explicitly configured region → a region embedded in a model ARN → AWS_REGION_NAME → AWS_REGION → the profile / default chain → the hard default us-west-2. The endpoint resolves as: an explicit API base → AWS_BEDROCK_RUNTIME_ENDPOINT → https://bedrock-runtime.{region}.amazonaws.com. LLM_ENDPOINT is not that API base: like the Anthropic path, the Bedrock factory deliberately passes no base URL, so a stray LLM_ENDPOINT cannot misroute Bedrock traffic. Use AWS_BEDROCK_RUNTIME_ENDPOINT for a custom runtime host. Converse only. Every model cognee ships routes to Converse after the model id is normalised (an ARN wrapper is unwrapped; cross-region inference prefixes such as eu./us./apac. and provisioned-throughput / context-window suffixes are stripped). Normalisation feeds routing and capability decisions only; the request URL always carries the model id exactly as configured. A model that routes to Bedrock’s legacy invoke API for chat is unsupported: construction fails with LlmError::FeatureNotSupported. (Embeddings are a separate path and do go over InvokeModel — see Embedding.) Capabilities. No streaming. No audio transcription. Image description (vision) is implemented over Converse image blocks — deliberately exceeding Python parity — but gated on a per-model capability table (caps.rs): an unlisted model is treated as text-only and returns LlmError::FeatureNotSupported. Structured output is capability-gated from the same table: models that advertise native structured output get Converse’s outputConfig.textFormat carrying the JSON schema, the rest get a synthetic json_tool_call tool (with toolChoice forced only for models that support it). Feature gating. The adapter and the embedding engine live behind the bedrock Cargo feature. It is non-default on the two leaf crates that carry the code (cognee-llm, cognee-embedding), but it is in the default feature list of cognee-components, cognee and cognee-cli (and of cognee-http-server), so every shipped binary has Bedrock available. Building with --no-default-features (or without bedrock) drops the whole AWS stack, and LLM_PROVIDER=bedrock then reports an unsupported provider.

Reasoning-model parameters

OpenAI reasoning families (o1*, o3*, o4*, gpt-5*) require a different request shape — max_completion_tokens instead of max_tokens, and no temperature/top_p/penalties. The adapter auto-detects this from the model name for any non-local endpoint (loopback and Ollama’s port keep the legacy shape). LLM_REASONING overrides that detection:
  • auto (default) — name/host auto-detection as above.
  • always — force the reasoning shape (e.g. a proxy that hides a reasoning model behind an opaque alias).
  • never — force the legacy max_tokens + temperature shape (e.g. a gateway serving a reasoning-named model that only accepts the legacy parameters).

Structured-output request shape

Nothing in the OpenAI-compatible protocol advertises which structured-output shape a server understands, so by default the adapter cascades: native tools, then legacy functions, then response_format: {"type": "json_object"}. The first shape that yields a parseable payload wins. The adapter keeps a per-mode miss counter and stops sending a mode that has produced nothing three calls running (see How retries and pacing interact). LLM_STRUCTURED_OUTPUT_MODE skips the discovery when you already know the answer:
  • auto (default) — cascade as above, bounded by the miss counter.
  • json_schema — try constrained decoding first, then cascade. See below.
  • tools — only native tool-calling.
  • functions — only the legacy functions/function_call pair.
  • json — only JSON mode.
Pick json when the server rejects tools/functions outright. If it merely ignores them — accepting the request and echoing JSON in content — leave it on auto: tool-calling mode already succeeds there in one request, and it is the only mode that puts the real JSON schema on the wire (JSON mode sends a prose example derived from the schema, which means weaker schema pressure). Pinning a mode means the other modes are never sent, and exhausting the pinned mode fails the call rather than falling through — the error names the pin. An unrecognised value falls back to auto. This is the counterpart of Python cognee’s llm_instructor_mode. json_schema — constrained decoding. json_schema is not a pin. It sends
ahead of the cascade, and falls back to it when the endpoint says no. On a backend that supports it the sampler cannot emit a token that would break the schema. It is opt-in because there is no capability table for an arbitrary OpenAI-compatible base URL, and some backends (Baseten’s gpt-oss-120b) answer HTTP 501 to a constrained request. The mode demotes rather than failing:
  1. strict: true with the all-required / additionalProperties: false schema;
  2. on rejection, the same envelope without strict, carrying the ordinary schema;
  3. on rejection again, out of the mode entirely: the tools → functions → json cascade runs exactly as under auto.
Only an outright refusal of the request shape moves that ladder — HTTP 501, or an HTTP 400 whose body names response_format / json_schema / schema / strict. Rate limits, other 5xx, network errors, model refusals and unrelated 400s leave the (process-lifetime, monotonic) memo untouched. The probe costs one request and is single-flighted per schema. A schema whose root is not an object never enters the mode, and a schema using a feature outside OpenAI’s strict subset (a map field, patternProperties, if/then/else) is refused and demoted. The Bedrock adapter does not read this knob; it picks Converse’s native outputConfig.textFormat.jsonSchema or a synthetic json_tool_call from its own capability table.
Ollama embeddings: set EMBEDDING_ENDPOINT explicitly when using EMBEDDING_PROVIDER=ollama. The Ollama embedder needs the /api/embed route, and when EMBEDDING_ENDPOINT is unset the embedding endpoint falls back to LLM_ENDPOINT, which for Ollama points at the /v1 chat base, so the embedder would POST to the wrong path. Set EMBEDDING_ENDPOINT=http://localhost:11434/api/embed.

Embedding

The embedding section is registered by the embedding kind and shared by every provider. Each provider’s factory registers the settings only it reads (see Component config sections). The Python flat name of each key is accepted as an alias, shown in brackets. Provider sections, registered by the provider’s factory and present only when it is compiled in: Provider values: onnx, fastembed, openai, openai_compatible, ollama, bedrock, mock, plus any provider a custom registry adds with ComponentRegistry::register_embedding. A provider whose factory is not registered (a misspelling, or a backend compiled out) is an error, never a silent ONNX fallback. MOCK_EMBEDDING diverges from the Python SDK, deliberately. Python’s embedding engines return [0.0] * dimensions under a truthy MOCK_EMBEDDING. Rust defaults to SHA-256-derived vectors instead, because zero vectors are silently discarded by cosine-distance KNN (the distance is NaN, and LanceDB filters NaN rows out), making retrieval vacuously empty. The mock engine is test-only. Set MOCK_EMBEDDING=zero to reproduce Python’s behaviour. EMBEDDING_PROVIDER=bedrock uses Amazon Bedrock’s InvokeModel API (POST {endpoint}/model/{modelId}/invoke) — Converse is never used here. Two model families are supported, selected from the normalised model id: Titan (amazon.titan-embed-text-v1, amazon.titan-embed-text-v2:0, amazon.titan-embed-image-v1), which takes one text per request, and Cohere (cohere.embed-*), which batches. Set EMBEDDING_MODEL to the Bedrock model id; EMBEDDING_DIMENSIONS drives the engine’s reported dimension() and is sent as dimensions on Titan v2, so it must match what the model returns. AWS configuration comes from the same AWS_* variables as the LLM path (see Bedrock), and EMBEDDING_API_KEY (falling back to LLM_API_KEY) is the Bedrock API key, which short-circuits to a bearer header with no SigV4; leaving it unset runs the ambient credential ladder. Returned vectors are always L2-normalised (server-side on Titan v2 via normalize: true, client-side for the others).
Bedrock embeddings and EMBEDDING_ENDPOINT: through Settings, EMBEDDING_ENDPOINT defaults to empty, and a Bedrock embedder inherits LLM_ENDPOINT only when the LLM provider is also bedrock. Direct users of EmbeddingConfig::default() start from the OpenAI base URL instead; the Bedrock engine ignores an endpoint that points at api.openai.com rather than POSTing Bedrock bodies there. Set it only to a real Bedrock runtime host (or use AWS_BEDROCK_RUNTIME_ENDPOINT).

Vector database

The vector section is registered by the vector kind. Backend-specific settings are registered by the backend’s factory, for example vector.pgvector. Python flat names are accepted as aliases, shown in brackets. Supported providers:
This list is the built-in set, not the set of valid values. Any crate can register an additional provider under any id on a ComponentRegistry, and a binary that injects that registry (ComponentManager::with_registry, or HandleState::from_settings_with_registry in the bindings) accepts the id in VECTOR_DB_PROVIDER / GRAPH_DATABASE_PROVIDER — including Postgres-backed providers, which receive the assembled vector_postgres_url / graph_postgres_url from the *_HOST / *_PORT / *_NAME / *_USERNAME / *_PASSWORD settings exactly as the built-ins do, whether or not the pgvector / pggraph features are compiled in. Registering over a built-in id replaces it. See External providers.
  • lancedb — embedded Apache-Arrow / Lance vector store, on disk. The default provider, and default-on in cognee, cognee-cli and every binding; requires the lancedb Cargo feature (the Arrow/Lance native stack is a large build, so a consumer on another vector store can drop it — and must then set vector_db_provider explicitly). Unavailable on Android, where its native stack does not cross-compile. The on-disk layout matches the Python SDK’s default LanceDB store, so a Rust deployment can be opened from Python and vice versa.
  • brute-force — pure-Rust in-memory linear scan. Default on Android. Selected on any target by setting vector_db_url = ":memory:".
  • pgvector — Postgres + the pgvector extension; requires the pgvector Cargo feature on the binary build (default-on in cognee-cli). See Pluggable Backends for HNSW indexing and cognee-cli vector-reindex.
Qdrant is not part of OSS. See Pluggable Backends. Setting vector_db_provider to qdrant is rejected at component initialization in OSS (it returns a config error rather than falling back).

pgvector index tuning

The vector.pgvector section, registered by the pgvector factory (pgvector builds only): four optional overrides for the adapter’s HNSW build, applied per adapter and clamped to the range shown. Keys are vector.pgvector.hnsw_m, .hnsw_ef_construction, .maintenance_workers and .maintenance_work_mem_mb, with aliases pgvector_hnsw_m and so on. Leave them unset unless a measurement says otherwise: the defaults are calibrated against a 100k-point corpus of 384-d embeddings, and the two that cost server resources exist so they can be turned down on a small or shared Postgres without patching the crate.
  • m / ef_construction apply to indexes built after the change. One already on disk keeps the parameters it was built with until it is rebuilt — create_missing_vector_indexes() (cognee-cli vector-reindex) only builds a missing or invalid index, never a valid one — so raising m on an existing store is a DROP INDEX + reindex, done deliberately.
  • ef_construction’s floor follows m because pgvector requires ef_construction >= 2 × m; no combination of these two can produce a CREATE INDEX that fails.
  • Asking for more parallel maintenance workers than the server can start is not an error. Postgres launches as many as the server’s max_parallel_workers / max_worker_processes still allow, down to zero (a serial build). The only cost of over-asking is a slower build, so no server-side configuration is required to adopt the defaults.
  • Surrounding whitespace in an env value is ignored. A blank, negative or non-numeric env value is ignored and the default applies; an out-of-range value (however large) clamps. A typo in a deployment environment should not take the store down.

Graph database

The graph section is registered by the graph kind; backend sections such as graph.ladybug are registered by their factory. Python flat names are accepted as aliases. Supported providers: ladybug/kuzu (embedded), postgres (feature pggraph). As with the vector store, these are the built-ins; an externally registered provider id is accepted too (see the note under Vector database). The default is derived from the graph backends the binary was compiled with, not hardcoded. In order:
  1. ladybug, when that feature is on — every default build;
  2. otherwise postgres, when pggraph is on (e.g. a --no-default-features … pggraph image), however many other ids are registered;
  3. otherwise the single registered backend, when the build has exactly one.
Only a build registering two or more backends of which neither is ladybug nor postgres has to set GRAPH_DATABASE_PROVIDER explicitly. When Postgres graph credentials are unset they fall back to the relational DB_* config (see roadmap/cognify-compatibility-plan.md).

Relational database

The relational section is registered by the relational kind. Backends are RelationalDbFactorys on the ComponentRegistry, selected by relational.provider (when that is empty, by the scheme of the resolved URL). An external crate adds a backend with register_relational, together with its own config sections; it implements cognee_database::RelationalDb. The built-in SeaORM factory is registered as sqlite (feature sqlite) and postgres / postgresql (feature postgres). An unregistered provider is an error. For postgres the URL is built from the connection parts; otherwise relational.url is used verbatim. db_sslmode and single_process stay cognee-wide Settings keys: the TLS mode is appended to every Postgres URL cognee builds (relational, pgvector and the Postgres graph), and single_process gates startup recovery.

Single-process startup recovery

single_process / COGNEE_SINGLE_PROCESS asserts that one process owns this database. In practice it is an opt-in: startup recovery does nothing until you turn it on. The setting is tri-state: null restores the derived answer (as for chunk_size). The env var is parsed by the same code as the config key; a blank or whitespace-only value means “derive”, not false. The derivation resolves true only for an in-memory SQLite URL (sqlite::memory:, ?mode=memory) and false for everything else — Postgres and file-backed SQLite, including the shipped default sqlite:./cognee.db?mode=rwc. An in-memory database cannot hold a leftover from a dead run, so the derived true gives the sweep nothing to do. Which surfaces read it. The HTTP server never loads the CLI config file and builds no Settings, so its environment is the only channel. The CLI runs no sweep on any command: every invocation is its own process, and two concurrent cognee-cli cognify runs against one SQLite file are exactly the case the claim exists to catch. Set it before the first operation. The setting is read once per relational database per process, on the first operation that opens that database. Flipping it on afterwards is ignored for the rest of that process’s life (the process may have live runs a sweep could not tell apart from a dead predecessor’s); a binding that notices the flip logs a WARN. Restart to recover a dataset that is already wedged. What it turns on. Startup recovery for a run killed mid-flight (SIGKILL, an OOM kill, Android killing the app). A killed run leaves three things behind, cleared in this order:
  1. What it had already written into the graph and vector stores, recorded in the ownership ledger against its pipeline_run_id — rolled back through the same RunSweeper the forget path uses, scoped strictly to the dead run in its own dataset (artifacts another run also claims are kept);
  2. The pipeline_runs row left at Started — read first by the run gate and never expiring;
  3. The exclusive-run claim on (dataset, pipeline) — otherwise expiring 24 h later.
Retiring the status row happens after the rollback (as in Python’s cognify_rollback_handler). A rollback that fails is logged and the status row is retired anyway, so the dataset never wedges permanently. Retiring an orphan writes a new Errored row in Rust, where Python resets the run to INITIATED; both resolve to “you may run” at the gate, but a status poller sees a failed build on Rust versus a never-started run on Python. Who needs to set it. Any single-process deployment on a file-backed database: an Android app, a desktop app, a one-process service, a CLI-only setup. Without it nothing regresses — the claim still ages out after 24 h and the orphaned row is still cleared by cognee-cli pipeline-unblock --clear or an HTTP-server restart — but nothing else removes what a killed run wrote. Set it to false to pin the safe behaviour in a config file.

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

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). The search and search.hybrid engine sections tune the standard retrievers; cognee hands them to SearchBuilder::with_tuning (Settings::search_tuning).

Triplet retriever hub cap

Opt-in, and it changes results. Unset — the default — the triplet retriever loads the full depth-1 neighbourhood of its seeds, which is result-identical to loading the whole graph. A cap truncates hub fan-out: edges from a hub seed to its dropped neighbours never reach the ranking. Those edges would carry the non-seed distance penalty and rank below seed-to-seed edges, but they could still have made the top k.
Measured on a synthetic 100k corpus, a cap of 100 keeps a Jaccard similarity of 0.93–0.96 against the uncapped result set, with a worst query at 0.67; a cap of 200 keeps 0.92–0.93 but has a worst query at 0.18. The latency it buys is small on a uniform corpus and large on real hub-heavy text (one graph_completion search went from 332 ms to 76 ms). Treat it as a knob for a corpus with known hubs, measured on that corpus, not as a default. PgGraphAdapter applies the cap in SQL; every other backend gets the same semantics from GraphDBTrait::get_neighborhood_scoped’s client-side default.

Hybrid retriever knobs

The HYBRID_COMPLETION search type (SearchType::HybridCompletion) accepts a set of per-request tuning knobs. These are library / orchestrator-only — typed Option<...> fields on SearchParams, populated from the snake_case JSON keys inside SearchRequest.retriever_specific_config. They have no env binding.
Not reachable from the CLI. cognee-cli search hardcodes retriever_specific_config: None, so these knobs are reachable only from a Rust caller that builds a SearchRequest with a populated retriever_specific_config, or from a language binding, which forwards its retrieverSpecificConfig search option verbatim (camelCase option name, snake_case knob keys inside).
When a knob is unset the retriever falls back through retriever_specific_config.<knob> → SearchRequest.top_k capped at 10 → the HybridRetriever::new constructor default (an explicit per-lane knob is never capped): Chunk-lane fusion. chunk_lane_fusion chooses how the DocumentChunk_text lane and the TextSummary_text lane are combined:
  • "relative_score" (default) normalises each lane’s similarities to [0, 1] against that lane’s own candidates and adds them, the summary lane weighted by summary_lane_weight (the chunk lane always weighs 1.0).
  • "reciprocal_rank" is Python’s rank_chunk_summary_pairs verbatim — RRF over ranks only.
The default diverges deliberately: RRF’s constant is calibrated for candidate lists of thousands, and on these short lists “present in both lanes” outranks “similar to the query”. Measured over 15 probe questions on Alice in Wonderland, relative-score fusion raised MRR in all four tested configurations (0.35–0.54 → 0.47–0.56). Set chunk_lane_fusion = "reciprocal_rank" to get the Python ranking back. Reserved (Phase 2, inert). These keys are parsed and stored but have no effect yet: use_truth_weight (default false), include_global_context_index (default false) and global_context_index_top_k (default 3). The HTTP-facing degradation notes live with the server’s search-router docs, which are not part of the cognee-rs repository.

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.

File storage

The storage section is registered by the storage kind. Backends are factories (StorageFactory) selected by storage.backend; an external crate adds one (an object store, say) with register_storage. Only local (files under data_root_directory) is built in. An unregistered backend id is an error. No built-in backend reads storage.bucket_name.

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.

System paths, users & datasets

Setting system_root_directory cascades to the default graph_file_path and vector_db_url unless those are set explicitly.

Session / cache & rate limiting

The session (Q&A history) store is chosen by the session section, registered by the session-store kind. Stores are SessionStoreFactorys on the ComponentRegistry, selected by session.store; an external crate adds one with register_session_store. A factory is handed the shared relational database. Built in:
  • relational, also registered as seaorm: the session tables in the relational database (any RelationalDb). This is the default.
  • fs (crate feature session-fs): JSON files under session.fs.directory.
  • redis (crate feature session-redis): Python’s Redis layout, at session.redis.*.
A store compiled out by its feature brings no keys: in a default build the CACHE_HOST … variables are not read, and setting cache_host is an unknown key. An unregistered store id is an error.
Behaviour change for cognee-cli and the bindings. cognee-cli (search/recall/remember/improve/bench) and the Python/TS/Java/C bindings used to build the relational store unconditionally and ignore COGNEE_SESSION_STORE. They now honour session.store like every other entry point — but they ship only the relational store (neither session-fs nor session-redis is enabled in their builds). So fs or redis, values the HTTP server accepts, are now an error there (“unsupported session_store …”). If such a process shares the server’s environment, unset COGNEE_SESSION_STORE or set it to relational for it.

How retries and pacing interact

These knobs form one resilience stack, matching Python cognee’s:
  • Two retry counts, one per loop. LLM_MAX_RETRIES (Python’s _MAX_VALIDATION_RETRIES) is the number of attempts at one structured-output call — the first ask plus corrective re-asks, so the default 3 is at most two re-asks. Every adapter floors it at 1. LLM_NETWORK_RETRIES (Python’s stop_after_attempt(2)) is the transport ladder underneath, for 429s, 5xx and connection failures. They are separate because they nest; one shared value multiplied the ladders into each other.
  • Retrying stops only once BOTH floors are met — at least LLM_NETWORK_RETRIES attempts and at least LLM_MIN_RETRY_SECONDS elapsed. The time floor lets a call outlast a provider’s rate-limit window. Backoff is exponential with jitter, 8s doubling to a 128s ceiling, and a Retry-After hint of up to 60s is honoured; a longer hint is ignored and the normal backoff applies. Set LLM_MIN_RETRY_SECONDS=0 to fail fast on attempts alone. Time spent queued for an LLM_MAX_PARALLEL_REQUESTS slot does not count against the floor.
  • Bedrock makes one attempt more. Its attempt floor is LLM_NETWORK_RETRIES + 1 (so 2 buys three attempts there), and the time floor applies to it like everywhere else. Bedrock ignores Retry-After, so its gaps are always the 8s-to-128s ladder.
  • Three timeouts, three scopes. LLM_CONNECT_TIMEOUT_SECONDS bounds the TCP handshake; LLM_REQUEST_TIMEOUT_SECONDS bounds one HTTP request (0 means “no limit” for both, matching curl). Both apply to the OpenAI-compatible (including Azure), Anthropic and Bedrock adapters; the Responses client uses a fixed 600s request / 10s connect pair. LLM_REQUEST_DEADLINE_SECONDS bounds one logical structured-extraction call and is the only bound on total time. Keep it above LLM_MAX_RETRIES x LLM_MIN_RETRY_SECONDS (720s at the defaults) and above 900s: the deadline includes time spent paced and queued, and a provider 429/503 opens a 900-second pacing episode. The 1200 default clears both; a warning is logged at startup if the deadline does not fit the computed ladder. The deadline gates starting new work rather than cancelling in flight, so the true ceiling is LLM_REQUEST_DEADLINE_SECONDS + LLM_REQUEST_TIMEOUT_SECONDS; retries’ pacing and slot waits are bounded by the remaining budget, while the first attempt may wait out a whole episode. Set it to 0 to disable.
  • The cascade stops sending a mode that never produces anything. On an endpoint without a server-side tool parser (e.g. vLLM started without --enable-auto-tool-choice --tool-call-parser), tools and functions can never produce a payload. The OpenAI-compatible adapter tracks, per endpoint and per mode, three consecutive structured calls that exhausted their attempts with no native payload, then skips that mode, re-probing once every 64 calls so a redeploy recovers without a restart. Errored requests (4xx/5xx, resets, deadline aborts) and truncated responses do not count against a mode, and any answered call clears its count. JSON mode is never skipped. There is no setting; transitions are logged at warn.
  • Concurrency and rate are separate ceilings. LLM_MAX_PARALLEL_REQUESTS bounds how many LLM requests are in flight at once (enforced inside the LLM transport, so it applies on every surface); the LLM_RATE_LIMIT_* bucket bounds how often they start. A slot is held for the HTTP exchange itself and released while a request waits for pacing or a retry backoff. It defaults to 1000 (matching the connection-pool limit openai-python and litellm set), and 128 on Android/iOS.
  • Pacing is off until it is needed. With LLM_RATE_LIMIT_ENABLED=false (the default) requests dispatch unbounded up to the concurrency ceiling. When the provider reports overload — HTTP 429, 503, 529, or a timeout — AUTO_RATE_LIMIT (default on) opens an episode: for the next 15 minutes dispatch is paced at LLM_RATE_LIMIT_REQUESTS per LLM_RATE_LIMIT_INTERVAL seconds. Fresh overload extends it; it lapses on quiet. Set LLM_RATE_LIMIT_ENABLED=true to pace from the start, or AUTO_RATE_LIMIT=false to never pace automatically.
  • Some failures are never retried, because no wait can fix them: bad request, authentication, unknown model, payment required, and a 429 whose body reports exhausted quota or billing rather than a per-minute limit.
Embedding requests have their own EMBEDDING_RATE_LIMIT_* bucket, which is flag-gated only — provider overload does not switch it on automatically.

Logging

Canonical table. Binding READMEs and .env.example link here. cognee writes structured logs to stdout and (when writable) to a rotating file. File logging is owned by cognee-logging: the CLI installs it at startup, the language bindings when you call their setup_logging entry point. Exception — machine-readable CLI output. cognee-cli sends console logs to stderr for any invocation whose stdout is a contract with a program: search/recall with -f json or -f simple, and export, visualize and bench, which print a path or a JSON document. Human-facing pretty output, and every other subcommand, still log to stdout.
Logging configures a process-wide singleton (the tracing subscriber), so it is process config: one LoggingConfig, resolved once, at startup. Its only window onto the environment is LoggingConfig::from_env_source (the from_env() boundary). Every field is also settable from code: assign it, or use LoggingConfig::set(key, value) with the option keys below. Precedence: defaults < environment < options from code.
  • CLI. The CLI loads Settings before it installs logging, so the two Settings keys log_level and logs_root_directory (default empty: use the logging default) are lowered into the logging config. Each fills only what the logging environment left unset: RUST_LOG > LOG_LEVEL > the config file’s log_level > the default filter, and COGNEE_LOGS_DIR > the config file’s logs_root_directory > ~/.cognee/logs. So cognee-cli config set log_level debug persists a level.
  • Bindings. A binding installs logging from a module-level function that runs before any handle exists, so a handle’s Settings (and config.set("log_level", …)) do not affect logging there. Pass the options to the entry point instead; they win over the environment. The first call wins, later calls are no-ops.
Option keys are snake_case; the bindings also accept camelCase (logLevel). An unknown key or an invalid value is an error (Python ValueError, a JS exception, CogneeException with VALIDATION_ERROR, C return code 2 with cg_last_error_message()). The default bridge subscriber a binding installs on import (into Python logging, or stderr) reads its level filter through the same LoggingConfig boundary (RUST_LOG > LOG_LEVEL > default).
Multi-process warning — when several cognee processes share one log file via LOG_FILE_NAME, rotation is not coordinated; concurrent rotation can corrupt the log. For sharded workers, give each shard its own COGNEE_LOGS_DIR (or unset LOG_FILE_NAME per shard).

Observability & telemetry

cognee emits OpenTelemetry traces (behind the telemetry feature) and opt-out product analytics. The deep references are observability/opentelemetry.md and observability/send_telemetry.md.

OpenTelemetry traces

Precedence: defaults < environment < code. The exporter is process-wide too, but where it reads its settings depends on who initialises it:
  • CLI — from Settings (defaults < config file < env < --flags), so cognee-cli config set otel_exporter_otlp_endpoint … works.
  • Rust embedders — cognee::telemetry::init_telemetry(&settings) takes any SettingsView: a Settings, or an EnvSettingsView (env only, plus EnvSettingsView::set(key, value) from code).
  • Bindings — from a module-level function that runs before any handle exists, so a handle’s Settings do not apply. The binding’s default service name (cognee.python-binding, cognee.node-binding, cognee.java-binding, cognee.capi-binding) is a default: OTEL_SERVICE_NAME and an explicit otel_service_name option both win over it, and the process environment is never modified. Options use the Settings key names above (camelCase also accepted):

Product analytics

Product analytics are on by default (opt-out, matching the Python SDK). The switch is process-wide: one TelemetryConfig governs every event the process sends, whichever handle emits it. It is not a Settings key and not a cognee-cli config key. Precedence: defaults (on) < environment < code. The environment is read when an event is sent, so a .env loaded when the first handle is built, or a variable set later, is honoured. An explicit call wins over the environment in both directions and stays in force until the next one: set_enabled pins only the switch, configure pins every field, reset_to_env returns to the environment. Disabling also drops events that are queued but not yet sent. The existing setup_telemetry_analytics() / setupTelemetryAnalytics() / Cognee.initTelemetry() / cognee_init_telemetry() arm the binding (which importing it already does) and report the same state as the query functions. Building without the telemetry cargo feature compiles analytics out entirely; the switch still exists and is_enabled() is always false. The api_key_tracking_id in each event is a salted hash of the resolved LLM key (llm.api_key, from env, a config file or code). Every ComponentManager holds a cognee::telemetry::LlmKeyRegistration for its key, updated when its configuration changes and dropped with the manager; embedders without a manager call cognee::telemetry::set_llm_api_key. The LLM_API_KEY variable is not read by the analytics client itself. When every live registration uses the same key, its hash is reported; when live handles use different keys, process-wide events cannot be attributed to one of them and the id is reported empty rather than mislabelled.
Divergence from Python: Python hashes os.getenv("LLM_API_KEY") only. Rust hashes the key it actually uses, which can also come from its OPENAI_TOKEN alias, a config file or code. The derivation is identical (PBKDF2-HMAC-SHA256, same salt), so the same key gives the same id in both SDKs, which is what cross-SDK grouping needs; a Rust process configured only through OPENAI_TOKEN reports an id where a Python process without LLM_API_KEY (and so without a working LLM) reports none.

HTTP server

The server binary is not part of the cognee-rs repository. It reads its own env surface (host/port, auth, body limits, pipeline registry, notebooks, health probes), which is documented with the server rather than here; see HTTP Server Tools.

Cloud

Cloud/Auth0 configuration (COGNEE_CLOUD_URL, COGNEE_AUTH0_*) and the serve()/disconnect() flow are not part of OSS.

Runtime configuration API

ConfigManager (Arc<RwLock<Settings>>) is the runtime API that the bindings and the CLI use.
  • Generic setter: set(key, value) accepts every key in the key registry (each Settings field name, plus aliases such as embedding_model) and every key of a registered component section, canonical or alias (see Component config sections). An unknown key follows strict mode. get(key) reads any of them. A JSON null clears the optional settings (chunk_size, llm_temperature, single_process, summarization_schema). Setting system_root_directory also moves the graph and vector paths derived from the old root.
  • Typed setters for the common keys, in the families set_llm_*, set_embedding_*, set_vector_db_*, set_graph_*, set_chunk_*, set_relational_db_*, set_*_root_directory, set_ontology_*, set_classification_model / set_summarization_model / set_summarization_schema.
  • Bulk setters: set_llm_config, set_embedding_config, set_vector_db_config and set_graph_db_config.
  • Introspection: read(), version(), get_settings() (secrets masked). Settings::get_key / set_key / reset_key work on a plain Settings, cognee::config::keys() lists the registry, and ConfigManager::schema() the component sections it knows. ConfigManager::apply_patch applies a settings object in bulk, which is how the C and Python constructors take theirs.
Full signatures are in the ConfigManager rustdoc. The binding ergonomics (granular JS setters vs generic set in Python/C/Java) are documented in Language bindings: configuration.

CLI config subcommand

cognee-cli config get|set|unset|list|reset reads/writes the persisted JSON file. The settable keys are the registry keys whose value is saved in that file (every Settings field except llm_args and summarization_schema), plus the keys of the component sections compiled into this build. cognee-cli config list prints them. Example: