Skip to content

Configuration reference

Layers · sections · keys

Telha loads one TelhaConfig from four layers, lowest precedence first, and validates the merged result before serve binds a single port. This page walks every section; the exhaustive per-key table lives in Reference › Configuration keys.

Normative spec

The layering rules, the environment whitelist, and every default and validation rule are implemented in core-engine/src/config.rs (TelhaConfig::load, ENV_KEYS, TelhaConfig::validate).

How layering works

flowchart LR
    A[Built-in defaults<br/>TelhaConfig::default] --> B[TOML file<br/>--config or TELHA_CONFIG]
    B --> C["TELHA_* env vars<br/>__ nesting, whitelisted keys only"]
    C --> D[CLI flags<br/>whitelisted overrides]
    D --> E[Validated TelhaConfig]
  1. Built-in defaults. Every field in TelhaConfig has a Default impl; an empty config is a complete, valid config.
  2. TOML file. Pass --config <path> on any subcommand, or set TELHA_CONFIG (read by the CLI's RunArgs, not by the config loader itself). A path that does not exist is an error; an omitted --config simply skips this layer.
  3. TELHA_* environment variables. Nested keys use a double underscore (__) as the separator, e.g. TELHA_REST__ADDR=0.0.0.0:7625 or TELHA_LLM__TENANT_CAPS__DAILY_TOKENS=2000000. Only a fixed whitelist of dotted keys is read (ENV_KEYS in config.rs) so unrelated TELHA_* variables, including TELHA_CONFIG itself, never leak into the figment.
  4. CLI flags. A small set of fields can be overridden per-invocation: --data-dir, --rest-addr, --grpc-addr, --uds-path, --log-level, --log-format. These win over everything else and apply to serve, check-config, and every subcommand that takes RunArgs.

The all-digit env gotcha

The environment layer parses a value that is all decimal digits as an integer, not a string. grpc.token_key is a 64-character hex string; if you generate one that happens to contain no letters (openssl rand -hex 32 can produce this by chance), TELHA_GRPC__TOKEN_KEY fails to parse as the expected string type and the server refuses to start or silently leaves gRPC/MCP disabled. Regenerate until the value contains at least one letter (a-f). This is called out in the deployment runbook and the operator handbook because it is the single most-hit config trap in practice.

Validate the fully resolved configuration (file + env + flags, in that order) before starting the server for real:

telha check-config --config /etc/telha/telha.toml

check-config prints every non-fatal warning to stderr, then prints the resolved configuration as TOML to stdout, then exits 0. A structurally or semantically invalid configuration exits non-zero with the specific validation error (see each section below for what gets validated).

data_dir

The single top-level scalar. Directory holding RocksDB column families, vector partition files, api_keys.json, and the erasure ledger. Created on serve if missing; must not be empty.

Key Type Default
data_dir path ./data

[rest]

The REST/Axum listener. GET /healthz and GET /readyz are served unauthenticated from this listener.

Key Env override Default
rest.addr TELHA_REST__ADDR 127.0.0.1:7625

Port 0 binds ephemerally. rest.addr and grpc.addr must differ unless both use port 0 (checked at load).

[grpc]

The gRPC/Tonic listener that carries the internal worker, MCP, clarify, admin, and connector token audiences. gRPC (and MCP, which shares its token key) stay disabled until token_key is set.

Key Env override Default
grpc.addr TELHA_GRPC__ADDR 127.0.0.1:7626
grpc.uds_path TELHA_GRPC__UDS_PATH unset
grpc.token_key TELHA_GRPC__TOKEN_KEY unset

token_key is a 64-character hex string (32 bytes) used as an HMAC key for every internal service token. uds_path is Unix-only; setting it on Windows produces a startup warning and gRPC falls back to grpc.addr (TCP).

[log]

Key Env override Default
log.level TELHA_LOG__LEVEL info
log.format TELHA_LOG__FORMAT json

log.level is a tracing_subscriber::EnvFilter directive (e.g. info or telha_core=debug,info) and is validated as such at load; an invalid filter string is a fatal config error. log.format is json (structured, production) or pretty (human-readable, local dev).

[storage]

RocksDB tuning. No environment overrides are whitelisted for this section (TOML file only).

Key Default
storage.block_cache_mb 256
storage.sync_writes true

Turning sync_writes off trades crash-durability of the most recent write batch for throughput; leave it on unless you understand the tradeoff.

[global_scan]

Limits for the global temporal scan operator. No environment overrides are whitelisted (TOML file only).

Key Default Meaning
global_scan.horizon_days 3653 (10 years) Iteration stops once a candidate's valid-time start drops this far below the queried instant. Records older than the horizon that are still valid are missed, so size generously.
global_scan.max_rows_scanned 1000000 Hard scan ceiling; exceeding it returns partial-with-warning.
global_scan.max_page_size 1000 Upper bound on a single result page.

[compare]

/v1/compare temporal-diff limits.

Key Env override Default
compare.max_scan_rows TELHA_COMPARE__MAX_SCAN_ROWS 2000000

Beyond this ceiling a compare page returns partial-with-warning and a resume cursor.

[decay]

Confidence decay parameters (path-based decay across graph hops).

Key Env override Default Range
decay.default_weight TELHA_DECAY__DEFAULT_WEIGHT 0.7 (0, 1]
decay.floor TELHA_DECAY__FLOOR 0.05 [0, 1)
decay.fusion_alpha TELHA_DECAY__FUSION_ALPHA 0.6 [0, 1]
decay.weights.<EDGE_TYPE> not whitelisted (TOML only) (none) (0, 1]

fusion_alpha balances similarity and decay in fused scoring: S = sim^α · decay^(1−α), with a per-query override available via vector.alpha. decay.weights is a per-edge-type override map, e.g. decay.weights.OWNS = 0.9; it is TOML-only because a map has no __-safe single env key. Paths whose decay falls below floor are pruned rather than expanded. All weights are validated at load; out-of-range values are a fatal config error, never silently clamped.

[bitmap]

Bitmap predicate acceleration for equality/range filters. Enabling on an existing dataset requires telha index rebuild-bitmaps first.

Key Env override Default
bitmap.enabled TELHA_BITMAP__ENABLED false
bitmap.max_values_per_property TELHA_BITMAP__MAX_VALUES_PER_PROPERTY 10000
bitmap.candidate_ratio TELHA_BITMAP__CANDIDATE_RATIO 0.3
bitmap.range_bands not whitelisted (TOML only) 16

range_bands must equal 16; v1 supports no other value and any other value is a fatal config error. The bitmap path is chosen only when the candidate count is below watermark × candidate_ratio.

[jobs]

Job queue leasing and retry.

Key Env override Default
jobs.lease_ttl_secs TELHA_JOBS__LEASE_TTL_SECS 300
jobs.reaper_interval_secs TELHA_JOBS__REAPER_INTERVAL_SECS 30
jobs.max_attempts TELHA_JOBS__MAX_ATTEMPTS 5
jobs.backoff_base_secs not whitelisted (TOML only) 10
jobs.backoff_factor not whitelisted (TOML only) 4
jobs.backoff_cap_secs not whitelisted (TOML only) 3600
jobs.backoff_base_secs_by_kind.<KIND> not whitelisted (internal) (none)

lease_ttl_secs and max_attempts must be positive; a dead job after max_attempts needs telha jobs retry to re-enter the queue. backoff_base_secs_by_kind is populated internally (for example clarify:temporal inherits the clarification escalation window) and is not an operator-facing env key.

[ingest]

Behaviour of the JSON direct ingestion path.

Key Env override Default
ingest.json_nested not whitelisted (TOML only) flatten
ingest.json_max_depth not whitelisted (TOML only) 8
ingest.json_max_records not whitelisted (TOML only) 10000

json_nested is flatten (nested objects become dotted property keys like address.city) or child (nested objects become child nodes linked by HAS_<KEY> edges); either can be overridden per-request via options.nested.

[embedding]

Activates when both endpoint and model are set. The API key is read from an environment variable named by api_key_env, never stored in the config file.

Key Env override Default
embedding.endpoint not whitelisted (TOML only) "" (disabled)
embedding.api_key_env not whitelisted (TOML only) TELHA_EMBEDDING_API_KEY
embedding.model not whitelisted (TOML only) "" (no auto fan-out)
embedding.batch_size not whitelisted (TOML only) 96
embedding.max_text_bytes not whitelisted (TOML only) 32768
embedding.poll_interval_secs not whitelisted (TOML only) 5
embedding.timeout_secs not whitelisted (TOML only) 60

After setting endpoint and model in TOML, register the model with telha vector register-model before ingestion will fan text out to it. See Key & secret management for the embedding credential.

[vector]

Partitioned HNSW index tuning (no environment overrides whitelisted; TOML file only).

Key Default Meaning
vector.bucket month Time-bucket size for partitions: week (7d), month (30d), or quarter (90d).
vector.seal_grace_secs 172800 (48h) Seconds past a bucket's end before its open partition seals.
vector.memory_budget_mb 4096 Memory budget for loaded sealed partitions; an LRU evicts above it.
vector.ef_search 64 Default ef_search; a per-query override is bounded to 4× this.
vector.hnsw_m 16 HNSW connectivity (M).
vector.hnsw_ef_construction 200 HNSW build-time expansion.

[mcp]

The integrated MCP server. The listener starts only when enabled is true and grpc.token_key is set, because MCP sessions authenticate with the same signed-token scheme as gRPC (audience mcp).

Key Env override Default
mcp.enabled TELHA_MCP__ENABLED true
mcp.addr TELHA_MCP__ADDR 127.0.0.1:7627
mcp.max_rows TELHA_MCP__MAX_ROWS 50
mcp.max_field_chars TELHA_MCP__MAX_FIELD_CHARS 500
mcp.specs_resource not whitelisted (TOML only) true
mcp.specs_dir TELHA_MCP__SPECS_DIR ./.ai/specs

max_rows and max_field_chars must be positive when enabled is true. mcp.addr must differ from both rest.addr and grpc.addr (unless all use port 0). specs_resource exposes .ai/specs/**/*.md as the specs:// MCP resource; specs_dir resolves relative paths against the process working directory.

[planner]

The evidence planner: how much source material a generation request may draw on and how it is weighted.

Key Env override Default Range
planner.default_budget TELHA_PLANNER__DEFAULT_BUDGET 6000 positive
planner.max_frac_per_source TELHA_PLANNER__MAX_FRAC_PER_SOURCE 0.4 (0, 1]
planner.cache_discount TELHA_PLANNER__CACHE_DISCOUNT 0.5 (0, 1)
planner.dedup_overlap TELHA_PLANNER__DEDUP_OVERLAP 0.8 (0.5, 1]
planner.kind_priors.<KIND> not whitelisted (TOML only) see below (0, 1]

Default kind_priors: paragraph = 1.0, heading = 0.6, cell = 0.8, code = 0.9. Kinds absent from the map take prior 1.0. The effective token budget is min(default_budget, floor(model_ctx × 0.5)) when the provider reports a context window. max_frac_per_source is a hard per-source diversity cap that is never relaxed; stranded budget shows up in response stats instead. Every ratio is validated at load and rejected outright on an out-of-range value, never silently clamped.

[llm]

Generation orchestration (/v1/generate). The subsystem activates only when provider is set; there is deliberately no credential field here (see Key & secret management).

Key Env override Default
llm.provider TELHA_LLM__PROVIDER unset (generation disabled)
llm.endpoint TELHA_LLM__ENDPOINT ""
llm.api_key_env TELHA_LLM__API_KEY_ENV TELHA_LLM_API_KEY
llm.secrets_file TELHA_LLM__SECRETS_FILE ""
llm.model_default TELHA_LLM__MODEL_DEFAULT ""
llm.models_allowed not whitelisted (TOML only) []
llm.max_output_tokens TELHA_LLM__MAX_OUTPUT_TOKENS 4096
llm.timeout_s TELHA_LLM__TIMEOUT_S 120
llm.tenant_caps.daily_tokens TELHA_LLM__TENANT_CAPS__DAILY_TOKENS 0 (unlimited)
llm.tenant_caps.monthly_tokens TELHA_LLM__TENANT_CAPS__MONTHLY_TOKENS 0 (unlimited)

provider is anthropic or openai_compat. When provider is set, validation requires: endpoint non-empty and passing egress validation (HTTPS, or plain HTTP only to a loopback host, localhost, 127.0.0.1, or ::1, so an Authorization header never crosses the network in the clear); model_default non-empty; max_output_tokens and timeout_s positive; and at least one of api_key_env / secrets_file naming a credential source. The effective model allowlist is models_allowed ∪ {model_default}; a request naming any other model is rejected with 400. Endpoint/provider/model configuration is operator-only. tenant_caps are default per-tenant token ceilings (input + output, 0 = unlimited); see the operator handbook's reservation-model section for how reservation and settlement work against these caps.

[verify]

Claim verification against planned evidence spans.

Key Env override Default Range
verify.beta TELHA_VERIFY__BETA 0.65 (0, 1)
verify.bands.supported TELHA_VERIFY__BANDS__SUPPORTED 0.75 ±0.1 of default
verify.bands.partial TELHA_VERIFY__BANDS__PARTIAL 0.50 ±0.1 of default
verify.candidates_m TELHA_VERIFY__CANDIDATES_M 8 positive
verify.policy TELHA_VERIFY__POLICY flag flag | redact
verify.decompose_model TELHA_VERIFY__DECOMPOSE_MODEL "" ,
verify.embed_model TELHA_VERIFY__EMBED_MODEL "" ,

beta is the geometric fusion balance for claim/span matching (match = clamp01(cos)^β · lex^(1−β)); 0 and 1 are formula fast-paths, not valid config. Band overrides are bounded to ±0.1 of the shipped defaults, and supported must remain at least partial + 0.10 after overrides, so bands can never invert or collapse. decompose_model empty uses the generation model for claim decomposition; embed_model empty uses the request's semantic model (else embedding.model) so claims and spans are embedded under exactly one space.

[clarify]

The temporal clarification loop (human-in-the-loop resolution of conflicting or uncertain facts). Ships disabled.

Key Env override Default
clarify.enabled TELHA_CLARIFY__ENABLED false
clarify.escalation_window_hours TELHA_CLARIFY__ESCALATION_WINDOW_HOURS 48
clarify.max_candidates TELHA_CLARIFY__MAX_CANDIDATES 3
clarify.per_person_daily_cap TELHA_CLARIFY__PER_PERSON_DAILY_CAP 5
clarify.digest_threshold TELHA_CLARIFY__DIGEST_THRESHOLD 3
clarify.digest_window_minutes TELHA_CLARIFY__DIGEST_WINDOW_MINUTES 30
clarify.redact_fallback TELHA_CLARIFY__REDACT_FALLBACK true
clarify.channels TELHA_CLARIFY__CHANNELS teams,slack
clarify.quorum.enabled TELHA_CLARIFY__QUORUM__ENABLED true
clarify.quorum.critical_required_confirmations TELHA_CLARIFY__QUORUM__CRITICAL_REQUIRED_CONFIRMATIONS 2
clarify.quorum.required_roles not whitelisted (TOML only) ["editor"]
clarify.quorum.allow_steward_override TELHA_CLARIFY__QUORUM__ALLOW_STEWARD_OVERRIDE true
clarify.severity.high_notifies_steward TELHA_CLARIFY__SEVERITY__HIGH_NOTIFIES_STEWARD true
clarify.severity.elevate.<KIND> not whitelisted (TOML only) {}

escalation_window_hours and max_candidates must be positive. quorum.critical_required_confirmations must be positive. quorum.required_roles entries must parse as viewer|editor|steward; severity.elevate values must parse as low|normal|high|critical. Enabling clarify without grpc.token_key set is a non-fatal warning (from check-config): clarifications will still be created and routed, but no clarify-bot can poll or answer them until a token key exists. See the Clarify bot page for CLARIFY_BOT_CONFIG and delivery.

[backup]

The in-process backup ticker. The telha backup CLI works regardless of enabled. See Backup & restore for the full procedure.

Key Env override Default
backup.enabled TELHA_BACKUP__ENABLED false
backup.dir TELHA_BACKUP__DIR unset
backup.interval_hours TELHA_BACKUP__INTERVAL_HOURS 24
backup.keep TELHA_BACKUP__KEEP 7
backup.pg_database_url_env TELHA_BACKUP__PG_DATABASE_URL_ENV DATABASE_URL

backup.enabled requires backup.dir to be set and interval_hours to be positive, or the config fails to load. keep = 0 disables pruning. pg_database_url_env names (never contains) the environment variable holding the app-layer PostgreSQL connection string for the two-surface backup capture.

[retention]

The in-process retention ticker. Off by default; nothing expires without an explicit policy. See Retention & archival.

Key Env override Default
retention.enabled TELHA_RETENTION__ENABLED false
retention.interval_hours TELHA_RETENTION__INTERVAL_HOURS 24
retention.orphan_ttl_days TELHA_RETENTION__ORPHAN_TTL_DAYS 14
retention.archive_dir TELHA_RETENTION__ARCHIVE_DIR unset (defaults under {backup.dir}/archives)
retention.max_entities_per_run TELHA_RETENTION__MAX_ENTITIES_PER_RUN 10000

retention.enabled requires a resolvable archive target: either retention.archive_dir is set, or backup.dir is set for it to default under. interval_hours must be positive when enabled. max_entities_per_run must always be positive (bounds each run; the scan resumes on the next interval).

[watch]

Memory-watch subscriptions (webhook/event delivery on matching writes). The CLI and REST/MCP surfaces work regardless of enabled; the flag gates only the in-process evaluator ticker. No environment overrides are whitelisted for this section (TOML file only).

Key Default
watch.enabled false
watch.eval_interval_secs 60
watch.max_watches_per_tenant 1000
watch.max_rows_per_tick 50000
watch.debounce_default_secs 3600
watch.debounce_floor_secs 60
watch.max_events_per_fire 100
watch.query_watch_max_results 1000
watch.suspend_after_gate_denials 5
watch.webhook_timeout_secs 10
watch.webhook_max_attempts 3
watch.fire_keep_per_watch 1000

[erasure]

GDPR erasure attestation settings. Deliberately not about mechanism (the masking, ledger, dual-approval, and verification behaviour are doctrine, not knobs), these keys capture facts the completion report attests to. No environment overrides are whitelisted (TOML file only).

Key Default Meaning
erasure.backup_horizon_days 35 Backups older than this age out naturally; the completion report records the date after which no backup contains the erased data.
erasure.log_retention_days_max 30 Operational log retention bound attested in the completion report.
erasure.log_targets ["app_logs","worker_logs","reverse_proxy_logs","job_runner_logs","cloud_provider_logs","error_tracking","support_exports"] Log targets the operator attests are covered by the bound.
erasure.per_subject_log_scrub_supported false Whether this deployment supports per-subject log scrubbing.
erasure.expect_pg_stage false Whether an app-layer PostgreSQL stage is expected to report before verification.

Validation summary

telha check-config runs every rule above in one pass and reports the first failure. Common failure classes:

Failure Where
data_dir must not be empty top level
rest.addr and grpc.addr must differ [rest] / [grpc]
log.level ... is not a valid filter [log]
decay.default_weight ... must be in (0, 1] [decay]
bitmap.range_bands ... v1 supports exactly 16 [bitmap]
jobs.lease_ttl_secs and jobs.max_attempts must be positive [jobs]
mcp.addr and rest.addr must differ [mcp]
planner.kind_priors.<kind> ... must be in (0, 1] [planner]
llm.endpoint is required when llm.provider is set [llm]
llm.endpoint ... uses plaintext http to a non-loopback host [llm]
verify.beta ... must be in (0, 1) [verify]
verify.bands overrides are bounded to ±0.1 of the defaults [verify]
clarify.quorum.required_roles: unknown role [clarify]
backup.enabled requires backup.dir [backup]
retention.enabled requires retention.archive_dir (or backup.dir to default under) [retention]