Configuration keys
Reference
Every key TelhaConfig accepts, its type, default, TELHA_* environment override, and a one-line description. This is a strict schema: unknown keys in a TOML file are rejected (deny_unknown_fields), and the environment provider only accepts the keys listed here.
Layering
Precedence, lowest to highest: built-in defaults → TOML config file (--config / TELHA_CONFIG) → TELHA_* environment variables → CLI flags. Nested keys use __ (double underscore) as the environment separator, e.g. rest.addr becomes TELHA_REST__ADDR. Only the keys in the tables below are read from the environment; unrelated TELHA_* variables (like TELHA_CONFIG itself, which belongs to the CLI) never leak into the config figment.
Gotcha: all-digit values parse as integers
The environment layer (figment) infers types from the string value. A value made entirely of digits, such as a grpc.token_key that happens to be all-numeric hex, is parsed as an integer and rejected because the field expects a string. Regenerate the key until it contains at least one letter (openssl rand -hex 32 almost always does, but check). See Operators › Configuration and the deployment runbook for the full rotation flow.
data_dir
| Key | Env | Type | Default | Description |
data_dir | TELHA_DATA_DIR | path | ./data | Directory holding RocksDB column families, vector partitions, and other engine state. Created on serve if missing. |
[rest]
| Key | Env | Type | Default | Description |
rest.addr | TELHA_REST__ADDR | socket addr | 127.0.0.1:7625 | Socket address for the REST listener. Port 0 binds ephemerally. |
[grpc]
| Key | Env | Type | Default | Description |
grpc.addr | TELHA_GRPC__ADDR | socket addr | 127.0.0.1:7626 | TCP socket address for the gRPC listener. |
grpc.uds_path | TELHA_GRPC__UDS_PATH | path (optional) | none | Unix domain socket path, preferred over TCP for same-host app-layer traffic on Unix. Ignored with a warning on Windows. |
grpc.token_key | TELHA_GRPC__TOKEN_KEY | string (optional) | none | Hex-encoded 32-byte HMAC key that signs and validates every internal service token (app, worker, mcp, clarify, admin, connector audiences). gRPC and MCP stay disabled until this is set. Must contain at least one non-digit character (see the gotcha above). |
[log]
| Key | Env | Type | Default | Description |
log.level | TELHA_LOG__LEVEL | string | info | Tracing filter directive, e.g. info or telha_core=debug,info. |
log.format | TELHA_LOG__FORMAT | enum: json|pretty | json | json for production (structured lines), pretty for local development. |
[storage]
| Key | Env | Type | Default | Description |
storage.block_cache_mb | TELHA_STORAGE__BLOCK_CACHE_MB | u64 | 256 | Shared RocksDB block cache size in MiB. |
storage.sync_writes | TELHA_STORAGE__SYNC_WRITES | bool | true | Fsync the WAL on every write batch. Turning this off trades crash-durability of the most recent writes for throughput. |
[global_scan]
| Key | Env | Type | Default | Description |
global_scan.horizon_days | TELHA_GLOBAL_SCAN__HORIZON_DAYS | u32 | 3653 (10 years) | Iteration stops once a candidate's validTimeStart drops more than this many days below the queried instant. Records older than the horizon that are still valid would be missed, so size generously. |
global_scan.max_rows_scanned | TELHA_GLOBAL_SCAN__MAX_ROWS_SCANNED | u64 | 1_000_000 | Hard row-scan ceiling; exceeding it returns partial-with-warning. |
global_scan.max_page_size | TELHA_GLOBAL_SCAN__MAX_PAGE_SIZE | usize | 1_000 | Upper bound on a single page of results. |
[decay]
| Key | Env | Type | Default | Description |
decay.default_weight | TELHA_DECAY__DEFAULT_WEIGHT | f64, (0, 1] | 0.7 | Per-hop weight for edge types without an override. |
decay.floor | TELHA_DECAY__FLOOR | f64, [0, 1) | 0.05 | Decay floor ε: paths are pruned (not expanded) below this value. |
decay.fusion_alpha | TELHA_DECAY__FUSION_ALPHA | f64, [0, 1] | 0.6 | Fusion balance α: S = sim^α · decay^(1−α). Per-query override via vector.alpha. |
decay.weights.<EDGE_TYPE> | TOML only | f64, (0, 1] | none | Per-edge-type weight overrides, e.g. weights.OWNS = 0.9. Not on the env allowlist; set via TOML. |
[bitmap]
| Key | Env | Type | Default | Description |
bitmap.enabled | TELHA_BITMAP__ENABLED | bool | false | Master switch for the roaring-bitmap predicate accelerator. Enabling on an existing dataset requires telha index rebuild-bitmaps first. |
bitmap.max_values_per_property | TELHA_BITMAP__MAX_VALUES_PER_PROPERTY | u32 | 10_000 | Distinct eq-values per property before it overflows to a full scan. |
bitmap.candidate_ratio | TELHA_BITMAP__CANDIDATE_RATIO | f64, [0, 1] | 0.3 | The bitmap path is chosen only when candidates < watermark × ratio. |
bitmap.range_bands | TOML only | u8 | 16 | Range bands for range predicates. v1 supports exactly 16; any other value fails validation. |
[jobs]
| Key | Env | Type | Default | Description |
jobs.lease_ttl_secs | TELHA_JOBS__LEASE_TTL_SECS | u64 | 300 | Lease TTL in seconds; a Heartbeat call extends it. |
jobs.reaper_interval_secs | TELHA_JOBS__REAPER_INTERVAL_SECS | u64 | 30 | Reaper wake interval in seconds. |
jobs.max_attempts | TELHA_JOBS__MAX_ATTEMPTS | u32 | 5 | Attempts before a job dead-letters. |
jobs.backoff_base_secs | TOML only | u64 | 10 | Retry backoff base, seconds (exponential). |
jobs.backoff_factor | TOML only | u64 | 4 | Retry backoff factor. |
jobs.backoff_cap_secs | TOML only | u64 | 3_600 | Retry backoff cap, seconds. |
jobs.backoff_base_secs_by_kind.<KIND> | TOML only | u64 | empty map | Per-kind backoff base overrides; an override also lifts the cap to at least itself. Populated internally (e.g. clarify:temporal); not an env-layer key. |
[ingest]
| Key | Env | Type | Default | Description |
ingest.json_nested | TOML only | enum: flatten|child | flatten | Default nested-object policy for format=json. flatten turns nested objects into dotted property keys (address.city); child makes them child nodes linked by HAS_<KEY> edges. Overridable per-request via options.nested. |
ingest.json_max_depth | TOML only | usize | 8 | Nesting-depth ceiling for the JSON direct path. |
ingest.json_max_records | TOML only | usize | 10_000 | Top-level record ceiling per JSON request. |
[embedding]
The embedding subsystem activates when both endpoint and model are set.
| Key | Env | Type | Default | Description |
embedding.endpoint | TOML only | string | "" | OpenAI-compatible endpoint base (e.g. https://api.openai.com/v1, or a local server). Empty disables embedding. |
embedding.api_key_env | TOML only | string | TELHA_EMBEDDING_API_KEY | Env var NAME holding the bearer token (empty = unauthenticated local endpoint). |
embedding.model | TOML only | string | "" | Default model for ingestion fan-out; must be registered (telha vector register-model). Empty disables automatic fan-out. |
embedding.batch_size | TOML only | usize | 96 | Max texts per provider call. |
embedding.max_text_bytes | TOML only | usize | 32768 (32 KiB) | Truncation ceiling for assembled node text. |
embedding.poll_interval_secs | TOML only | u64 | 5 | Embed-runner poll interval, seconds. |
embedding.timeout_secs | TOML only | u64 | 60 | Provider request timeout, seconds. |
[vector]
Partitioned-HNSW settings.
| Key | Env | Type | Default | Description |
vector.bucket | TOML only | enum: week|month|quarter | month | Time-bucket size for partitions (week = 7d, month = 30d, quarter = 90d). |
vector.seal_grace_secs | TOML only | u64 | 172_800 (48h) | Seconds past a bucket's end before its open partition seals; a late-arriving backfill grace period. |
vector.memory_budget_mb | TOML only | u64 | 4096 | Memory budget for loaded sealed partitions; an LRU evicts above it. |
vector.ef_search | TOML only | usize | 64 | Default ef_search; a per-query override is bounded by 4× this. |
vector.hnsw_m | TOML only | usize | 16 | HNSW connectivity (M). |
vector.hnsw_ef_construction | TOML only | usize | 200 | HNSW build-time expansion (ef_construction). |
[mcp]
The MCP listener starts only when enabled is true and grpc.token_key is set.
| Key | Env | Type | Default | Description |
mcp.enabled | TELHA_MCP__ENABLED | bool | true | Master switch for the MCP listener. |
mcp.addr | TELHA_MCP__ADDR | socket addr | 127.0.0.1:7627 | Socket address for the MCP streamable-HTTP listener. |
mcp.max_rows | TELHA_MCP__MAX_ROWS | usize | 50 | Hard cap on rows any tool returns. |
mcp.max_field_chars | TELHA_MCP__MAX_FIELD_CHARS | usize | 500 | Per-field text cap in characters; longer values truncate with an ellipsis and a full-fetch hint. |
mcp.specs_resource | TOML only | bool | true | Expose .ai/specs/**/*.md as the specs:// MCP resource. |
mcp.specs_dir | TELHA_MCP__SPECS_DIR | path | ./.ai/specs | Directory the specs:// resource reads from. Relative paths resolve against the process working directory. |
[planner]
Evidence-planner settings. All ratios are validated at load and never silently clamped.
| Key | Env | Type | Default | Description |
planner.default_budget | TELHA_PLANNER__DEFAULT_BUDGET | u64 | 6_000 | Default token budget B when the request supplies none. The effective budget is min(B, floor(model_ctx × 0.5)) when the provider reports a context window. |
planner.max_frac_per_source | TELHA_PLANNER__MAX_FRAC_PER_SOURCE | f64, (0, 1] | 0.4 | Hard per-source diversity cap: at most this fraction of B may come from spans of one source_id. Never relaxed; stranded budget is reported in stats instead. |
planner.cache_discount | TELHA_PLANNER__CACHE_DISCOUNT | f64, (0, 1) | 0.5 | Weight multiplier for cache-class (derived) spans. |
planner.dedup_overlap | TELHA_PLANNER__DEDUP_OVERLAP | f64, (0.5, 1] | 0.8 | Same-source span overlap above which the lower-weight span is dropped. |
planner.kind_priors.<KIND> | TOML only | f64, (0, 1] | paragraph=1.0, heading=0.6, cell=0.8, code=0.9 | Span-kind weight priors; the single source of truth for span-kind weighting. Kinds absent from the map take prior 1.0. |
[llm]
The generation subsystem activates when provider is set. There is deliberately no credential field: keys come from the env var named by api_key_env or from secrets_file, never from config files.
| Key | Env | Type | Default | Description |
llm.provider | TELHA_LLM__PROVIDER | enum: anthropic|openai_compat (optional) | none | Provider protocol; unset disables generation. |
llm.endpoint | TELHA_LLM__ENDPOINT | string | "" | Provider endpoint base. Must be https unless the host is loopback (localhost, 127.0.0.1/other loopback IPv4, or [::1]): plaintext http to a remote host would put the Authorization header on the wire. |
llm.api_key_env | TELHA_LLM__API_KEY_ENV | string | TELHA_LLM_API_KEY | Env var NAME holding the API key. |
llm.secrets_file | TELHA_LLM__SECRETS_FILE | string | "" | Alternative to api_key_env: a file whose entire trimmed content is the key. Refused on unix unless permissions are 0600 or tighter; on Windows this is a logged warning only. |
llm.model_default | TELHA_LLM__MODEL_DEFAULT | string | "" | Default generation model. Required when provider is set. |
llm.models_allowed | TOML only | array of string | [] | Model allowlist. Requests naming a model outside models_allowed ∪ {model_default} are rejected with 400. |
llm.max_output_tokens | TELHA_LLM__MAX_OUTPUT_TOKENS | u64 | 4_096 | Per-request output-token ceiling, independent of the tenant budget. |
llm.timeout_s | TELHA_LLM__TIMEOUT_S | u64 | 120 | Provider request timeout, seconds. |
llm.tenant_caps.daily_tokens | TELHA_LLM__TENANT_CAPS__DAILY_TOKENS | u64 | 0 (unlimited) | Default per-tenant input+output token cap per daily window. |
llm.tenant_caps.monthly_tokens | TELHA_LLM__TENANT_CAPS__MONTHLY_TOKENS | u64 | 0 (unlimited) | Default per-tenant input+output token cap per monthly window. |
[verify]
Claim-verification settings.
| Key | Env | Type | Default | Description |
verify.beta | TELHA_VERIFY__BETA | f64, (0, 1) | 0.65 | Geometric fusion balance β for claim↔span matching: match = clamp01(cos)^β · lex^(1−β). 0/1 are formula fast-paths, not valid config. |
verify.bands.supported | TELHA_VERIFY__BANDS__SUPPORTED | f64 | 0.75 | Claim score ≥ this → verdict supported. Overrides are bounded to ±0.1 of the default. |
verify.bands.partial | TELHA_VERIFY__BANDS__PARTIAL | f64 | 0.50 | Claim score in [partial, supported) → partial; below → unsupported. Overrides bounded to ±0.1 of the default; supported must stay ≥ partial + 0.10 (no band collapse). |
verify.candidates_m | TELHA_VERIFY__CANDIDATES_M | usize | 8 | Top-m plan spans by embedding similarity added to each claim's candidate set (cited markers are always candidates). |
verify.policy | TELHA_VERIFY__POLICY | enum: flag|redact | flag | Unsupported-claim policy: flag annotates and delivers the draft unchanged; redact replaces unsupported claims with an omission notice. |
verify.decompose_model | TELHA_VERIFY__DECOMPOSE_MODEL | string | "" | Model used for claim decomposition; empty uses the generation model. |
verify.embed_model | TELHA_VERIFY__EMBED_MODEL | string | "" | Embedding model for claim↔span similarity; empty uses the request's semantic model, else embedding.model. Claims and spans embed under one model only. |
[compare]
| Key | Env | Type | Default | Description |
compare.max_scan_rows | TELHA_COMPARE__MAX_SCAN_ROWS | u64 | 2_000_000 | Hard version-row scan ceiling per /v1/compare request; beyond it the page returns partial-with-warning and a resume cursor. |
[clarify]
Ships disabled until a bot surface is configured.
| Key | Env | Type | Default | Description |
clarify.enabled | TELHA_CLARIFY__ENABLED | bool | false | Master switch: gates CLARIFICATION creation, clarify:temporal job enqueue, and the capability flag advertised to workers. |
clarify.escalation_window_hours | TELHA_CLARIFY__ESCALATION_WINDOW_HOURS | u64 | 48 | Hours before an undelivered/unanswered candidate escalates to the next ranked person; also the clarify:temporal backoff base. |
clarify.max_candidates | TELHA_CLARIFY__MAX_CANDIDATES | usize | 3 | Ranking length ceiling. Steward escalation is always attempt ranking+1 and does not count against this. |
clarify.per_person_daily_cap | TELHA_CLARIFY__PER_PERSON_DAILY_CAP | u32 | 5 | Per-person daily question cap, enforced by the delivery surface. |
clarify.digest_threshold | TELHA_CLARIFY__DIGEST_THRESHOLD | u32 | 3 | Questions targeting one person inside the digest window before delivery batches into a digest. |
clarify.digest_window_minutes | TELHA_CLARIFY__DIGEST_WINDOW_MINUTES | u64 | 30 | Digest batching window, minutes. |
clarify.redact_fallback | TELHA_CLARIFY__REDACT_FALLBACK | bool | true | When no candidate passes the read gate, send the steward a redacted question (document link, no span text). |
clarify.channels | TELHA_CLARIFY__CHANNELS | string | teams,slack | Comma-separated delivery surfaces, consumed by the clarify-bot. |
clarify.quorum.enabled | TELHA_CLARIFY__QUORUM__ENABLED | bool | true | Whether critical severity requires multi-confirmation at all. |
clarify.quorum.critical_required_confirmations | TELHA_CLARIFY__QUORUM__CRITICAL_REQUIRED_CONFIRMATIONS | u32 | 2 | Matching binding-capable confirmations required to bind a critical clarification. |
clarify.quorum.required_roles | TOML only | array of string | ["editor"] | Minimum rights whose responses count toward quorum. Each must be viewer|editor|steward. |
clarify.quorum.allow_steward_override | TELHA_CLARIFY__QUORUM__ALLOW_STEWARD_OVERRIDE | bool | true | Whether a steward may bind single-handedly via an explicit override. |
clarify.severity.high_notifies_steward | TELHA_CLARIFY__SEVERITY__HIGH_NOTIFIES_STEWARD | bool | true | Notify the steward when a high-severity question is asked. |
clarify.severity.elevate.<KIND> | TOML only | string (severity) | empty map | Kind → minimum severity elevation, e.g. elevate.conflicting_dates = "high". Values must be low|normal|high|critical. |
[backup]
| Key | Env | Type | Default | Description |
backup.enabled | TELHA_BACKUP__ENABLED | bool | false | Run periodic backups from the serving process. Requires backup.dir to be set. |
backup.dir | TELHA_BACKUP__DIR | path (optional) | none | Backup target directory; stamp dirs are created inside it. |
backup.interval_hours | TELHA_BACKUP__INTERVAL_HOURS | u64 | 24 | Hours between ticker runs. |
backup.keep | TELHA_BACKUP__KEEP | usize | 7 | Stamps retained; older ones prune after a verified create. 0 disables pruning. |
backup.pg_database_url_env | TELHA_BACKUP__PG_DATABASE_URL_ENV | string | DATABASE_URL | Env var NAMING the app-layer PostgreSQL URL for the two-surface capture. Unset env means core-only backups, marked partial. |
[retention]
| Key | Env | Type | Default | Description |
retention.enabled | TELHA_RETENTION__ENABLED | bool | false | Run the periodic retention pass from the serving process. Requires retention.archive_dir or backup.dir to be set. |
retention.interval_hours | TELHA_RETENTION__INTERVAL_HOURS | u64 | 24 | Hours between system:retention runs. |
retention.orphan_ttl_days | TELHA_RETENTION__ORPHAN_TTL_DAYS | u64 | 14 | Age (days) past which dead provisional streamed source versions (canonical hash None) are swept, with no policy opt-in required. |
retention.archive_dir | TELHA_RETENTION__ARCHIVE_DIR | path (optional) | none | Archive slice target directory. Defaults under {backup.dir}/archives when unset. |
retention.max_entities_per_run | TELHA_RETENTION__MAX_ENTITIES_PER_RUN | usize | 10_000 | Per-run entity ceiling; bounded runs resume next interval, and the remaining backlog is reported. |
[watch]
The CLI/REST/MCP watch surfaces work regardless of enabled; the flag gates only the in-process evaluator ticker.
| Key | Env | Type | Default | Description |
watch.enabled | TELHA_WATCH__ENABLED | bool | false | Run the periodic evaluator sweep from the serving process. |
watch.eval_interval_secs | TELHA_WATCH__EVAL_INTERVAL_SECS | u64 | 60 | Seconds between evaluator ticks. |
watch.max_watches_per_tenant | TELHA_WATCH__MAX_WATCHES_PER_TENANT | usize | 1000 | Per-tenant watch ceiling; create returns WATCH_LIMIT at the cap. |
watch.max_rows_per_tick | TELHA_WATCH__MAX_ROWS_PER_TICK | u64 | 50_000 | Bounded sweep: tx-index rows scanned per tenant per tick. The backlog resumes next tick. |
watch.debounce_default_secs | TELHA_WATCH__DEBOUNCE_DEFAULT_SECS | u64 | 3600 | Default coalescing window (seconds) when a watch omits one. |
watch.debounce_floor_secs | TELHA_WATCH__DEBOUNCE_FLOOR_SECS | u64 | 60 | Floor on any watch's coalescing window. |
watch.max_events_per_fire | TELHA_WATCH__MAX_EVENTS_PER_FIRE | usize | 100 | Cap on events carried in a single fire; excess sets truncated. |
watch.query_watch_max_results | TELHA_WATCH__QUERY_WATCH_MAX_RESULTS | usize | 1000 | Row ceiling for a query watch's result fingerprint. |
watch.suspend_after_gate_denials | TELHA_WATCH__SUSPEND_AFTER_GATE_DENIALS | u32 | 5 | Consecutive fire-time gate denials before a watch auto-suspends. |
watch.webhook_timeout_secs | TELHA_WATCH__WEBHOOK_TIMEOUT_SECS | u64 | 10 | Webhook delivery timeout, seconds. |
watch.webhook_max_attempts | TELHA_WATCH__WEBHOOK_MAX_ATTEMPTS | u32 | 3 | Webhook delivery attempts before dead-letter and watch suspend. |
watch.fire_keep_per_watch | TELHA_WATCH__FIRE_KEEP_PER_WATCH | usize | 1000 | Durable WatchFire rows kept per watch (pruned oldest-first); the audit sink stays the unbounded record. |
[erasure]
Deliberately minimal: masking semantics, the blocklist check, verification-before-close, ledger emission, dual approval, replay-before-serve, archive supersession, and the strong-identifier zero gate are doctrine, not knobs. None of these keys are on the environment allowlist; set them via TOML.
| Key | Env | Type | Default | Description |
erasure.backup_horizon_days | TOML only | u64 | 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 | TOML only | u64 | 30 | Operational log retention bound attested in the completion report. |
erasure.log_targets | TOML only | array of string | 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 retention bound. |
erasure.per_subject_log_scrub_supported | TOML only | bool | false | Whether this deployment supports per-subject log scrubbing (v1 core: no). Attested in the completion report. |
erasure.expect_pg_stage | TOML only | bool | false | Whether an app-layer PostgreSQL stage is expected to report before verification. Deployments without the app layer record the stage not_applicable with a loud warning. |
- CLI reference -
telha check-config prints the fully resolved TOML; RunArgs flags override the highest layer. - Glossary - definitions for terms like decay, fusion, KEK/DEK, and quorum used in the descriptions above.
- Operators › Configuration - the narrative guide to configuring a deployment section by section.
- Deployment runbook -
grpc.token_key generation, rotation, and the key inventory.