Configuration Foundations
How CortexDB resolves configuration — the cortex.toml file, environment variables, CLI flags, precedence rules, and reload semantics.
This page covers the mechanics — where files live, what overrides what, and what happens on reload. For tuning advice, see the per-subsystem pages.
Resolution order
When the cortexdb binary starts, configuration is resolved in this order. Each step overrides the
previous.
- Compiled defaults — the
default_*()functions. What you get with zero config. <data_dir>/cortex.toml— loaded at startup. Missing fields fall back to compiled defaults; a missing file falls back to all defaults (a warning is logged).- Environment variables — read inline by each subsystem on first use. Most overrides are honored at startup; a handful (recall-tuning knobs) are read per-request.
- CLI flags — used only for cluster topology (
--node-id,--rpc-addr,--gossip-addr,--seed-nodes,--rf,--port,--data-dir,--peers). No CLI flag duplicates a TOML field or env var.
The data_dir is resolved from the second positional CLI argument (or --data-dir= in cluster mode),
defaulting to cortexdb_data single-node. Once data_dir is known, the config file is always read
from <data_dir>/cortex.toml.
The cortex.toml file
Location: <data_dir>/cortex.toml. Changing your data directory also moves the config file — point
at a fresh data_dir and you get pristine defaults.
Schema
The top-level sections are:
| Section | Purpose |
|---|---|
[cluster] | Node identity, peer set, replication factor, vnodes |
[storage] | Data path, WAL sync mode, disk watermarks |
[engine] | RocksDB cache, HNSW shape, vector dims, quantization |
[network] | API / gossip / gRPC ports, request timeouts |
[llm] | Default LLM provider, model, fallback chain |
[governance] | Retention, PII handling, audit logging |
[scheduler] | Background job intervals (compaction / methylation / drain) |
[blob_store] | Local / S3 / GCS / Azure blob backend + KMS |
[content_processors] | Image / audio / video / document / sensor handlers |
[security] | Encryption, TLS/mTLS, RBAC, rate limits, breach detection, field masking |
[compliance] | Data residency, consent, classification, DSAR, SIEM, cross-agent |
[deployment] | Profile preset + per-feature gate overrides |
Twelve sections, not fourteen
The schema enumerates 12 top-level sections (above). Every field uses serde(default = "..."), so
partial files are valid — you only need to specify what you want to change. Empty sections mean "use
all defaults for this section."
A minimal override file (empty sections = defaults; the one non-default field disables the scheduler for repeatable benchmark runs):
[storage]
[engine]
[network]
[llm]
[governance]
[scheduler]
enabled = falseValidation
Config loading returns a validation error (and refuses to start) if a field is out of range — examples:
engine.vector_dimensionsnot in{256, 384, 512, 768, 1024, 1536, 3072}engine.hnsw_moutside[4, 64]storage.max_disk_usage_percent > 100cluster.replication_factor == 0scheduler.compaction_interval_secs < 30
A bad config file fails fast at startup rather than silently using defaults. Watch the logs on first boot.
Reload semantics
cortex.toml is read once at startup. There is no SIGHUP reload — to change a TOML field, restart
the binary. Most fields (vector_dimensions, data_path, cluster topology) cannot be safely changed
at runtime. For tunables you do want to change without restart, use the env-var overrides in
Recall Tuning (read on the request path).
Environment variables
Roughly 100 variables exist, grouped by prefix:
| Prefix | Subsystem | Examples |
|---|---|---|
CORTEX_LOG_* | Logging | CORTEX_LOG_FORMAT=json |
CORTEX_EMBEDDING_* | Embeddings | CORTEX_EMBEDDING_MODEL, CORTEX_EMBEDDING_DIMS |
CORTEX_LLM_* | Entity-extraction LLM | CORTEX_LLM_MODEL=gpt-4o-mini |
CORTEX_ANSWER_* | Answer-generation LLM | CORTEX_ANSWER_MODEL=... |
CORTEX_VERIFIER_* | Optional answer verifier | CORTEX_VERIFIER_MODEL=gpt-4.1 |
CORTEX_ENRICHMENT_* | Async KG enrichment LLM | CORTEX_ENRICHMENT_MODEL= (off by default) |
CORTEX_BLOB_* | Blob storage backend | CORTEX_BLOB_PROVIDER=s3 |
CORTEX_IMAGE_*, CORTEX_AUDIO_*, … | Content modality processors | CORTEX_AUDIO_MODEL=whisper-1 |
CORTEX_RERANKER_* | Cross-encoder reranker | CORTEX_RERANKER_PROVIDER=cohere |
CORTEX_*_DISABLE | Kill switches | CORTEX_SCHEDULER_DISABLE=1 |
CORTEX_METHYLATION_*, CORTEX_CONSOLIDATION_* | Memory-evolution thresholds | CORTEX_METHYLATION_MIN_ACCESS=10 |
CORTEX_GRAPH_RETRIEVAL_*, CORTEX_MULTIHOP_*, CORTEX_HYDE_* | Recall-strategy tuning | CORTEX_GRAPH_RETRIEVAL_TOP_K=40 |
CORTEX_SYNC_* | Synchronous-pipeline kill switches | CORTEX_SYNC_FACT_EXTRACT_DISABLE=1 |
CORTEX_MS_* | Multi-session executor flags | CORTEX_MS_COUNT_RELEVANCE_ENABLE=1 |
CORTEX_ANSWER_MODEL default is a cloud value
claude-opus-4-6 is the managed-cloud default answer model. Self-hosted, set CORTEX_ANSWER_* to your
own provider; see Self-hosting defaults.
A few env vars use the ecosystem's own name: OPENAI_API_KEY (embedding key fallback),
ANTHROPIC_API_KEY (fallback for CORTEX_ANSWER_API_KEY when provider is anthropic), COHERE_API_KEY
(when CORTEX_EMBEDDING_PROVIDER=cohere), LLM_API_KEY (generic fallback), RUST_LOG (tracing
filter; the image's default includes cortex_embedding=warn, so the embedding clamp and retry warnings
reach the log).
Context, enrichment, erasure and code-plane settings
Defaults marked ✓ are printed in the boot config_lint dump; the others are compiled in. Leave the
defaults unless a row says otherwise.
Recall and answer context
| Variable | Default | What it does |
|---|---|---|
CORTEX_RECALL_BUDGET_EXCERPTS | on | Budget excerpts and the strict context budget; 0, off or false restores the pre-0.10.1 render and withholds budget_excerpt_v1 from capabilities[] (logged as RECALL_BUDGET_EXCERPTS_DISABLED). See Recall |
CORTEX_EVIDENCE_ROLE_CLASSIFIER | packer | Answer-context evidence classifier (full, legacy) |
CORTEX_CONTEXT_STRIP_ECHO | paired | Drop an assistant turn's echo of a user turn rendered in the same context (all, 0) |
CORTEX_CONTEXT_RENDER_SKIP_OVERSIZED | on | Skip an event too large for the remaining budget and keep packing |
CORTEX_FACET_PACK_POLICY | tiered | Facet merge policy (legacy; other values are for evaluation) |
CORTEX_CONTEXT_ASSISTANT_EXCERPTS | off | Opt-in excerpts of assistant turns (1, ms) |
CORTEX_CONTEXT_ASSISTANT_EXCERPT_MAX_BYTES | 600 | Per-event cap of those excerpts |
CORTEX_QUERY_VARIANTS_FUSION | interleave | How query_variants are fused (depth_cap, deep_rrf for evaluation) |
CORTEX_QUERY_VARIANTS_DEPTH | 10 | Depth for depth_cap (evaluation) |
CORTEX_QUERY_EMBED_CACHE_CAP | 512 ✓ | Query-embedding cache entries (0 disables); unchanged, now documented |
CORTEX_EMBEDDING_CACHE_DISABLE | unset ✓ | Measurement only: bypass the provider embedding cache |
CORTEX_CHUNK_FRAGMENTS | on | Write the chunk fragment rows behind matched_fragments |
Enrichment and extraction (enrichment-on deployments)
| Variable | Default | What it does |
|---|---|---|
CORTEX_ENRICHMENT_SKIP_NON_EN | off ✓ | Skip LLM enrichment for non-Latin-script text |
CORTEX_POLARITY_RECHECK | on | The polarity re-check call (0 records the doubt without the call) |
CORTEX_POLARITY_QUOTE_TRIGGER_EN | on | The English quote trigger of that re-check |
CORTEX_ENRICHMENT_MAX_LLM_CALLS_PER_EVENT | (not in the dump) | Caps the enrichment-model calls one event may use; the polarity re-checks count against it |
CORTEX_ENRICHMENT_SCOPE_QUIET_MS | 0 (off) | Hold a busy scope's captures for one batched extraction call (at most 60000) |
CORTEX_ENRICHMENT_SCOPE_QUIET_MAX_MS | 30000 | Longest hold of that window |
Forget and erasure (see Erasures)
| Variable | Default | What it does |
|---|---|---|
CORTEX_DESTRUCTIVE_LEASE_WAIT_SECS | 120 ✓ | How long a new-scope capture waits for a running forget or erasure (1 to 3600) |
CORTEX_SHUTDOWN_DESTRUCTIVE_WAIT_SECS | 60 ✓ | How long a graceful stop waits for running forgets and erasures (0 to 3600; 0 skips) |
CORTEX_ERASURE_RESIDUAL | on | The background pass that compacts erased bytes out of the storage files (0 turns it off and logs ERASURE_RESIDUAL_DISABLED) |
CORTEX_ERASURE_RESIDUAL_SHUTDOWN_SECS | 20 | How long a graceful stop drains that pass |
CORTEX_ERASURE_RESIDUAL_MAX_BYTES | 536870912 | SST bytes one pass may rewrite per store |
CORTEX_ERASURE_RESIDUAL_FULLTEXT_MAX_DOCS | 1000000 | Largest full-text segment one pass merges |
CORTEX_ERASURE_RESIDUAL_MAX_PENDING_KEYS | 131072 | Recorded keys waiting per store |
CORTEX_ERASURE_RESIDUAL_MIN_INTERVAL_MS | 30000 | Minimum gap between passes |
CORTEX_ERASURE_RESIDUAL_DEBOUNCE_MS | 1000 | Quiet time before a pass starts |
CORTEX_ERASURE_RESIDUAL_PARKED_RECHECK_SECS | 900 | Recheck interval of a parked over-budget segment |
CORTEX_ERASURE_PERIODIC_COMPACTION_SECS | unset | Periodic compaction backstop (for example 86400) |
CORTEX_ERASURE_INDEX | on | Read the erasure index (0: kept up to date but never read) |
CORTEX_ERASURE_INDEX_BACKFILL | on | Backfill the index at the first start on an older store |
CORTEX_ERASURE_INDEX_BACKFILL_ROWS | 20000 | Rows per backfill step |
CORTEX_ERASURE_INDEX_BACKFILL_PAUSE_MS | 20 | Pause between backfill steps |
Request log (see Request log and request ids)
| Variable | Default | What it does |
|---|---|---|
CORTEX_REQUEST_LOG | slow | One log line per request: slow, all or off |
CORTEX_REQUEST_LOG_SLOW_MS | 1000 | The slow threshold in milliseconds (0 logs every request) |
Code plane, console and evaluation
| Variable | Default | What it does |
|---|---|---|
CORTEX_CODE_REPO_ROOTS | unset ✓ | Directories repositories may be registered from; unset allows any readable directory (with a startup warning), set-but-empty refuses every registration. See Code Plane |
CORTEX_UPDATE_FEED_URL | GitHub Releases | The console's update check; off (also 0, false, none, disabled) turns it off. See Admin Console |
CORTEX_ABSTAIN_SELF_RESCUE | on | Answer self-rescue pass; new opt-in value guarded, 0 turns it off |
CORTEX_EVAL_FACET_PLAN_OVERRIDE | off | Evaluation only. Never on a production server |
CORTEX_EVAL_DUMP_ANSWER_POOLS | off | Evaluation only; writes customer content to disk. Never on a production server |
Boolean conventions
Boolean env vars accept 1/0, true/false (case-insensitive); empty/unset means default. Most
boolean knobs are kill switches — the feature starts on and you set =1 to disable
(CORTEX_LLM_DISABLE, CORTEX_SCHEDULER_DISABLE, CORTEX_GRAPH_RETRIEVAL_DISABLE, …). The exceptions
start off and you opt in — the _ENABLE suffix is the giveaway (CORTEX_AUTO_ROUTE,
CORTEX_ENTITY_VECTOR_SEED_ENABLE, CORTEX_MS_COUNT_RELEVANCE_ENABLE,
CORTEX_FACT_EVENT_PROMOTION_ENABLE).
CLI flags
Only 10 flags exist, all about cluster topology. Single-node operators need none — cortexdb with no
arguments binds the v1 API on port 3141 and writes to ./cortexdb_data.
Single-node mode
cortexdb [PORT] [DATA_DIR]# All defaults: API on 3141, data in ./cortexdb_data
cortexdb
# Custom port, custom data dir
cortexdb 4000 /var/lib/cortexdbThe server exposes a single public port — the v1 API (plus the bundled
Admin Console) on 3141 by default.
Cluster mode (experimental — not operational)
Clustering is refused at boot
The cluster flags exist in the binary, but the distribution layer is not operational (no request
forwarding, no replication, no membership). A complete set of cluster flags is a
boot refusal: the process exits with code 78 and
FATAL [CLUSTER_MODE_UNSUPPORTED]: cluster mode is not supported in this release … Remove the cluster flags and run single-node. Single-node is the supported deployment.
Cluster mode is selected by passing --node-id, --rpc-addr, --gossip-addr and --peers together
(or their CORTEX_CLUSTER_NODE_ID, CORTEX_CLUSTER_RPC_ADDR, CORTEX_CLUSTER_GOSSIP_ADDR,
CORTEX_CLUSTER_PEERS env forms). --peers lists the other members as ID:HOST:PORT:
# documented for recognition only: this exits 78 with CLUSTER_MODE_UNSUPPORTED
cortexdb \
--node-id=1 \
--rpc-addr=10.0.0.1:7100 \
--gossip-addr=10.0.0.1:7000 \
--peers=2:10.0.0.2:7000,3:10.0.0.3:7000 \
--rf=3 \
--port=3141 \
--data-dir=/data/cortex/node1A partial set (some cluster flags but not all four) is also fatal, with exit 78 and
CLUSTER_FLAGS_INCOMPLETE; a malformed peer entry (for example host:port without the numeric id) is
CLUSTER_NODE_ID_INVALID. There is no silent fallback to single-node any more: to run single-node,
pass no cluster flags at all.
Inspecting what's actually loaded
On startup, CortexDB logs the resolved scheduler config:
scheduler configuration loaded
enabled=true
compaction_interval_secs=300
methylation_interval_secs=600
cognitive_persist_interval_secs=60
feedback_weight_interval_secs=120For everything else:
# Liveness (always 200 while the process is up) → {"status":"healthy","version":"v0.10.2"}
curl http://localhost:3141/v1/admin/health
# Readiness — data-dir writability + storage handles, the pinned embedding provider,
# and degraded:true on mock corpora. 200 ready / 503 not. Point healthchecks here.
curl http://localhost:3141/v1/admin/ready
# Version and build info
curl http://localhost:3141/v1/admin/version
# Full metrics snapshot (JSON) — storage + vector_indexes blocks show what actually loaded
curl http://localhost:3141/v1/admin/metrics?format=jsonChecking which settings loaded
There is no config-read endpoint (GET /v1/admin/config answers 404). To confirm an env var took
effect, read the boot-time config_lint log lines (~85 of them — the real "what loaded" surface)
and the storage / vector_indexes blocks of GET /v1/admin/metrics?format=json.
Reading the config_lint dump
config_lint is the closest thing to a live config endpoint. It needs no setup: the image ships a
default RUST_LOG that includes it, so every boot prints the block.
docker logs cortexdb 2>&1 | grep config_lintEach line carries the variable, its effective value, and where that value came from:
config CORTEX_VECTOR_SHARDS = 4 (env)
config CORTEX_VECTOR_RESIDENT_MAX = unset (uncapped hot tier) (default; cortex-coordinator/src/production.rs::resident_max_from_env)(env)— your setting was read and is in force. This is the definitive answer to "did my variable take effect?"(default; <file>::<fn>)— the compiled default is in force, and the trailing path names the code that resolved it.
Some entries also emit a runtime sizing warning computed against the machine it booted on, for example flagging that an uncapped hot vector tier can outgrow available memory and suggesting the matching watermark variable. Read those before a production rollout.
Absence from config_lint does not mean a variable is wrong
The dump covers roughly 85 variables, which is a subset of the CORTEX_* surface — plenty of real
settings (the embedding, answer and verifier lanes among them) never appear. So a variable missing from
the block is inconclusive, while a variable shown as (env) is confirmed. To check whether a name is
read by the server at all, see the note on unread variables in
Recall Tuning.
Startup warnings
Some settings log an advisory WARN at boot. It never stops the server, and the defaults log none of
them. Each line carries a code="…" field you can alert on:
docker logs <container> 2>&1 | grep 'code="'| Code | Logged when |
|---|---|
ERASURE_RESIDUAL_DISABLED | CORTEX_ERASURE_RESIDUAL=0: erased bytes stay in the storage files until the engines compact on their own |
ENGLISH_ONLY_PLANE_ENABLED | CORTEX_CODE_PLANE (or the artifact and summary planes) is on; those planes use English-only cues |
ERASURE_RESIDUAL_LEGACY_WAL | The legacy dual WAL (CORTEX_UNIFIED_WAL=0), which the residual pass can't cover |
DOCUMENT_PROVIDER_FORGET_RESIDUE | A server document processor is configured; a forget by id leaves its whole-document derived text |
DOCUMENT_PROVIDER_NATIVE_DEAD_LETTERS | CORTEX_DOCUMENT_PROVIDER=native, which yields no usable text for PDF, DOCX and the like |
ENTITY_GRAPH_ENRICHMENT_FALSE_MERGE | The entity graph is on with enrichment; the graph resolver can merge near-identical names |
ENRICHMENT_RESOLVER_ERASURE_RESIDUE | Enrichment on with the graph off; an erased capture's entity names stay in the resolver's files |
RECALL_BUDGET_EXCERPTS_DISABLED | CORTEX_RECALL_BUDGET_EXCERPTS is 0, off or false: recall renders as before v0.10.1 and budget_excerpt_v1 is not listed |
With the code plane on and CORTEX_CODE_REPO_ROOTS unset, the server also warns that any readable
directory may be registered.
The embedding client also warns, without a code, when it clamps the batch size (target
cortex_embedding):
WARN cortex_embedding: CORTEX_EMBEDDING_MAX_BATCH_ITEMS=2048 clamped to 340: one response for 2048 x 1536-dim vectors would exceed the embedding client's response byte limit; each provider call carries at most 340 texts configured=2048 effective=340 model="text-embedding-3-small" dims=1536It tells you the value in effect and is harmless. At 3072 dimensions the image's own 256 is above the
limit, so it appears (clamped to 170) even when you set nothing.
Request log and request ids
The server can write one info line per request under the target cortex_api::request,
which the image's default RUST_LOG (cortex_api=info) enables. CORTEX_REQUEST_LOG picks which
requests:
slow(the default): every response outside 2xx — unauthenticated401probes included — and every request that took at leastCORTEX_REQUEST_LOG_SLOW_MS(default 1000 ms). A 0.6 s recall is not logged; a 1.4 s one is. A health checker or proxy that calls an authenticated route without a key writes one line per call.all: every request.off: none.
INFO cortex_api::request: request request_id=req_01a0e9fc-5496-7622-add0-b3bb97cf7fc0 client_request_id="docs-verify-408-single" method=POST route=/v1/experience status=408 total_ms=30011 admission_ms=6 lease_wait_ms=0 wal_append_ms=3 indexer_wait_ms=30000 queue_depth=1 outcome="timeout"request_idis the id the response carries inX-Request-Id: your own when you send one, else a server-mintedreq_…. When an error response mints its own id, yours appears asclient_request_id.routeis the route template (/v1/events/{id}), never the path; the line holds no body or query string.- Stage timings appear for the stages the request went through:
admission_ms(the scope check and any new-scope registration),lease_wait_ms,wal_append_ms,indexer_wait_ms,queue_depth(the unindexed writes ahead of this one, itself included) andstage_wait_ms(the wait for a later stage after indexing) on a capture;lease_wait_ms,blocking_queue_msandengine_mson forgets and erasures. outcomeisok,timeout(a?wait=ran out) orclient_gone(the client left first;statusis then0).
The same id is on the audit rows the request writes (the
authentication layer's row, and the gdpr: true row of a forget), so a forget's response, log line and
audit trail can be joined on it.
Next steps
- Profiles & Presets — copy-paste configs for common deployments
- Embeddings — pick a model and tune cost
- Recall Tuning — make queries faster or more accurate
Admin Console
The operator UI compiled into the CortexDB binary — served at / on the API port, zero-dependency and air-gap-friendly.
Profiles & Presets
Seven copy-paste configurations — Benchmark / Max-Recall / Voice / Batch / Cost / Enterprise / Quickstart — for the most common CortexDB deployment shapes.