Skip to main content
Khora is configured through environment variables prefixed KHORA_ or a KhoraConfig instance constructed programmatically. Both paths are backed by the same pydantic-settings model in src/khora/config/schema.py.

Two ways to configure

Environment variables

All settings use the KHORA_ prefix with single-underscore separators for nested fields. Examples:
KHORA_DATABASE_URL=postgresql://khora:khora@localhost:5434/khora
KHORA_NEO4J_URL=bolt://neo4j:pleaseletmein@localhost:7688
KHORA_LLM_MODEL=gpt-4o
KHORA_QUERY_ENABLE_HYDE=auto
KHORA_QUERY_DEFAULT_MODE=hybrid
Legacy double-underscore nesting (KHORA_STORAGE__GRAPH__URL) is still accepted as a backwards-compatible alias on every nested-config field. New code and .env files should use the single-underscore form shown throughout this document. The legacy form continues to work but is no longer documented. Nested-object env vars (graph backend, vector backend, dream-phase per-op toggles) are documented in the Nested env vars section below.

Programmatic

from khora import KhoraConfig, Khora
from khora.config.schema import StorageSettings, LLMSettings

config = KhoraConfig(
    database_url="postgresql://khora:khora@localhost:5434/khora",
    neo4j_url="bolt://neo4j:pleaseletmein@localhost:7688",
    llm=LLMSettings(model="gpt-4o", embedding_model="text-embedding-3-small"),
)

async with Khora(config) as kb:
    ...
Programmatic values take priority over environment variables.

Install extras

ExtraPurposePulls in
(default)Core: PostgreSQL + pgvector + Neo4j driver + litellm-
sqliteSQLite embedded relational + vectoraiosqlite>=0.21.0
lancedbLanceDB embedded vector storelancedb>=0.30.0, pyarrow>=24.0.0
sqlite-lanceUnified SQLite + LanceDB embedded backend, the recommended embedded stack for VectorCypherlancedb>=0.30.0, aiosqlite>=0.21.0, pyarrow>=24.0.0
binary-readersdocx / xlsx readers (used by downstream ingestors)openpyxl>=3.1.0, python-docx>=1.2.0
parquetParquet readerspyarrow>=24.0.0
accelAccelerated CPU ops (string-matching fuzz, used by dream-phase centroid recompute)rapidfuzz>=3.0.0
nlpspaCy-based sentence splittingspacy>=3.8.0
otelOpenTelemetry SDK + OTLP/HTTP exporter (vendor-neutral)opentelemetry-sdk>=1.34.1, opentelemetry-exporter-otlp-proto-http>=1.34.1
otel-grpckhora[otel] + OTLP/gRPC transportadds opentelemetry-exporter-otlp-proto-grpc>=1.34.1
logfireLogfire - managed OTel backend with auto-bootstraplogfire>=4.6.0
rustRust acceleration (khora-accel); pin tracks khora’s own version in lockstepkhora-accel (exact lockstep pin)
Combine extras as needed: pip install 'khora[rust,otel]'. See Observability for the full description of open telemetry env-var contract, precedence rules, and vendor recipes. Khora always exposes the OTel API. The [otel] and [logfire] extras determine where spans/metrics go.

Core settings

VariableTypeDefaultDescription
KHORA_DATABASE_URLstr-PostgreSQL URL (shortcut for storage.postgresql_url).
KHORA_NEO4J_URLstr-Neo4j URL (shortcut for storage.graph.url).
KHORA_LLM_EXTRACTION_MODELstr-Override extraction model (shortcut for llm.extraction_model).
KHORA_DEBUGboolfalseEnable debug-level logging.
KHORA_ENVIRONMENTstrdevelopmentdevelopment, staging, or production.
KHORA_APP_NAMEstrkhoraUsed in logs and telemetry.

Storage

Prefix: KHORA_STORAGE_. See Storage backends for the full backend matrix.
VariableDefaultDescription
KHORA_STORAGE_BACKENDpostgrespostgres (PostgreSQL + pgvector + Neo4j) or sqlite_lance (SQLite + LanceDB embedded).
KHORA_STORAGE_POSTGRESQL_URL-PostgreSQL connection URL.
KHORA_STORAGE_POSTGRESQL_POOL_SIZE50asyncpg pool size.
KHORA_STORAGE_POSTGRESQL_MAX_OVERFLOW30Max overflow connections.
KHORA_STORAGE_POSTGRESQL_POOL_PRE_PINGfalseValidate connections before checkout (adds latency, prevents stale-connection errors).
KHORA_STORAGE_HNSW_M24HNSW index M (max connections per layer).
KHORA_STORAGE_HNSW_EF_CONSTRUCTION128Build-time HNSW search width.
KHORA_STORAGE_HNSW_EF_SEARCH100Query-time HNSW search width.
KHORA_STORAGE_USE_HALFVECtrueUse halfvec (float16) for HNSW indexes. Requires pgvector >= 0.7.0; falls back gracefully.
Graph and vector backends nest under storage.graph and storage.vector. The flat fields KHORA_STORAGE_NEO4J_URL, KHORA_STORAGE_NEO4J_USER, KHORA_STORAGE_NEO4J_PASSWORD, KHORA_STORAGE_PGVECTOR_URL, and KHORA_STORAGE_EMBEDDING_DIMENSION remain supported as a back-compat path and are migrated into the discriminated-union configs automatically.

Neo4j pool metrics

With any OTel backend installed ([otel] or [logfire]), the Neo4j backend emits OTel metrics automatically. See Observability. For high-frequency sub-minute sampling enable:
KHORA_STORAGE_GRAPH_POOL_SAMPLER_ENABLED=true
KHORA_STORAGE_GRAPH_POOL_SAMPLER_INTERVAL_MS=500    # clamped to [50, 60000]

Neo4j relationship limits

Relationship.source_document_ids and Relationship.source_chunk_ids are append-bounded on every MERGE to prevent unbounded growth on hot edges. Defaults are 100 and 250 respectively. For deep-provenance workloads, where many documents contribute to the same edge, raise the relevant knob and watch the khora.neo4j.relationship.source_id_truncated metric:
KHORA_STORAGE_GRAPH_RELATIONSHIP_SOURCE_DOCUMENT_IDS_MAX=500
KHORA_STORAGE_GRAPH_RELATIONSHIP_SOURCE_CHUNK_IDS_MAX=1000
See the Neo4j nested-env-var table below for the full Neo4j-and-friends table, including the matching entity-side caps. When the (existing + incoming) union exceeds the cap, the most-recent tail is kept, dropped entries are counted on the metric, and a logger.warning(...) records the field name, dropped count, rows affected, and configured limit.

Embedded backends

The embedded sqlite_lance path is appropriate for demos, evaluation, tests, and small single-user CLIs. It is not the deployment story. For production, use PostgreSQL + pgvector + Neo4j. Documented scale ceiling, where performance and recall degrade noticeably above these thresholds:
  • ~1M chunks (LanceDB IVF-PQ training time + write serialisation start to dominate)
  • ~100k entities (recursive-CTE traversal cost on hub nodes)
  • ~500k relationships
  • Traversal depth ≤3 (the instr(walk.visited, ...) visited-set scan in graph.py is O(depth × fan-out × visited-len) and degrades sharply at depth ≥4 with high fan-out)
Known gaps and warts:
  • Partial atomicity in coordinator.transaction(): only the SQL session is enrolled. LanceDB writes happen post-commit with compensating-delete-on-failure. A crash between SQLite commit and Lance write can leave orphaned vectors or missing embeddings, and reconciliation runs on the next ingest.
  • Point-in-time queries are not supported on the embedded stack. target_date queries raise NotImplementedError. The bi-temporal entity versioning that powers them lives in Neo4j (version_valid_from / version_valid_to on :Entity / :EntityVersion nodes), which sqlite_lance has no equivalent of.
  • FTS5 covers chunks only: entity-anchored recall falls back to LIKE / JSON-equality. Recommend the PostgreSQL stack for entity-heavy corpora.
  • Install footprint is ~130–180 MB unpacked (pyarrow + lancedb native + Arrow C++ runtime). “Embedded” means “no server”, not “no native deps”.
  • IVF-PQ retraining is automatic when the corpus grows past retrain_factor × (rows at last training). Tune via KHORA_STORAGE_SQLITE_LANCE_RETRAIN_FACTOR.
Vector index tuning lives on the sqlite_lance storage sub-config. See the KHORA_STORAGE_SQLITE_LANCE_* table below for DB_PATH, LANCE_PATH, EMBEDDING_DIMENSION, USE_HALFVEC, LANCE_INDEX, IVF_PARTITIONS, HNSW_M, and RETRAIN_FACTOR with defaults and tuning guidance.

LLM

Prefix: KHORA_LLM_. LiteLLM handles the provider dispatch.
VariableDefaultDescription
KHORA_LLM_MODELgpt-4o-miniPrimary model for generation.
KHORA_LLM_API_KEY_ENVOPENAI_API_KEYEnvironment variable holding the API key.
KHORA_LLM_TEMPERATURE0.7Sampling temperature.
KHORA_LLM_MAX_TOKENS12288Max output tokens per extraction call.
KHORA_LLM_TIMEOUT30Request timeout in seconds.
KHORA_LLM_MAX_RETRIES3Retry budget on failure.
KHORA_LLM_MAX_CONCURRENT_LLM_CALLS10Cap on concurrent in-flight LLM requests.
KHORA_LLM_EMBEDDING_MODELtext-embedding-3-smallEmbedding model.
KHORA_LLM_EMBEDDING_DIMENSION1536Must match your DB schema.
KHORA_LLM_EXTRACTION_MODEL-Override extraction model (falls back to model). Haiku / Gemini Flash work well here.

Pipeline (extraction)

Prefix: KHORA_PIPELINES_.
VariableDefaultDescription
KHORA_PIPELINES_CHUNKING_STRATEGYsemanticfixed, semantic, or recursive.
KHORA_PIPELINES_CHUNK_SIZE512Target chunk size (tokens).
KHORA_PIPELINES_CHUNK_OVERLAP50Overlap between chunks.
KHORA_PIPELINES_CONVERSATION_TIME_GAP_MINUTES15Split conversations after this many quiet minutes.
KHORA_PIPELINES_CONVERSATION_MAX_GROUP_SIZE50Max messages per conversation chunk.
KHORA_PIPELINES_CONVERSATION_MIN_GROUP_SIZE2Merge groups below this size.
KHORA_PIPELINES_EXTRACT_ENTITIEStrueRun the entity extractor.
KHORA_PIPELINES_ENTITY_TYPESPERSON,ORGANIZATION,CONCEPT,LOCATIONEntity type allowlist.
KHORA_PIPELINES_SELECTIVE_EXTRACTIONtrueKET-RAG selective extraction (cost reduction).
KHORA_PIPELINES_EXTRACTION_IMPORTANCE_RATIO0.7Top fraction of chunks sent to LLM extraction.
KHORA_PIPELINES_EXTRACTION_MIN_IMPORTANCE0.2Minimum importance threshold; chunks above this are always extracted.
KHORA_PIPELINES_SKIP_EMBEDDING_ENTITY_TYPESDATE,URL,EMAILSkip embeddings for these types when mention_count is low.
KHORA_PIPELINES_SKIP_EMBEDDING_MENTION_THRESHOLD1Skip embedding for rare-mention entities of the above types.

Query

Prefix: KHORA_QUERY_. See Retrieval for guidance.
VariableDefaultDescription
KHORA_QUERY_DEFAULT_MODEhybridvector, graph, hybrid, or all.
KHORA_QUERY_MIN_CHUNK_SIMILARITY0.05Chunk similarity floor.
KHORA_QUERY_MIN_ENTITY_SIMILARITY0.05Entity similarity floor.
KHORA_QUERY_VECTOR_WEIGHT0.5Fusion weight.
KHORA_QUERY_GRAPH_WEIGHT0.3Fusion weight.
KHORA_QUERY_KEYWORD_WEIGHT0.2Fusion weight.
KHORA_QUERY_APPLY_RECENCY_BIASfalseBias scoring towards newer documents.
KHORA_QUERY_RECENCY_WEIGHT0.35How strong the recency bias is.
KHORA_QUERY_ENABLE_HYDEautoHyDE query expansion: auto / always / never (legacy booleans normalize to always / never). See Retrieval.
KHORA_QUERY_HYDE_NUM_HYPOTHETICALS1Number of hypothetical documents to generate (1–5).
KHORA_QUERY_ENABLE_HYDE_CYPHERfalseOpt-in. Run LLM-picked parameterized Cypher templates as an extra retrieval channel for structured queries. See Retrieval.
KHORA_QUERY_HYDE_CYPHER_LIMIT20Max entities returned per HyDE-Cypher template execution.
KHORA_QUERY_ENABLE_RERANKINGtrueCross-encoder reranking of top candidates.
KHORA_QUERY_TEMPORAL_SQL_PUSHDOWNtruePush relative-date filters into SQL WHERE clauses.

Telemetry

Khora has two independent telemetry paths. Spans and metrics (OpenTelemetry). Khora emits spans (@trace, trace_span()) and metrics through the OpenTelemetry API unconditionally. Whether they’re exported depends only on which TracerProvider / MeterProvider is installed, not on any KHORA_* variable. Install the [otel] extra and call configure_telemetry() (honors OTEL_* env vars), or install [logfire] and run logfire.configure(), and khora’s signals flow to your collector. With no provider configured, OTel returns a NonRecordingSpan and the helpers are near-free. See Observability for the full setup and the OTLP env-var contract. Structured event log (PostgreSQL). Separately, khora can write structured LLMEvent / StorageEvent / PipelineEvent rows to a PostgreSQL table. This is opt-in and independent of the OTel path above.
VariableDefaultDescription
KHORA_TELEMETRY_DATABASE_URL-PostgreSQL URL for the structured event collector. Unset → a zero-cost no-op collector. Does not affect OTel spans/metrics.
KHORA_TELEMETRY_SERVICE_NAMEkhoraService tag attached to recorded events.

Logging

Khora uses loguru. Call khora.logging_config.setup_logging() once per process (or configure your own sinks with enqueue=True). See the Logging section of the khora CLAUDE.md for the full rationale. Short version: default loguru sinks are synchronous and will block an asyncio event loop on every logger.* call.
VariableDefaultDescription
KHORA_NEO4J_LOG_LEVEL-Neo4j driver log level (DEBUG / INFO / WARNING / ERROR / CRITICAL, case-insensitive). Unset = no-op. See examples/neo4j_debug_logging.py.

Secrets

Secret parameters, like API keys (OpenAI, Anthropic, etc.) are read from the environment variable named by KHORA_LLM_API_KEY_ENV (default OPENAI_API_KEY). Khora never reads credentials from disk. They come from the environment. Credentials are read once when KhoraConfig is constructed and bound into the connection pools and the LLM client at startup, so rotating a secret takes effect on the next process start (or whenever you rebuild the config and reconnect). There’s no in-process reload.

Credential fields

Credential fields on KhoraConfig (PostgreSQL DSN, Neo4j password, LLM API key, telemetry DSN, etc.) are pydantic.SecretStr. This has two operator-visible consequences:
  • repr() and config-dump output render the value as '**********'. Logs, error messages, and KhoraConfig().model_dump() do not leak cleartext credentials.
  • Code that reads the cleartext value must call .get_secret_value() explicitly. SQLAlchemy engines and graph drivers receive the cleartext at the boundary. Downstream library consumers must do the same. See the consumers guide for the integration note.
from khora.config import KhoraConfig

cfg = KhoraConfig()
print(cfg.storage.postgresql_url)             # SecretStr('**********')
dsn = cfg.storage.postgresql_url.get_secret_value()   # cleartext, for engine init

Lockfile policy

khora’s pyproject.toml includes [tool.uv] exclude-newer = "7 days", a relative, evaluated-on-every-sync guard against pulling brand-new upstream releases that haven’t had time to stabilise. Security-critical packages opt out via exclude-newer-package (currently only urllib3 for CVE-2026-44431 / CVE-2026-44432). Downstream consumers that mirror khora’s pin policy inherit the same 7-day staging window for transitive dependencies; override per-package as needed.

Nested env vars

Reference for every Khora environment variable that lives on a sub-object attached to a sub-settings class: graph backend, vector backend, the SQLite+LanceDB embedded stack, and the dream-phase per-op toggles.
Spelling. All env vars in this section use single underscore between every level: KHORA_STORAGE_GRAPH_URL, not KHORA_STORAGE__GRAPH__URL. The legacy double-underscore form continues to work as a backwards-compatible alias on every nested-config field. It is no longer documented. New code and .env files should use the single-underscore form.

Neo4j graph backend

Configuration for the Neo4j graph backend (storage.graph).
VariableDefaultWhy change it
KHORA_STORAGE_GRAPH_BACKENDneo4jGraph adapter. Neo4j on the production stack.
KHORA_STORAGE_GRAPH_URLBolt / connection URL. SecretStr.
KHORA_STORAGE_GRAPH_USERneo4jUsername.
KHORA_STORAGE_GRAPH_PASSWORDemptyPassword. SecretStr.
KHORA_STORAGE_GRAPH_DATABASEneo4jMulti-database selector inside a Neo4j cluster.
KHORA_STORAGE_GRAPH_MAX_CONNECTION_POOL_SIZE100Lower on small drivers; raise for high concurrency.
KHORA_STORAGE_GRAPH_CONNECTION_ACQUISITION_TIMEOUT60.0 sLower for fast-fail under pool starvation.
KHORA_STORAGE_GRAPH_RETRY_DELAY_JITTER_FACTOR0.5Jitter (0.0–1.0) on transaction-retry backoff. Raise to spread retry storms.
KHORA_STORAGE_GRAPH_MAX_CONNECTION_LIFETIME900 sRotate connections before this. Set below your server-side TTL (Aura ~20 min) to avoid BrokenPipe.
KHORA_STORAGE_GRAPH_LIVENESS_CHECK_TIMEOUT30.0 sIdle threshold before pre-checkout liveness check. None disables.
KHORA_STORAGE_GRAPH_QUERY_TIMEOUT5.0 sPer-transaction read timeout (1–300 s, None disables). Raise for deep traversals; lower to fail fast.
KHORA_STORAGE_GRAPH_ENTITY_WRITE_CONCURRENCY12Concurrent entity-write transactions during ingest. Raise when Neo4j has headroom; lower on lock contention.
KHORA_STORAGE_GRAPH_RELATIONSHIP_WRITE_CONCURRENCY8Concurrent relationship-write transactions.
KHORA_STORAGE_GRAPH_POOL_SAMPLER_ENABLEDfalseOpt-in high-frequency pool sampler. Requires an OTel backend installed. Enable to investigate pool exhaustion; zero-cost when off.
KHORA_STORAGE_GRAPH_POOL_SAMPLER_INTERVAL_MS500Sample cadence in ms (clamped 50–60000). Drop to 50–100 when chasing sub-second pool events.
KHORA_STORAGE_GRAPH_RELATIONSHIP_SOURCE_DOCUMENT_IDS_MAX100Cap on Relationship.source_document_ids retained after MERGE. When the cap is exceeded, the most-recent tail is kept and the dropped count is recorded on khora.neo4j.relationship.source_id_truncated{field=source_document_ids}. Raise for deep-provenance workloads.
KHORA_STORAGE_GRAPH_RELATIONSHIP_SOURCE_CHUNK_IDS_MAX250Same as above, for source_chunk_ids.
KHORA_STORAGE_GRAPH_ENTITY_SOURCE_DOCUMENT_IDS_MAX100Cap on Entity.source_document_ids retained after MERGE. Tail-keep semantics identical to the relationship cap; metric is khora.neo4j.entity.source_id_truncated{field=source_document_ids}.
KHORA_STORAGE_GRAPH_ENTITY_SOURCE_CHUNK_IDS_MAX250Same as above, for entity source_chunk_ids.

Vector backend

Discriminated union over PgVectorConfig | SQLiteVectorConfig, keyed by backend. Default is pgvector via default_factory=PgVectorConfig.
VariableDefaultApplies whenWhy change it
KHORA_STORAGE_VECTOR_BACKENDpgvectoralwayspgvector / sqlite.
KHORA_STORAGE_VECTOR_URLpgvector / sqliteConnection URL.
KHORA_STORAGE_VECTOR_EMBEDDING_DIMENSION1536alwaysMust match the LLM embedding model. Changing requires a schema migration.
For backend=sqlite, only BACKEND / URL / EMBEDDING_DIMENSION are model-exposed.

Embedded backend

Used when KHORA_STORAGE_BACKEND=sqlite_lance. Pairs an on-disk SQLite database (graph + relational + event store) with a sibling LanceDB directory (vector search). Zero infrastructure: both backends run in-process.
VariableDefaultWhy change it
KHORA_STORAGE_SQLITE_LANCE_DB_PATH./khora.dbSQLite file path. Move to faster storage or a backup-friendly location.
KHORA_STORAGE_SQLITE_LANCE_LANCE_PATHsibling .lance dirExplicit LanceDB directory. Override to put vectors on different storage (e.g. SSD) than chunk metadata.
KHORA_STORAGE_SQLITE_LANCE_EMBEDDING_DIMENSION1536Must match LLM embedding model.
KHORA_STORAGE_SQLITE_LANCE_USE_HALFVECfalseFloat16 storage: halves index size with minor recall loss. Enable on memory-constrained boxes.
KHORA_STORAGE_SQLITE_LANCE_LANCE_INDEXautoauto / ivf_pq / hnsw / brute. Force ivf_pq above ~1M rows; hnsw for low-latency under ~1M; brute for tiny corpora.
KHORA_STORAGE_SQLITE_LANCE_IVF_PARTITIONSnull (auto)Hand-tuned IVF partition count (lance_index=ivf_pq only). Override only if profiling shows recall miss.
KHORA_STORAGE_SQLITE_LANCE_HNSW_M16HNSW max connections per layer (lance_index=hnsw only). Raise for recall; linear memory cost.
KHORA_STORAGE_SQLITE_LANCE_RETRAIN_FACTOR2.0Trigger LanceDB ANN retrain when row count grows by this factor. Lower for fresher index; raise to defer re-training cost. <= 1.0 disables.

Dream-phase per-op toggles

DreamConfig.ops: DreamOpsConfig carries per-operation enable flags. Every destructive op defaults to false. KHORA_DREAM_ENABLED=true alone runs no destructive work, and each op must be flipped explicitly. See Dream phase for operational guidance, retention floors, and the kill-switch (KHORA_DREAM_DISABLE_APPLY).
VariableDefaultWhy change it
KHORA_DREAM_OPS_DEDUPE_ENTITIESfalseEnable cross-batch entity dedupe (cosine-merge with verifier). Turn on after validating planner output in dry-run.
KHORA_DREAM_OPS_PRUNE_EDGESfalseRemove low-confidence / orphaned edges (default targets ASSOCIATED_WITH co-occurrence). Turn on when edge soup degrades retrieval.
KHORA_DREAM_OPS_COMPACT_FACTSfalseHard-delete tombstoned memory_facts rows past the 7-day retention floor. The only hard-delete op. Flip with care.
KHORA_DREAM_OPS_CLUSTER_EVENTSfalseMerge near-duplicate events (cosine ≥ 0.95 within a 7-day window).
KHORA_DREAM_OPS_RECOMPUTE_CENTROIDSfalseRecompute entity / cluster centroid embeddings after dedupe. Pair with DEDUPE_ENTITIES.

Env vars that are not nested

These reach top-level fields of each sub-settings class via the sub-class’s own env_prefix. There is no sub-object hop, and they’re covered in the sections above:
  • KHORA_STORAGE_BACKEND, KHORA_STORAGE_POSTGRESQL_*, KHORA_STORAGE_HNSW_*, KHORA_STORAGE_USE_HALFVEC: flat on StorageSettings.
  • KHORA_LLM_*: every field on LLMSettings is top-level (no sub-objects).
  • KHORA_PIPELINES_*: every field on PipelineSettings is top-level.
  • KHORA_QUERY_*: every field on QuerySettings is top-level.
  • KHORA_TELEMETRY_*: flat.
  • KHORA_DREAM_* (e.g. KHORA_DREAM_ENABLED, KHORA_DREAM_DEFAULT_MODE, KHORA_DREAM_LLM_MAX_TOKENS_PER_RUN): flat on DreamConfig. Only the per-op toggles under the ops: sub-object are listed above.