Environment Variables
The canonical config surface is the declarative config graph. Environment variables are process wiring, tuning escape hatches, and a small set of policy toggles being migrated into typed config.
The declarative config graph is selected by $CACOPHONY_DIR/config.yaml (default ~/.cacophony/config.yaml). This fleet's home wrapper imports daemon/checkouts/cacophony-config/config.yaml directly; the Cacophony source checkout's compatibility forwarder is not the runtime entrypoint.
This is a summary. The full, drift-checked registry of every CACO_* variable a release binary reads lives in docs/env-vars.md.
Classes
| Class | Meaning |
|---|---|
| policy | Routing/behavioral policy that should be typed config. Env is a migration override. |
| tuning | Operator escape hatch (timeout, cap, threshold, feature toggle). Env is the intended surface; the default is safe. |
| internal | Process wiring, identity, build-time, or test/bench harness. Not an operator knob. |
Policy / routing knobs
These routing/policy-critical knobs historically lived only as env strings (typically in services.caco-daemon-extra-env) and are being promoted to typed config. Once promoted the usual precedence is env > config > compiled default: an explicitly-set CACO_* env var still wins over the typed config value. Safety exception: typed speech.tts.fallback_5xx_threshold: 0 is authoritative disabled policy and wins over a stale positive CACO_TTS_FALLBACK_5XX_THRESHOLD; an env zero may still disable an enabled/default policy. Child promotion beads are tracked under umbrella.
Promoted to typed config (env still overrides)
| Variable | Typed config home | Default |
|---|---|---|
CACO_REINTEGRATION_FETCH_TIMEOUT_SECS | reintegration.fetch_timeout_secs | 180s |
CACO_STATE_BRANCH_FETCH_TIMEOUT_SECS | reintegration.state_branch_fetch_timeout_secs | 1800s |
CACO_INTEGRATION_CLONE_TIMEOUT_SECS | reintegration.integration_clone_timeout_secs | 300s |
CACO_REINTEGRATION_LOCAL_OP_TIMEOUT_SECS | reintegration.local_op_timeout_secs | 300s |
CACO_PR_BACKEND_CI_AUTO_MERGE | already config-driven: projects.<name>.integration.backend: pull_request + mode pr_auto_merge (no separate field) | off |
CACO_MSG_BROADCAST_LIVE_INJECT_MAX_AGE_SECS | messaging.broadcast.live_inject_max_age_secs | 600s |
CACO_TTS_FALLBACK_MODEL | speech.tts.fallback_model | gpt-4o-mini-tts |
CACO_TTS_FALLBACK_5XX_THRESHOLD | speech.tts.fallback_5xx_threshold (typed 0 disables fail-closed even against stale positive env) | 3 |
CACO_TTS_FALLBACK_RESTORE_SECS | speech.tts.fallback_restore_secs | 600s |
Still env-only (promotion pending)
All class-a routing/policy knobs tracked here have now been promoted to typed config. The typed land-queue and merge-queue enablement keys left with the queues, so no queue environment is derived from configuration. CACO_PR_BACKEND_CI_AUTO_MERGE was already config-driven (set from the resolved PR-backend mode AutoMerge). New env-only knobs land under umbrella.
Env vs typed config
For process-scoped daemon environment that is not a Cacophony policy knob, prefer the config-level services.caco-daemon-extra-env map (portable, applied before the tokio runtime starts) over host-manager extraEnv. Explicit config values override inherited env for processes spawned afterward.
The non-secret CACOPHONY_SET_MODEL_ACTIVE and CACOPHONY_SET_MODEL_IDLE values provide exact provider/model targets for the pi-model-selection profile. They are rendered into the profile once; missing or malformed values become visible inert sentinels rather than fallback model choices.
Warm reintegration cache controls
The daemon's stable integration worktrees are bounded by CACO_REINT_GATE_WORKTREE_MAX_TOTAL_GB (default 50 GiB): the hourly lock-safe sweep counts each integration checkout plus any leftover gate target and removes oldest idle pairs until under the cap. Set the cap to 0 to disable size-based reaping while retaining the 48-hour age sweep.
Beads store maintenance
The daemon's WAL-safe, load-gated loose-object repack loop is enabled by default because beads stores deliberately keep git auto-gc disabled. CACO_BEADS_GC_ENABLED=0 (or false/off) is the explicit rollback; leaving the variable unset keeps maintenance active on a newly elected primary.
CACO_BEAD_TRANSFER_OWNERSHIP_REQUEST_TIMEOUT_SECS bounds bead-ownership transfer requests (default 120 seconds, minimum 5 seconds) so a long cross-node transfer can return typed indeterminate evidence before generic middleware cancels it.
CACO_BEADS_SQLITE_QUERY_TIMEOUT_SECS bounds each beads-store SQLite query (default 30 seconds) so one stuck query cannot wedge the authority, and CACO_BEADS_STORE_BLOCKING_CONCURRENCY caps concurrent blocking store work on the daemon (default 16).
Safety-store backpressure budget
CACO_SAFETY_STORE_LOCK_TIMEOUT_MS bounds shared daemon-store lock acquisition for current-choice reads/acceptance plus optional compact agent-summary collectors (default 1000ms, minimum 100ms). Busy safety state fails closed with structured retry metadata instead of looking clear; the compact summary preserves independently available base counts and marks only the affected collector degraded.
Secret-safe agent env diagnostics
A managed agent's materialized env.sh is a runtime input, not a diagnostic surface: it carries credential-bearing variables next to ordinary model, provider, and routing settings, so any command that prints it prints both. Use caco agent introspect --show env-keys (keys, provenance, and a coarse length band, with no values at all) or caco agent introspect --show env (credential-shaped values withheld, routing values intact) instead of reading the file. --reveal-secrets is refused inside a managed agent runtime because managed stdout becomes a durable session transcript; CACO_ALLOW_MANAGED_ENV_REVEAL=1 is the operator-owned override for one deliberate, non-transcribed recovery session.
GitHub identity routing
Managed sessions use real gh, not a PATH shim.
First-party PR, lifecycle, recovery and Cara commands resolve the typed project identity and host explicitly; caco pr gh-env --project P prints the non-secret ad-hoc route.
CACO_GH_AUTH_HOSTNAME, CACO_GH_AUTH_USER and the legacy CACO_GH_AUTH_ROUTES projection remain compatibility hints, not first-party credential selection.
See explicit project routing.
CACO_GH_TOKEN_LOOKUP_TIMEOUT_SECS bounds first-party typed-project account lookup: default five seconds, integer 1–60, with one second of hard-stop grace.
Invalid values, missing coreutils timeout, failed or empty lookup never start the requested command or fall back to an active account.
Bare gh owns its normal option/help behavior.
Existing installed shims require compatible deployment and normal owner-controlled materialization or environment refresh; no credential/account migration is implied.
STT metadata log bounds
CACO_STT_LOG_MAX_BYTES sets the best-effort metadata-log rotation threshold before each append: unsigned bytes, default 5 MiB (5242880) when unset or unparsable; parsed values clamp to 4096–104857600.
CACO_STT_LOG_KEEP retains three rotated metadata archives by default when unset or unparsable; parsed values clamp to 1–32.
Zero retains one archive rather than disabling rotation or truncating in place.
These prospective rotation bounds do not change capture/transcript retention or grant authority to clean existing live evidence.
CLI diagnostics and process placement
CACO_CLI_TIMINGS=1 enables fixed-label startup-stage elapsed_ms diagnostics on stderr; unset or any other value disables them. It never prints config, arguments, environment values or errors.
CACO_READ_STAGE_TIMINGS=1 enables daemon read-stage breadcrumbs for inbox, agent-status and bead reads; unset or any other value disables them.
Records contain only timestamp, process-local observation counter, fixed operation/stage/reason and elapsed_ms, never caller, headers, project, path, body or error text.
scope_exit means the handler future dropped, not read success or native timeout; this is not a deadline, keepalive or monitoring task.
CACO_EXECUTION_NODE is an internal daemon-launched CLI placement hint selecting the configured node endpoint, not a replacement caller, credential or authority grant. Repair placement checks also use it before falling back to CACO_NODE.
CACO_DAEMON_URL is managed-agent MCP bridge wiring exported from the daemon's recorded local listener. The bridge falls back to loopback port 11100 when unrecorded; nested ordinary CLI/runtime children scrub it with caller identity rather than inherit another caller's endpoint.
CACO_AHP_HOST_JSON is daemon-materialized resolved AgentAhpHostBinding JSON for an AHP launch.
Present invalid JSON yields no binding rather than falling back; when absent, legacy pi-daemon-config JSON's ahp may supply the pi-daemon binding.
Treat launch metadata as private, not a general routing or authority override.
CACO_TEST_WORKLOAD_MARKER is a queue-manager-provided per-job probe-file path.
Resolver-owned shell payloads and generated devshell payloads best-effort create/truncate it at entry; unset/empty or a failed touch does not stop the payload, and explicit nix develop remains opaque.
It records payload entry, not compiler start, success, lock diagnosis or authority to signal/adopt a process; it is not an operator path override.
Cron run identity
CACO_CRON_RUN_ID is internal per-run operation/correlation wiring injected into logged non-hook command crons.
Daemon-scoped restart uses it when present to hand off to a separately owned worker.
The delegated child clears it before re-entry; accepted handoff is not completed restart.
Leave it unset outside daemon-managed cron execution.
Agent recovery bounds
CACO_CHECKPOINT_GIT_TIMEOUT_SECS bounds each local Git step of a dirty-work checkpoint before a terminal transition or off-node recovery: dirty and status checks, the identity probe, staging, commit and the unpublished-commit check.
The default is 120 seconds; only a positive integer overrides it, so 0 or an invalid value keeps the default, and there is no upper clamp.
It is independent of the 5-second CACO_AGENT_CONTROL_HELPER_TIMEOUT_SECS control-probe bound and of the 240-second provider-ready wait.
CACO_RECOVERY_CONCURRENCY caps how many per-agent recovery tasks one reconcile pass runs at once: off-node checkpoints of dead agents and due retry resumes.
The default is 8; parsed values are clamped to 1–64, so 0 means 1 rather than disabling recovery, and a missing or invalid value keeps the default.
Per-agent state transitions are unchanged.
See Herdr restart recovery for the design.
Shared SSH multiplexing
CACO_SSH_SHARED_MUX controls Unix shared SSH control-directory reuse for TUI and raw attach clients.
Sharing is attempted by default; only the literal value 0 disables it, while unset and other values, including empty, still attempt sharing.
Selection is cached on first use in each process.
The shared caco-ssh-mux-<uid> directory uses an absolute existing XDG_RUNTIME_DIR or /tmp.
It requires the caller's UID, no symlink or group/world permissions, valid UTF-8 and a path of at most 60 bytes.
Disabled or unavailable sharing, or non-Unix, uses the historical per-process pool directory; CLI raw attach omits shared mux arguments.
Shared masters outlive one client's shutdown until their idle/keepalive lifetime ends.
This changes reuse, not SSH authentication or remote-operation authority.
TUI launch marker
CACO_TUI_CLI_SHADER_OVERRIDE is a process-local marker emitted for a validated caco tui --shader launch.
The TUI checks readable string presence, including an empty string, to suppress restoring the persisted shader; the marker value does not itself select or validate a look.
When absent, a known persisted look may be restored.
Prefer the CLI flag rather than setting this internal marker.
Isolated fixture controls
The test-only registry includes CACO_CONFIG_WATCH_BENCH_ROOT and CACO_CONFIG_WATCH_BENCH_KIND for the marked temporary config-watch comparison, CACO_GLSLANG and optional CACO_LUMEN_DUMP for the ignored generated-shader compile test, CACO_PI_SELECTION_TEST_BIN for isolated real-Pi/tmux Flow retention acceptance, and CACO_TMUX_FIXTURE_CAPTURE for the native shim helper's JSON environment/argument receipt. These are harness inputs, not operator/runtime controls; the full registry records their prerequisites and unset behavior.
CACO_PI_DAEMON_1_DIR names a built pi-daemon 1.0 checkout for explicitly ignored AHP integration/lifecycle canaries; it must contain dist/cli.js, support sessionEnvFiles, and have node available.
Unset or missing built entrypoint makes the selected canary skip.
These use isolated sessions and local mock-provider endpoints, not a runtime installation/routing or authority control.
CACO_PI_DAEMON_LIVE_AHP_URL and CACO_PI_DAEMON_LIVE_AHP_TOKEN_FILE are required only when the explicitly ignored live_ahp_status_reports_the_host_bd_798ea1 canary is deliberately selected.
The ten-second status probe initializes and disconnects from an existing live AHP host without a session or turn, asserting reachability and protocol metadata.
A missing variable fails the selected test; ordinary tests ignore it.
These endpoint/credential-file references neither provision a host, distribute credentials, select normal runtime routing nor grant authority to execute the probe.
CACO_AGENT_PANE_ARTIFACT_DIR optionally retains named JSON cell snapshots from shared-agent-pane presentation tests using synthetic AHP snapshots and TestBackend renders.
Any present OS-string value is used as an output path; tests create the directory and write or overwrite the named artifacts, with path/write errors failing the fixture.
Unset keeps the render in memory.
It is not a live capture, runtime or cleanup control.
CACO_TEST_CHOICES_ENDPOINT_CHILD prevents recursive child launch in the isolated choices endpoint regression. When unset, the parent starts the exact test in a child with managed caller variables removed and sets the marker to 1; any present value selects the child fixture path. It is not a runtime routing or authority control.
CACO_BRANCH_FIXTURE_CHILD is a test-only re-entry marker for the branch-scoped materialization/reintegration regression.
When unset, the parent launches the exact test with an env-cleared, isolated HOME/Git/runtime and sets the marker to 1; any present OS-string value, including empty, selects the child body.
Let the parent own this marker: setting it ambiently bypasses that isolation bootstrap.
It is not a daemon policy or runtime control.
CACO_TUI_TEST_CAPTURE_DIR optionally retains footer-render fixture JSON from actual Ratatui buffers.
Any present OS-string path, including empty, is used; relative paths resolve from the test working directory.
The fixture writes or overwrites footer-false.json and footer-true.json with dimensions and cell text/colors/modifiers, without creating the directory; path/write errors fail the fixture.
Unset keeps rendering and assertions in memory.
It is not a live capture, runtime or cleanup control.
Applying an env change (restart semantics)
Daemon environment is read at process start and is never injected into an already-running daemon, so any env change needs a restart of the process that reads it. Which restart — and whether a Nix rebuild is required — depends on where the value lives:
services.caco-daemon-extra-env(config map): applied atcaco daemonbringup viastd::env::set_varbefore the tokio runtime starts, so the daemon and the child git/subprocesses it spawns inherit it. A change needs only a daemon restart — no Nix rebuild — so acaco update --restart/caco restartloop applies it on the next tick, including in non-Nix installs (containers, AKS, Codespaces).- Nix Home-Manager
services.cacophony.extraEnv: baked into the generated service unit'sEnvironment=at build time, so a change needs a Nix rebuild (home-manager/nixos-rebuild/darwin-rebuild switch) to regenerate the unit, followed by a service restart to pick up the new unit environment; a daemon-only restart keeps inheriting the old values.
To confirm a restart actually applied a config extra-env change, caco doctor and caco ops check surface a stale-daemon-extra-env finding (the daemon-computed /api/v1/node data.stale_daemon_extra_env) that lists values set in effective config but not yet present in the running daemon process.
Drift check
scripts/check-env-vars-docs.py extracts the canonical read set from crates/ (string-literal env::var reads, env! macros, and env-name constants) and fails if any read variable is missing from docs/env-vars.md.
It runs in the required PR CI source-guard bundle as a pure Python walk with no Cargo build or docs shell, and again in just docs-check in the docs-drift lane, so a new CACO_* read without a registry row fails the PR before it can land.
The registry includes CACO_PR_INIT_TIMEOUT_SECS, the bounded total clone/fetch/checkout budget for recoverable product-PR initialization.
The test-only registry includes CACO_TEST_UPDATE_DECODE_EVIDENCE_PATH, which redirects persisted update-release decode evidence so decode-failure fixtures never write into the ordinary runtime directory.
scripts/check-env-vars-docs.py # report + gate
scripts/check-env-vars-docs.py --list # print the canonical read set
Source of truth
CACO_AGENT_DISPLAY_NAME carries the resolved human name for Pi/session presentation. Materialization writes it separately from stable CACO_AGENT_ID and overrides inherited/project/profile values; it is never a routing, storage or authorization key. See the display-to-runtime-handle mapping.
CACOPHONY_AGENT_NAME is the agent's name as declared in configuration (router of ms-mac-cacophony-router), for space-limited presentation such as editor chips. Materialization exports it from the declaration in env.sh and the Pi-Daemon session environment, and unsets it for agents without one; inherited/project/profile values never override it. Like the display name, it is never a routing, storage or authorization key.
The messaging registry includes CACO_MSG_SPEAK_WAIT_REQUEST_TIMEOUT_SECS, the bounded outer messages/speak and inner --wait terminal-ack budget (default 120 seconds; the inner wait returns an explicit unconfirmed result two seconds before the outer middleware deadline).
CACO_MESSAGE_FANOUT_REQUEST_TIMEOUT_SECS bounds project/global broadcast and node-targeted send fan-out (default 130 seconds, minimum 5 seconds), leaving enough time for bounded per-recipient runtime receipts instead of the generic 30-second timeout.
CACO_MERGE_QUEUE_REQUEST_TIMEOUT_SECS bounds the merge-queue read projection (default 70 seconds, minimum 5 seconds), while CACO_AGENT_ASSIGNMENT_READ_REQUEST_TIMEOUT_SECS bounds authoritative per-agent assignment reads (default 12 seconds, minimum 6 seconds). Invalid or zero values retain their compiled defaults so these correctness reads finish inside their client budgets.
The full registry with per-subsystem tables (reintegration, queued governor, beads, agent lifecycle, daemon watchdog, TTS/STT, controller, messaging, web, TUI, MCP, microVM, codespaces/AKS, release, gh/git, disk/prune, Pi runtime, cert/PKI, dynamic-prefix families, build-time, and test/bench) is docs/env-vars.md. Behavior remains defined by the code that reads each variable and the typed Config Reference.