> ## 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 Runtime Configuration

> cognee-rust settings for the session cache and rate limiting, logging, observability and telemetry, the HTTP server and cloud.

The session, cache and rate-limit keys resolve 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). Logging,
observability and telemetry are process-wide instead: each is resolved once per
process, with its own precedence given in its section
([two scopes](https://docs.cognee.ai/rust/configuration.md#two-scopes)).

## Session / cache & rate limiting

The session (Q\&A history) store is chosen by the `session` section, registered by
the session-store kind. Stores are `SessionStoreFactory`s 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`, `sqlite` and `postgres`: the session tables in the relational
  database (any `RelationalDb`). This is the default when neither
  `session.store` nor `CACHE_BACKEND` selects another store.
* `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 while `session.caching` is on; with
`CACHING=false` no store id is resolved.

> [!WARNING]
>
> **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.

| Env var | Key (alias) | Default |
| - | - | - |
| `COGNEE_SESSION_STORE` | `session.store` (`session_store`) | *(empty: `CACHE_BACKEND`, else `relational`)* |
| `COGNEE_SESSION_DIR` | `session.fs.directory` (`session_directory`; `session-fs` builds only) | `<system_root>/sessions` |
| `CACHE_HOST` / `CACHE_PORT` | `session.redis.host` / `.port` (`cache_host` / `cache_port`; `session-redis` builds only) | `localhost` / `6379` |
| `CACHE_USERNAME` / `CACHE_PASSWORD` | `session.redis.username` / `.password` (`cache_*`; `session-redis` builds only) | *(empty)* |
| `COGNEE_FEEDBACK_LLM_TIMEOUT_MS` | `session.feedback_llm_timeout_ms` | `8000` (trace-step feedback LLM call) |
| `SESSION_ABANDON_AFTER_SECONDS` | `session.abandon_after_seconds` (`session_abandon_after_seconds`) | `1800` (idle running session reads as `abandoned`) |
| `CACHE_BACKEND` | `session.cache_backend` (`cache_backend`): used only while `session.store` is empty; an id this build lacks falls back to `relational` with a warning | *(empty)* |
| `SESSION_TTL_SECONDS` | `session.ttl_seconds` (`session_ttl_seconds`) | `604800` (7d); `0` or a negative value never expires |
| `CACHING` | `session.caching` (`enable_caching`, `caching`) | `true` |
| `LLM_RATE_LIMIT_ENABLED` / `_REQUESTS` / `_INTERVAL` | `llm.rate_limit_*` (see [LLM](https://docs.cognee.ai/rust/configuration/llm-and-embedding.md#llm)) | `false` / `60` / `60` |
| `EMBEDDING_RATE_LIMIT_ENABLED` / `_REQUESTS` / `_INTERVAL` | `embedding.rate_limit_*` (see [Embedding](https://docs.cognee.ai/rust/configuration/llm-and-embedding.md#embedding)) | `false` / `60` / `60` |
| `AUTO_RATE_LIMIT` | `llm.auto_rate_limit` (`auto_rate_limit`) | `true` |

**Python's session-cache settings.** Python selects its session cache with
`CACHE_BACKEND` (`redis`, `fs`, `sqlite`, `postgres`, `turso`, `tapes`; default
`sqlite`). Here it binds `session.cache_backend`, a fallback for
`session.store` (`COGNEE_SESSION_STORE`): it is used only while `session.store` is
empty, and an explicit `session.store` always wins. `sqlite` and `postgres` select
the `relational` store (Rust keeps sessions in the relational database, not a
separate `cache.db`); `fs` and `redis` need their build features, and `turso` /
`tapes` are not built in. Unlike `session.store`, a `CACHE_BACKEND` value naming a
store this build does not provide is **not** an error: it is logged as a warning
and the `relational` store is used, so a `.env` shared with Python (which documents
`CACHE_BACKEND=redis` next to `CACHE_HOST`) cannot take down a stock `cognee-cli`
or binding, none of which ship the `fs` or `redis` store. To insist on a store and
fail when it is missing, set `session.store` (`COGNEE_SESSION_STORE`) instead.
Both unset, the store is `relational`.

`session.ttl_seconds` expires a session's entries, trace steps and graph-context
snapshot, each that long after its own last write, as Python does. The `redis` store sets a
key expiry; the `fs` store deletes an expired file the next time it is accessed (read or
written). The
`relational` store does not expire sessions: Python's SQL cache keeps an expiry
per row, which the Rust session tables have no column for. A negative
`SESSION_TTL_SECONDS` is accepted and, like `0`, never expires a session, as in
Python, whose cache adapters set an expiry only for a value `> 0`.

`session.caching = false` (`CACHING=false`) switches session memory off, as in
Python: whatever the store, session reads are empty, a search answers without
session history and stores nothing, and `auto_feedback` has no effect.
`remember(data, session_id=…)` still succeeds and only logs "session cache not
available", as Python's `_add_to_session` does; a typed entry (`remember_entry`:
Q\&A, trace or feedback) fails with "session memory is disabled" before doing any
work (no `session_records` upsert, no feedback LLM call), as Python's
`_dispatch_session_entry` raises "Session cache unavailable".

> [!NOTE]
>
> `cognee-cli` migrates a saved config file written before these settings took
> effect: the old, never-read `cache_backend: "fs"` default and the old
> `./target/models/...` ONNX path defaults are dropped, so an upgraded CLI keeps
> the `relational` store and places the model under `cache_root_directory`. An
> explicitly chosen `cache_backend` other than `fs` (for example `redis`) is kept;
> an explicit `fs` cannot be told apart from the old default and is dropped too.

### 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`](https://github.com/topoteretes/cognee-rs/blob/main/crates/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`](https://github.com/topoteretes/cognee-rs/blob/main/crates/logging/src/config.rs), 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.

| Env var | Option key | Default | Purpose |
| - | - | - | - |
| `COGNEE_LOG_FILE` | `log_file` | `true` | Master file-logging toggle (`false`/`0`/`no` disables). |
| `COGNEE_LOGS_DIR` | `logs_root_directory` | `~/.cognee/logs` | Log directory (falls back to `/tmp/cognee_logs` if unwritable). |
| `COGNEE_LOG_FORMAT` | `log_format` | `plain` | `plain` (Python-compatible) or `json`. Applies to stdout + file. |
| `COGNEE_LOG_ROTATION` | `log_rotation` | `daily` | `daily` / `hourly` / `minutely` / `never`. |
| `COGNEE_LOG_BACKUP_COUNT` | `log_backup_count` | `5` | Rotated files retained by age. |
| `COGNEE_LOG_MAX_FILES` | `log_max_files` | `10` | Hard cap on retained log files. |
| `COGNEE_LOG_MAX_BYTES` | `log_max_bytes` | *(unset)* | Accepted for Python parity, ignored (size-based rotation is not supported); a warning is logged. |
| `LOG_FILE_NAME` | `log_file_name` | *(timestamped)* | Override the log file name. |
| `RUST_LOG` / `LOG_LEVEL` | `log_level` | cognee's default filter | Level filter (`RUST_LOG` preferred). The default is `info` with noisy libraries (`ort`, `reqwest`, `sqlx`, …) at `warn`. |
| — | `console_stream` | `stdout` | `stdout` or `stderr`. |

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

| Binding | Entry point |
| - | - |
| Rust | `cognee_logging::init_logging(LoggingConfig::from_env()?, layers)`; set fields or call `cfg.set(key, value)` first |
| Python | `cognee_py.setup_logging({"log_level": "debug", "logs_root_directory": "/var/log/cognee"})` |
| TypeScript | `setupLogging({ logLevel: "debug", logsRootDirectory: "/var/log/cognee" })` |
| Java | `Cognee.setupLogging(Map.of("log_level", "debug"))` |
| C | `cognee_setup_logging_with_options("{\"log_level\":\"debug\"}")` |

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](https://github.com/topoteretes/cognee-rs/blob/main/docs/observability/opentelemetry.md) and
[observability/send\_telemetry.md](https://github.com/topoteretes/cognee-rs/blob/main/docs/observability/send_telemetry.md).

### OpenTelemetry traces

| Env var | `Settings` / option key | Default | Purpose |
| - | - | - | - |
| `COGNEE_TRACING_ENABLED` | `cognee_tracing_enabled` | `false` | Activate OTLP trace export. |
| `OTEL_EXPORTER_OTLP_ENDPOINT` | `otel_exporter_otlp_endpoint` | *(empty)* | OTLP collector endpoint (non-empty also activates tracing). |
| `OTEL_SERVICE_NAME` | `otel_service_name` | `cognee` (CLI); `cognee.<binding>-binding` in a binding | Service name attribute. |
| `OTEL_EXPORTER_OTLP_HEADERS` / `_PROTOCOL` | `otel_exporter_otlp_headers` / `otel_exporter_otlp_protocol` | *(empty)* / `grpc` | Exporter headers / protocol. |
| `OTEL_SPAN_PROCESSOR` | `otel_span_processor` | `batch` | `batch` or `simple`. |
| `OTEL_TRACES_SAMPLER` / `_ARG` | `otel_traces_sampler` / `otel_traces_sampler_arg` | *(empty)* | Sampler selection. |
| `ENV` | `otel_deployment_environment` | `development` | The `deployment.environment.name` resource attribute. |
| `MONITORING_TOOL` | `monitoring_tool` | `none` | Python's observer selection. Only `none` is supported, as in Python, whose `get_observe` rejects every other tool: setting another is an error, and from env it is ignored with a warning. Tracing goes through the OpenTelemetry settings above. |

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

| Binding | Entry point |
| - | - |
| Python | `cognee_py.setup_telemetry({"otel_exporter_otlp_endpoint": "http://collector:4317"})` |
| TypeScript | `setupTelemetry({ otelExporterOtlpEndpoint: "http://collector:4317" })` |
| Java | `Cognee.initOtlp(Map.of("otel_exporter_otlp_endpoint", "http://collector:4317"))` |
| C | `cognee_init_otlp_with_options("{\"otel_exporter_otlp_endpoint\":\"http://collector:4317\"}")` |

### Product analytics

Product analytics are **on by default** (opt-out, matching the Python SDK). The
switch is **process-wide**: one
[`TelemetryConfig`](https://github.com/topoteretes/cognee-rs/blob/main/crates/telemetry/src/config.rs) governs every event the
process sends, whichever handle emits it. It is not a `Settings` key and not a
`cognee-cli config` key.

| Env var | `TelemetryConfig` field | Default | Purpose |
| - | - | - | - |
| `TELEMETRY_DISABLED` | `enabled` = `false` when set to any non-empty value | *(unset)* | Opt out. |
| `ENV` | `enabled` = `false` when `test` or `dev` | *(unset)* | Opt out in test/dev environments. |
| `COGNEE_HOST_SDK` | `host_sdk` | *(unset)* | Set by a host SDK that embeds a binding and sends its own analytics; suppresses that binding's events (not the CLI's) to avoid double counting. |
| `TELEMETRY_REQUEST_TIMEOUT` | `request_timeout_secs` | `5` | HTTP timeout, clamped to `[1, 60]`. |
| `TRACKING_ID` | `tracking_id` | *(unset)* | Fixed `anonymous_id`. |
| `TELEMETRY_API_KEY_TRACKING_SALT` | `api_key_tracking_salt` | well-known default | PBKDF2 salt for `api_key_tracking_id`. |
| `COGNEE_TELEMETRY_PROXY_URL_FOR_TESTS` | `endpoint_override` | *(unset)* | Collector override for test harnesses; honoured only while `COGNEE_TELEMETRY_INTEGRATION_TEST` is non-empty. |

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

| Surface | Turn analytics off | Query |
| - | - | - |
| Rust | `cognee::telemetry::set_enabled(false)`, or `configure(TelemetryConfig { .. })` | `cognee::telemetry::is_enabled()` |
| CLI | `cognee-cli --no-telemetry <command>` (or `TELEMETRY_DISABLED=1`) | — |
| Python | `cognee_py.set_telemetry_enabled(False)` | `cognee_py.is_telemetry_enabled()` |
| TypeScript | `setTelemetryEnabled(false)` | `isTelemetryEnabled()` |
| Java | `Cognee.setTelemetryEnabled(false)` | `Cognee.isTelemetryEnabled()` |
| C | `cognee_set_telemetry_enabled(0)` | `cognee_is_telemetry_enabled()` |

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.

> [!NOTE]
>
> **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](https://docs.cognee.ai/rust/tools/http-server.md).

## Cloud

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


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