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

> cognee-rust env vars, config keys and defaults for the vector, graph and relational databases and file storage.

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

## 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`](#pgvector-index-tuning). Python flat names are accepted as
aliases, shown in brackets.

| Env var | Key (alias) | Default |
| - | - | - |
| `VECTOR_DB_PROVIDER` | `vector.provider` (`vector_db_provider`) | `lancedb` (embedded, persistent) on non-Android; falls back to `brute-force` (in-memory) on Android |
| `VECTOR_DB_URL` | `vector.url` (`vector_db_url`) | *(empty — defaults to `{system_root_directory}/databases/cognee.lancedb`; set to `:memory:` to force the in-memory brute-force store)* |
| `VECTOR_DB_HOST` / `VECTOR_DB_PORT` | `vector.host` / `vector.port` (`vector_db_host` / `vector_db_port`) | *(empty)* / `1234` |
| `VECTOR_DB_NAME` / `VECTOR_DB_KEY` | `vector.name` / `vector.key` (`vector_db_name` / `vector_db_key`) | *(empty)* |
| `VECTOR_DB_USERNAME` / `VECTOR_DB_PASSWORD` | `vector.username` / `vector.password` (`vector_db_*`) | *(empty)* |
| `VECTOR_DATASET_DATABASE_HANDLER` | `vector.dataset_database_handler` (`vector_dataset_database_handler`) | *(empty — derived from the provider: `lancedb`, `pgvector`, `memory` for `brute-force`/`mock`)*; also `pgvector_shared` (a schema per dataset in the relational database). `pgvector` needs `CREATEDB` on the vector server, `pgvector_shared` `CREATE` on the relational database. Read only with `ENABLE_BACKEND_ACCESS_CONTROL` ([per-dataset storage](https://docs.cognee.ai/rust/configuration/datasets-and-access.md#per-dataset-storage)) |

Supported providers:

> [!NOTE]
>
> 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](https://github.com/topoteretes/cognee-rs/blob/main/docs/guides/external-providers.md).

* `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](https://docs.cognee.ai/rust/tools/backends.md) for HNSW indexing and
  `cognee-cli vector-reindex`.

Qdrant is not part of OSS. See [Pluggable Backends](https://docs.cognee.ai/rust/tools/backends.md).
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.

| Env var | Default | Range | Effect |
| - | - | - | - |
| `COGNEE_PGVECTOR_HNSW_M` | `24` | 2–100 | `m` of every HNSW index built from now on |
| `COGNEE_PGVECTOR_HNSW_EF_CONSTRUCTION` | `128` | `2 × m` – 1000 | `ef_construction` of those builds |
| `COGNEE_PGVECTOR_MAINTENANCE_WORKERS` | `4` mid-load rebuild / `7` end-of-bulk-load build | 0–64 | `max_parallel_maintenance_workers` for a build; when set, overrides both defaults |
| `COGNEE_PGVECTOR_MAINTENANCE_WORK_MEM_MB` | `2048` | 64–65536 | ceiling of the per-build `maintenance_work_mem` (the adapter sizes each build from the row count and dimension and clamps to this) |

* `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, as is a value too
  large for the field's integer type; an in-type value above the limit 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.

| Env var | Key (alias) | Default |
| - | - | - |
| `GRAPH_DATABASE_PROVIDER` | `graph.provider` (`graph_database_provider`) | `ladybug` (derived — see below) |
| `GRAPH_FILE_PATH` | `graph.file_path` (`graph_file_path`) | *(empty; defaults under the system root)* |
| `GRAPH_DATABASE_URL` | `graph.url` (`graph_database_url`) | *(empty)* |
| `GRAPH_DATABASE_HOST` / `GRAPH_DATABASE_PORT` | `graph.host` / `graph.port` (`graph_database_host` / `graph_database_port`) | *(empty)* / `123` |
| `GRAPH_DATABASE_NAME` / `GRAPH_DATABASE_KEY` | `graph.name` / `graph.key` (`graph_database_*`) | *(empty)* |
| `GRAPH_DATABASE_USERNAME` / `GRAPH_DATABASE_PASSWORD` | `graph.username` / `graph.password` (`graph_database_*`) | *(empty)* |
| `GRAPH_DATASET_DATABASE_HANDLER` | `graph.dataset_database_handler` (`graph_dataset_database_handler`) | *(empty — derived from the provider: `ladybug`, `kuzu`, `postgres_graph`, `memory` for `mock`)*; also `postgres_graph_shared` (a schema per dataset in the relational database). `postgres_graph` needs `CREATEDB` on the graph server, `postgres_graph_shared` `CREATE` on the relational database. Read only with `ENABLE_BACKEND_ACCESS_CONTROL` ([per-dataset storage](https://docs.cognee.ai/rust/configuration/datasets-and-access.md#per-dataset-storage)) |
| `GRAPH_MAX_DB_SIZE` | section key `graph.ladybug.max_db_size` (alias `graph_max_db_size`; `ladybug` builds only) | `1073741824` (1 GiB) |

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](#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](https://github.com/topoteretes/cognee-rs/blob/main/docs/roadmap/cognify-compatibility-plan.md)).

## Relational database

The `relational` section is registered by the relational kind. Backends are
`RelationalDbFactory`s 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.

| Env var | Key (alias) | Default |
| - | - | - |
| `DB_PROVIDER` | `relational.provider` (`db_provider`) | `sqlite` |
| `DATABASE_URL` | `relational.url` (`relational_db_url`) | `sqlite:./cognee.db?mode=rwc` |
| — | `relational.migration_url` (`migration_db_url`) | *(empty)* |
| `DB_HOST` / `DB_PORT` | `relational.host` / `relational.port` (`db_host` / `db_port`) | `localhost` / `5432` |
| `DB_NAME` | `relational.name` (`db_name`) | `cognee_db` |
| `DB_USERNAME` / `DB_PASSWORD` | `relational.username` / `relational.password` (`db_username` / `db_password`) | *(empty)* |
| `DB_SSLMODE` (else derived from `DATABASE_CONNECT_ARGS`) | `db_sslmode` | *(empty)* |
| `COGNEE_SINGLE_PROCESS` | `single_process` | *(unset — derived; effectively off)* |

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:

| value | meaning |
| - | - |
| unset / `null` | derive it — see below |
| `true` (`1`, `yes`, `on`) | one process owns this database; enable startup recovery |
| any other non-blank value | explicitly not; leave everything alone |

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

| how you run cognee | how to assert it | what happens |
| - | - | - |
| a binding — Python, JS, C API, Java/Android | `set_config("single_process", true)`, `ConfigManager::set_single_process(true)`, or `COGNEE_SINGLE_PROCESS=1` | recovery runs on the first operation |
| `cognee-http-server` (not in this repository) | `COGNEE_SINGLE_PROCESS=1` **only** | recovery runs at server startup |
| `cognee-cli cognify` / `memify` | — | **no startup recovery at all**; use `cognee-cli pipeline-unblock --clear` |

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.

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

| Env var | Key (alias) | Default |
| - | - | - |
| `STORAGE_BACKEND` | `storage.backend` (`storage_backend`) | `local` |
| `STORAGE_BUCKET_NAME` | `storage.bucket_name` (`storage_bucket_name`) | *(empty)* |

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


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