CortexDB Docs
Operations

Storage & Cluster

Storage paths, WAL, HNSW shape, scheduler intervals, blob backends, cold backups, and the experimental cluster mode.

CortexDB stores everything on one canonical write path: an append-only WAL backed by RocksDB. Every searchable index — HNSW vectors, Tantivy fulltext, the KG, materialized views — is a derivative of that WAL. Restore the WAL and you can rebuild every index.

Storage paths and durability

[storage]
data_path = "/data/cortex"          # default
wal_sync = true                     # default
max_disk_usage_percent = 95         # default
FieldTypeDefaultWhat it controls
data_pathPathBuf/data/cortexRoot for RocksDB, Tantivy, and the WAL.
wal_syncbooltruefsync() after every WAL append. false = ~10 ms durability gap, ~2–5× faster writes.
max_disk_usage_percentu8 [0..100]95Soft refuse-writes watermark. At this fullness /v1/experience returns 507.

The default /data/cortex suits the Docker/k8s shape (mount a real volume there); bare-metal usually overrides to /var/lib/cortexdb. The config file lives at <data_path>/cortex.toml, so changing data_path also moves the config. wal_sync = false (or CORTEX_WAL_SYNC=0) is the explicit faster/weaker opt-out — fine for voice/realtime, not for financial/medical/compliance workloads. max_disk_usage_percent is a soft floor (RocksDB itself doesn't refuse writes) — keep ~10% headroom on the actual disk.

The extraction cache

Alongside the stores, the data directory holds an extraction cache that lets repeated extraction work skip the model call. It is on by default and lives at <data_dir>/extraction_cache (verified present on a fresh v0.9.9 boot). Four environment variables control it:

Env varDefaultWhat it controls
CORTEX_EXTRACTION_CACHE(unset = on)Set =0 to disable; call sites then send no cache key.
CORTEX_EXTRACTION_CACHE_DIR<data_dir>/extraction_cacheWhere the on-disk tier lives.
CORTEX_EXTRACTION_CACHE_RAM_CAP4096 entriesIn-memory tier size; 0 disables the RAM tier.
CORTEX_EXTRACTION_CACHE_TTL_DAYS14 (range 1–365)Entry lifetime. Expiry is lazy, on read.

Because expiry is lazy, the directory can hold entries past their TTL until they are next read — size the volume for the cache as well as the stores.

Engine: HNSW and cache

[engine]
max_memory_bytes = 25769803776            # 24 GB (default)
vector_dimensions = 3072                  # default; MUST match embedding output
hnsw_m = 16                               # default
hnsw_ef_construction = 200                # default
hnsw_ef_search = 100                      # default; runtime-tunable
hnsw_quantization = "tq2"                 # default TQ2; opt out with sq8 / fp32
hnsw_tombstone_rebuild_threshold = 0.15   # default; 15% deleted → rebuild
block_cache_bytes = 8589934592            # 8 GB (default)
FieldDefaultRangeTuning
max_memory_bytes24 GB≥ 4 GBEngine heap budget. Physical RAM minus 4–8 GB.
vector_dimensions3072{256, 384, 512, 768, 1024, 1536, 3072}Must equal the embedding output dim.
hnsw_m16[4, 64]Edges per node. Higher = better recall, larger/slower build.
hnsw_ef_construction200[10, 2000]Build candidate pool.
hnsw_ef_search100[10, 2000]Query candidate pool. Higher = better recall, slower queries.
hnsw_quantizationtq2tq2 / sq8 / fp32sq8 = 1 byte/dim (~4× memory savings, ~-0.5 pp recall); fp32 keeps full precision.
hnsw_tombstone_rebuild_threshold0.15[0.0, 1.0]Rebuild when 15% of nodes are deleted.
block_cache_bytes8 GBRocksDB block cache; affects cold-data read latency.

The quantization default is TQ2, not ScalarU8

config_lint reports CORTEX_HNSW_QUANTIZATION = tq2 (unset = TQ2; opt out with sq8/fp32), and GET /v1/admin/metrics shows the running indexes at quantization: "TQ2" (main) / "SQ8" (entity). The accepted values are tq2 / sq8 / fp32ScalarU8 / None are not the engine's tokens.

HNSW recipes

Goalhnsw_mef_constructionef_searchQuantization
Default (LongMemEval-S 93.8%)16200100tq2
Voice / realtime1620060tq2
Memory-constrained16200100sq8
Max recall accuracy32500200fp32
Bulk ingest (build fast)16100100tq2

The Max-Recall config buys ~+0.5–1 pp at ~3× memory, ~2× build, ~2× query — rarely worth it in production.

Vector index persistence and memory

The HNSW index is snapshotted to the data directory so a restart warms from disk instead of rebuilding. These are environment variables, not cortex.toml fields; every default below is the value config_lint reports on v0.9.9.

Env varDefaultWhat it controls
CORTEX_SNAPSHOT_INTERVAL_SECS300How often an index snapshot is written.
CORTEX_SNAPSHOT_KEEP_PREV1Previous generations kept alongside the current one.
CORTEX_SNAPSHOT_WRITE_MBPS256Snapshot write throttle; 0 = unthrottled.
CORTEX_VECTOR_RESIDENT_MAX(unset = uncapped)Hot-tier watermark: how many vectors stay resident in memory.
CORTEX_EVICT_VECTORS_ON_ENRICH(unset = off)Demote vectors during enrichment, for steady-state memory.
CORTEX_VECTOR_SHARDS8HNSW shard count.
CORTEX_SHARD_SEARCH_THREADS8Per-query chunk fan-out across shards.
CORTEX_SHARD_SEARCH_POOLavailable cores (cap 64)Search thread-pool size.
CORTEX_DB_WRITE_BUFFER_MB256 (512 for the v1 WAL store)Per-store RocksDB memtable budget.
CORTEX_TOTAL_WRITE_BUFFER_MB(unset = per-store caps only)Shared memtable budget across stores.

These snapshots are not backups

CORTEX_SNAPSHOT_* controls vector-index persistence, so that a restart can warm from disk instead of rebuilding the index. It is not a backup: it captures the index rather than the data directory, and it is not a recovery point. The supported backup path is the verified cold backup below.

The boot log warns when the hot tier is uncapped

With CORTEX_VECTOR_RESIDENT_MAX unset the hot tier is uncapped, and config_lint computes what the booting machine can actually hold — reporting bytes-per-vector at the active quantization against available memory, and recommending a watermark for any corpus that can outgrow it. Set that watermark (and consider CORTEX_EVICT_VECTORS_ON_ENRICH) before the corpus reaches that size. See Reading the config_lint dump.

Network and ports

[network]
api_port = 8443                # cortex.toml default
gossip_port = 7000             # default
grpc_port = 9042               # default
request_timeout_ms = 10000     # default (10 s)
gossip_interval_ms = 1000      # default (1 s)
PortRoleNotes
3141v1 API + admin UI (single-node CLI default)The one public port.
8443api_port from cortex.tomlCluster-mode binaries (experimental).
7000UDP gossipCluster membership (experimental).
9042Internal gRPC RPCInter-node calls (experimental).

Port-defaults gotcha

The single-node CLI defaults --port=3141, while the TOML api_port defaults to 8443. Pick the value your reverse proxy forwards to and set both consistently.

Scheduler

[scheduler]
enabled = true                            # default
compaction_interval_secs = 300            # 5 min (min: 30)
methylation_interval_secs = 600           # 10 min (min: 60)
enrichment_drain_interval_secs = 30       # 30 s (min: 5)
cognitive_persist_interval_secs = 60      # 1 min (min: 10)
feedback_weight_interval_secs = 120       # 2 min (min: 30)
JobDefaultWhat it does
Compaction5 minMerge/dedupe entries; reduce footprint, improve recall over time.
Methylation10 minDecay-adjust salience by access patterns.
Enrichment drain30 sConsume async LLM extraction results.
Cognitive persist1 minCheckpoint planner state + ranker weights.
Feedback weight2 minApply feedback gradients to ranker weights.

CORTEX_SCHEDULER_DISABLE=1 disables the whole scheduler at startup (always set for benchmarks). Schema validation enforces the minimum intervals — compaction_interval_secs = 10 fails startup.

Cluster topology (experimental — not operational)

Cluster mode does not provide fault tolerance today

Single-node (cortexdb [PORT] [DATA_DIR]) is the supported deployment, production-ready for ≤ 10M events. Cluster mode (all four of --node-id, --rpc-addr, --gossip-addr, --seed-nodes together) is under development — multi-node replication and HA do not work; nodes run as independent databases. A "3-node, rf=3" deployment does not survive a node loss. Do not deploy cluster mode for redundancy, failover, or capacity.

cortexdb \
  --node-id=1 \
  --rpc-addr=10.0.0.1:7100 \
  --gossip-addr=10.0.0.1:7000 \
  --seed-nodes=10.0.0.1:7000,10.0.0.2:7000,10.0.0.3:7000 \
  --rf=3 \
  --port=3141 \
  --data-dir=/data/cortex/node1
[cluster]
node_id = 1                                   # must match --node-id
seed_nodes = ["10.0.0.1:7000", "10.0.0.2:7000"]
replication_factor = 3
vnodes_per_node = 256
consistency_default = "Quorum"                # "One" | "Quorum" | "All"

These fields describe the intended design; none of the replication or consistency settings provide fault tolerance in the current release.

Blob storage

Binary content (images, audio, video, documents) is stored in a blob backend with a content-addressed reference in the WAL.

[blob_store]
provider = "local"                  # "local" | "s3" | "gcs" | "azure"

# Local mode
data_dir = "/data/cortex/blobs"

# S3 mode
bucket = "acme-cortex-blobs"
region = "us-east-1"
endpoint = ""                       # optional — S3-compatible (R2, MinIO, B2)
access_key_id = ""                  # falls back to AWS_ACCESS_KEY_ID env
secret_access_key = ""              # falls back to AWS_SECRET_ACCESS_KEY env
allow_http = false                  # true ONLY for MinIO over LAN
virtual_hosted_style_request = true # false for path-style URLs

# S3 encryption
s3_encryption_type = "aws:kms"      # "AES256" | "aws:kms" | "" (none)
s3_kms_key_id = "arn:aws:kms:..."   # required if s3_encryption_type = aws:kms
s3_bucket_key_enabled = true        # KMS bucket key — saves KMS API costs

Every TOML field has an env-var form (e.g. CORTEX_BLOB_BUCKET, CORTEX_BLOB_S3_KMS_KEY_ID); the env var wins if both are set. GCS uses gcs_bucket + gcs_application_credentials; Azure uses azure_account + azure_container + azure_access_key (falls back to AZURE_STORAGE_KEY); MinIO is S3 with endpoint + allow_http = true + virtual_hosted_style_request = false.

Blobs are referenced from /v1/experience payloads by content hash and served via /v1/blobs/{id}. The server reads/writes the backend transparently — co-locate CortexDB and the blob backend in the same region/VPC.

Content modality processors

Each modality has its own extraction integration; all are optional — if unconfigured, ingest silently skips extraction for that modality.

# Image (GPT-4o vision)
export CORTEX_IMAGE_PROVIDER=openai
export CORTEX_IMAGE_API_KEY=$OPENAI_API_KEY
export CORTEX_IMAGE_MODEL=gpt-4o

# Audio (Whisper)
export CORTEX_AUDIO_PROVIDER=openai
export CORTEX_AUDIO_API_KEY=$OPENAI_API_KEY
export CORTEX_AUDIO_MODEL=whisper-1
export CORTEX_AUDIO_LANGUAGE=en        # optional

# Video (ffmpeg keyframes → image processor)
export CORTEX_FFMPEG_PATH=ffmpeg
export CORTEX_VIDEO_KEYFRAMES_PER_MIN=6

# Document (OCR / PDF extraction — e.g. Tika)
export CORTEX_DOCUMENT_PROVIDER=...
export CORTEX_DOCUMENT_API_URL=...
export CORTEX_DOCUMENT_API_KEY=...

The same fields can be set under [content_processors.image] / .audio / .video / .document / .sensor. See Media ingestion.

Disk sizing

Rough rule of thumb on text-heavy workloads with text-embedding-3-small (1536 d, tq2 quantization):

Events storedDisk footprintMemory (cache + HNSW)
100 K~2 GB~1 GB
1 M~15 GB~6 GB
10 M~120 GB~40 GB
100 M~1.1 TB~350 GB (cluster)

Above ~10M events, plan a very large single box — clustering is experimental/not operational, so scaling out isn't an option yet. Mitigate the one-box risk with regular verified cold backups and infra-level redundancy (RAID, replicated volumes).

Backups and snapshots

The supported path is a verified cold backup of the data directory using scripts/cold_backup.py. There is no online/hot backup, no scheduled job, no object-store upload target, and no PITR in the current release — those are planned, separate work.

# Server must be STOPPED — the tool refuses a live data dir.
python scripts/cold_backup.py backup  <data_dir> <archive.tar.gz>

# Recompute every file digest against the per-file SHA-256 manifest.
python scripts/cold_backup.py verify  <archive.tar.gz>

# Restore into a FRESH directory only; refuses non-empty targets and tampered archives.
python scripts/cold_backup.py restore <archive.tar.gz> <fresh_target_dir>

The archive captures the whole data directory byte-for-byte — WAL, RocksDB, Tantivy, HNSW, and cortex.toml — with a per-file SHA-256 manifest. See Backups & Disaster Recovery for the full procedure.

Next steps

On this page