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

ClassMeaning
policyRouting/behavioral policy that should be typed config. Env is a migration override.
tuningOperator escape hatch (timeout, cap, threshold, feature toggle). Env is the intended surface; the default is safe.
internalProcess 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)

VariableTyped config homeDefault
CACO_REINTEGRATION_FETCH_TIMEOUT_SECSreintegration.fetch_timeout_secs180s
CACO_STATE_BRANCH_FETCH_TIMEOUT_SECSreintegration.state_branch_fetch_timeout_secs1800s
CACO_INTEGRATION_CLONE_TIMEOUT_SECSreintegration.integration_clone_timeout_secs300s
CACO_REINTEGRATION_LOCAL_OP_TIMEOUT_SECSreintegration.local_op_timeout_secs300s
CACO_PR_BACKEND_CI_AUTO_MERGEalready config-driven: projects.<name>.integration.backend: pull_request + mode pr_auto_merge (no separate field)off
CACO_MSG_BROADCAST_LIVE_INJECT_MAX_AGE_SECSmessaging.broadcast.live_inject_max_age_secs600s
CACO_TTS_FALLBACK_MODELspeech.tts.fallback_modelgpt-4o-mini-tts
CACO_TTS_FALLBACK_5XX_THRESHOLDspeech.tts.fallback_5xx_threshold (typed 0 disables fail-closed even against stale positive env)3
CACO_TTS_FALLBACK_RESTORE_SECSspeech.tts.fallback_restore_secs600s

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:

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.