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] - Built-in defaults. Every field in
TelhaConfighas aDefaultimpl; an empty config is a complete, valid config. - TOML file. Pass
--config <path>on any subcommand, or setTELHA_CONFIG(read by the CLI'sRunArgs, not by the config loader itself). A path that does not exist is an error; an omitted--configsimply skips this layer. TELHA_*environment variables. Nested keys use a double underscore (__) as the separator, e.g.TELHA_REST__ADDR=0.0.0.0:7625orTELHA_LLM__TENANT_CAPS__DAILY_TOKENS=2000000. Only a fixed whitelist of dotted keys is read (ENV_KEYSinconfig.rs) so unrelatedTELHA_*variables, includingTELHA_CONFIGitself, never leak into the figment.- 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 toserve,check-config, and every subcommand that takesRunArgs.
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:
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] |
Related¶
- Reference › Configuration keys, the exhaustive per-key table.
- Key & secret management, API keys,
grpc.token_key, the field-encryption KEK, and LLM/embedding credentials. - Backup & restore,
[backup]in practice. - Retention & archival,
[retention]in practice. - Troubleshooting, symptom-to-cause tables including the all-digit token key gotcha.
- Deployment runbook, install and first-boot config walkthrough.