Configuration Reference

Cacophony uses a YAML configuration file per node, stored at $CACOPHONY_DIR/config.yaml. The same configuration on every node is sufficient for cluster operation.

Splitting a config across files

A config can import other YAML files, so larger setups usually split it up: config.yaml as the entrypoint, projects.yaml for the projects: map, shared project defaults in project.yaml, role-scoped agent snippets under agents/ (for example pi-common.yaml for Pi runtime mixins, persistent-observer.yaml for observers and controllers), tui.yaml for named themes, and shared template values in values.yaml via values.imports.

This fleet's configuration lives in the separate cacophony-config repository. The operator's $CACOPHONY_DIR/config.yaml (default ~/.cacophony/config.yaml) imports daemon/checkouts/cacophony-config/config.yaml directly. The in-repo .cacophony/config.yaml is a temporary compatibility forwarder: retain it until runtime roots and bootstrap consumers have cut over. Profiles, flows, Pi overlays, and the self-contained .cacophony/config.ci.yaml policy fixture remain here, and the caco binary embeds the profiles, the Cacophony project flows and the Pi overlays, so agents need no Cacophony canonical checkout for them.

Fleet source changes ship through a Git PR in cacophony-config. The daemon's config refresher fetches the configured branch about once a minute, exports the fetched commit to a temporary tree, and runs the activation preflight, config load and strict validation on it; a valid commit is checked out under the canonical writer fence and hot reload applies it, while an invalid or deferred commit leaves the checkout on its last good commit. caco checkout refresh --project cacophony-config runs the refresher on demand, and the private config generations it replaced are retired. Product schema/runtime/profile changes need a linked product PR. Use separately owned source worktrees, never edit the home wrapper or daemon-owned checkout to simulate rollout, and distinguish source merge, refreshed config checkout, compatibility deferral/activation, running daemon config, and loaded agent resources.

Only when an explicitly authorized bootstrap change needs distribution, distribute the home import wrapper, not a materialized config or the config repository's root file with checkout-relative imports. Preview with caco config distribute --json before applying with --yes.

Config File Selection

--config Flag

Every caco executable accepts an explicit config path, for example CONFIG="/path/to/config.yaml"; caco --config "$CONFIG" status. This flag is parsed before subcommand dispatch, so it works uniformly across caco, caco_daemon, caco_bd, and all alias binaries.

Resolution Order

  1. CONFIG="/path/to/config.yaml"; caco --config "$CONFIG" ... (if provided, authoritative for entire invocation)
  2. $CACOPHONY_DIR/config.yaml
  3. $CACOPHONY_DIR/config.yml
  4. Error (no silent synthesis)
Child Process Propagation Commands that spawn or supervise other caco processes must pass the resolved config path explicitly. Child processes must not fall back to ambient environment discovery.

Recursive Schema Browser

The complete field reference is generated from the current runtime schema and split into lightweight Pages under Recursive Configuration Schema. It lists every top-level section and recursive field path exposed by caco config schema --json, including deeply nested maps such as tui.graphics, tui.themes.<name>.graphics, project remotes/integration, GitHub SSH routing policy, dynamic-node relay settings, speech routing, release channels, image generation, and persistent-agent declarations.

Use both views: the generated schema pages are exhaustive field-by-field reference; the examples and notes below explain common composition patterns, validation, and operational conventions.

Open the generated schema index for the current per-section pages and row counts; they are regenerated with just docs-build and are not duplicated here.

Example Config Skeleton

version: 1

node:
  default: localhost                    # Semantic node name (not transport)

nodes:
  - name: localhost
    host: localhost                     # Network address for this node
    ssh:
      username: operator                   # Also accepts "user" as alias
      port: 22
      key:
        path: $HOME/.ssh/id_ed25519
    tls:
      cert: $HOME/.cacophony/pki/nodes/localhost.crt
      key: $HOME/.cacophony/pki/nodes/localhost.key

identities:
  - name: personal
    user:
      name: Example User
      email: user@example.com
    auth:
      key:
        path: $HOME/.ssh/id_ed25519

projects:
  - name: cacophony
    remote: ssh://git@github.com/someorg/cacophony.git
    default_branch: main
    identity: personal                  # References identities[].name
    github_ssh_routing: auto            # auto | ssh_over_443 | default_port
    beads:
      enabled: true
      branch: beads                     # Git branch for bead journal
    worktree:
      base_dir: $HOME/.cacophony/agents/cacophony
    agent_defaults:
      type: codex
      profile: agent
      resources:
        vcpu: 1.0
        memory: 8GiB
        disk: 40GiB              # Isolation backend writable work disk/overlay ceiling
      isolation:                 # Optional; host is the default backend
        backend: host            # host | cloud-hypervisor | firecracker | kata | oci
        required: false
        unavailable: fallback-host   # fail | fallback-host
        mounts:
          checkout: host-rw      # host-rw | guest-private | none
          cache: ro              # none | ro | cow
          artifacts: host-dir    # host-dir | git-bundle | tarball
        network:
          mode: daemon-proxy     # daemon-proxy | nat-allowlist | none
    test:
      command: cargo test
      maxParallel: 1
      maxRuntimeSecs: 1200
    # Fork-and-PR topology (bd-a00003). Optional. When present, the
    # daemon configures these named remotes inside the canonical
    # checkout in addition to `origin = remote` above, and refuses
    # to place a `push: forbidden` URL at `origin`.
    remotes:
      fork:
        url: git@github.com:example-user/project.git
        push: allowed
        default_branch: user/main
      upstream:
        url: git@github.com:example-org/project.git
        push: forbidden
        default_branch: main
        pr_target: true              # PRs land here
    integration:
      reintegrate_target: fork/user/main
      pr_base: upstream/main
      # Short-term fallback while PR auto-merge is unproven:
      # use direct + local_merge, then restore review + pull_request.
      default_intent: direct        # direct | review | artifact_only | none
      backend: local_merge          # local_merge (default) | pull_request

services:
  caco-daemon:
    - node: localhost
      port: 11100                       # Local HTTP listener
      cluster_port: 12100              # Cluster HTTPS listener
      sidecar: 11200                   # Sidecar HTTP
      sidecar_cluster_port: 12200      # Sidecar HTTPS

agents:
  transient_compute:                    # Optional provider-neutral short-lived agent scheduler
    default: fake                       # Used by --compute default
    max_parallel: 4                     # Global active transient-job ceiling
    max_runtime_secs: 3600              # Hard runtime ceiling
    launch_spec_ttl_secs: 600           # Launch-spec validity, capped by runtime
    max_retries: 1                      # Global retry-attempt ceiling
    reaper_grace_secs: 30               # Grace before terminal-resource reaping
    budget_cents_per_day: 2000          # Admission budget, not a billing system
    providers:
      fake:                             # Deterministic daemon/CLI test provider
        type: fake
        enabled: true
        dynamic_node_template: transient-worker
        max_parallel: 2
        max_runtime_secs: 600
        launch_spec_ttl_secs: 120
        max_retries: 1
        sku: test-small
        allowed_skus: [test-small, test-medium]
        workload_profile: burst
        allowed_workload_profiles: [burst]
        cost_per_minute_cents: 0
        artifact_uri: fake://artifacts

providers:
  openai:
    base_url: https://api.openai.com/v1
    api_key_file: /run/secrets/openai-api-key
    models:
      - gpt-5.6-sol
      - gpt-5.4-mini
  anthropic:
    base_url: https://api.anthropic.com
    auth_token_file: /run/secrets/anthropic-auth-token
    models:
      - claude-sonnet-4-6
      - claude-haiku-4-6

agents.transient_compute configures the provider-neutral scheduler used by caco agent new --compute PROVIDER and caco bd dispatch --compute PROVIDER. --compute local preserves the ordinary local tmux-backed path, --compute default resolves the configured default provider, and the built-in fake provider exercises launch-spec hashing plus admission/budget logic without creating cloud resources. Runtime ceilings, launch-spec TTLs, retry ceilings, SKU/workload-profile allow-lists, reaper grace, and orphan quarantine are enforced before provider submission or cleanup. Real dynamic-node providers plug in behind the same schema.

Model availability is configured canonically under providers.*.models and augmented by model_discovery. Legacy per-agent catalogs such as agents.claude.models and agents.codex.models are removed.

speech:
  tts:
    model: gpt-4o-mini-tts           # also: gemini-3.1-flash-tts-preview, azure/speech/azure-tts
    # command_template: some_tool --prompt "$BODY" --out "$OUTPUT_FILE"
    voice: sage
    lang:                                  # Ordered TTS language preferences; first non-blank = default
      - en-US                             # default synthesis language
      - en-GB                             # alternates/fallbacks surfaces may offer
      - ja-JP
    # lang omitted or empty resolves to en-US; resolved default persists in TTS daemon state
    voice_filter:                         # Sox-based post-TTS voice filter
      preset: telephone                   # Named preset (see table below)
      rotation:                           # Or rotate filters per agent
        presets:
          - telephone
          - hall
          - lofi
          - radio
          - cave
        combine_with_voice: true          # Combine with voice rotation
    profiles:                              # Runtime-selectable named TTS bundles
      mission-control:
        model: azure/speech/azure-tts
        voice: Mai-Voice-Finetuned-1
        voice_filter:
          preset: radio
      local-fixture:
        model: fixture-tts
        command_template: 'printf "%s" "$BODY" > "$OUTPUT_FILE"'
    personas:                              # Per-event speak categories
      alert:
        profile: mission-control
        voice: nova
        voice_filter:
          preset: radio
        director_note: crisp urgent status
    hrtf:                                  # Disabled/no-op unless fully configured
      enabled: false
      dataset_path: builtin-test
      dataset_license: test fixture only
      backend: identity_stereo
  stt:
    model: gpt-4o-mini-transcribe
    # command_template: stt_tool "$AUDIO_FILE" > "$TRANSCRIPT_FILE"
    vad:                                  # top-level VAD config (bd-a00004)
      enabled: true                       # enable VAD pre-processing for any STT backend
      engine: silero                      # silero, webrtc, etc.
      threshold: 0.5                      # speech probability threshold (0.0–1.0)
      min_speech_duration_ms: 250         # discard segments shorter than this
      min_silence_duration_ms: 300        # silence before segment ends
      speech_pad_ms: 100                  # padding before/after segments
      model: silero-v6.2.0               # VAD model name
    scribble:                             # local Whisper STT via scribble
      model: large-v3-turbo-q8_0          # whisper model name
      acceleration: default               # default | metal | vulkan | cuda | coreml | cpu
      language: en
      vad:
        enabled: true                     # scribble-specific VAD override (inherits stt.vad if default)
        model: silero-v6.2.0              # Silero VAD model name
    agent:                                # Per-target STT overrides
      rewriting: false
      smart_append: false
      enter_after_completed: false
    scratchpad:
      rewriting: true
      smart_append: true
    terminal:
      rewriting: false
    text_field:
      rewriting: true
    bead:
      rewriting: true
  io:
    output:
      mode: local-default               # local-default | local-device | pulse-default
      device: null
    input:
      mode: local-default
      device: null
    inputs:                              # Named input sources (bd-a00005)
      primary_mic:
        type: alsa
        device: "hw:0,0"
      remote_pulse:
        type: pulse
        server: "192.0.2.10:4713"
        device: default
      remote_ssh:
        type: ssh_pulse
        node: example-audio-node
        device: default

tui:
  reload_config: live                  # live | on_tui_restart
  graphics:
    batchUploads: true                   # Batch kitty APC writes; false restores legacy per-surface flushing
    detailedPerfTelemetry: true          # Richer cache/traffic telemetry; false restores coarse perf accounting
    imageSourceCache: true               # Cache decoded/scaled background images; false restores legacy decode/resize path
    surfaceUploadDedupe: true            # Skip unchanged kitty surface reuploads; false restores pointer-only invalidation
    activeAnimationOnly: true            # Skip idle animation wakeups unless animated kitty surfaces are active
    disableOnResize: true                # Suppress kitty graphics while terminal/frame geometry stabilizes
    resizeDebounceMs: 1000               # Debounce before re-enabling graphics after resize
    renderScale: 0.5                     # raster scale multiplier; 2 = HiDPI, 1 = normal, 0.5 = half-scale
    scaledBackgroundCache: true          # bucket procedural background cache sizes
    scaledBackgroundCacheMaxPx: 1280     # longest cached procedural-background edge
    gpuRendering: false                  # headless wgpu compositing for eligible surfaces (default: false)
    gpuBackend: default                  # default | cpu | vulkan | metal | dx12
    sparkline:                           # Kitty graphics sparkline config
      style: gradient_fill_line          # bar | line | gradient_fill_line
      opacity: 0.8
      fadeOpacity: 0.0
      levels: 10
    button:                              # Button chrome style layers
      # ... GraphicsStyleLayers fields
    buttonPadding: 1                     # Uniform, or { top, left, bottom, right }
    spanGlowBleedX: 2                    # Horizontal glow bleed columns
    spanGlowBleedY: 1                    # Vertical glow bleed rows

profiles:
  - name: agent
    hooks:
      on_start:
        - type: command
          command: caco agent status set "starting"
          timeout: 10
      on_complete: []
    reintegration:
      mode: direct

Required Fields

This short table highlights the minimum shape for most clusters. For all nested required fields, use the generated recursive schema browser.

FieldDescription
versionConfig schema version (currently 1)
nodesAt least one node entry
projectsAt least one project entry (for nodes running agents)
services.caco-daemonDaemon listener configuration

Common Optional Fields

The table below calls out high-value operational fields. It is intentionally not the exhaustive list; the generated recursive schema browser is the comprehensive reference for every optional nested field.

FieldDescription
node.defaultDefault node name for resolution
profilesAgent profile definitions
providersProvider base URLs and secret file paths
speechTTS/STT model and audio I/O defaults
macos.colorsNative macOS app color defaults, including foreground/background and palette entries. The app reads these from effective daemon config; user ColorPicker choices remain local app overrides until Settings resets to configured defaults.
webhooks / projects[].webhooksToken-authenticated HTTP ingress served at /hooks/<scope>/<id> on a dedicated listener: bead/bash/action handlers, per-hook or section token/token_env/token_file, bind_host/bind_port, and opt-in funnel. Distinct from project lifecycle hooks; validation requires a resolvable token for public ingress.
services.caco-daemon-extra-envMap of KEY: VALUE applied to the daemon process environment before the Tokio runtime starts, so spawned git/subprocess children inherit it (e.g. GIT_LFS_SKIP_SMUDGE, PULSE_SERVER). Portable non-Nix sibling of the Home Manager extraEnv; config-only, no rebuild.
Per-project agent_defaultsDefault agent type, profile, merge policy, resources, and optional isolation backend request
projects[].friend_projects and scoped friend_projectsDeclare related configured projects for agents. Project-level entries inherit into agents, while agent_defaults, config-profile, and persistent-declaration entries can narrow or disable them. Each entry names the target project, branch or pinned revision, permission strings such as checkout_read / checkout_write / beads_read / beads_create, and optional checkout materialization. checkout_write requires a materialized branch checkout, enables first-party friend-project reintegration/PR flows, and still does not grant blanket all-project authority. When materialize is true, setup creates or backfills an agent-owned friend-checkouts/<project>/ clone; friend bead permissions become named-project bead-only token authority, pending/ready/failed state is surfaced in refresh/status/log evidence, and stale extras are preserved until explicit cleanup.
projects[].remotesOptional named git remotes for fork-and-PR workflows. Each remote declares url, push (allowed or forbidden) and optional pr_target: true.
projects[].integrationOptional project reintegration policy: reintegrate_target, pr_base, default_intent (direct, review, artifact_only, none), and backend (local_merge or pull_request). For -style PR rollout fallback, keep the fork/upstream refs declared but set default_intent: direct and backend: local_merge until PR auto-merge / agent-merge is proven reliable.
projects[].github_ssh_routingOptional daemon-managed GitHub SSH routing policy for implicit GitHub SSH remotes with configured identities: auto, ssh_over_443, or default_port. Node-level policy wins when present.
Per-project hooksProject-level lifecycle hooks (checkout_updated, tag_pushed)
Per-node sshSSH overrides for cert distribution and remote ops
nodes[].github_ssh_routingNode-local override for daemon-managed GitHub SSH routing, useful when one host should keep the configured default endpoint while others use SSH-over-443.
sync.stateCluster state replication triggers. push.* controls proactive peer broadcasts; pull.on_connect.enabled controls startup/rejoin catch-up; pull.periodic.enabled is the only switch that starts the periodic pull timer.
shell.shell_rcGlobal inline shell init for agent sessions (layered with project and profile shell_rc)
tui.reload_configConfig reload mode: live (default, hot-reload on change) or on_tui_restart (defer until restart)
tui.themes / tui.theme_nameNamed TUI theme registry and selected default theme. Theme entries can import fragments and define explicit colors.foreground_color, background_color, and color0–color15 palette slots plus optional graphics overrides.
nodes[].tui.theme_namePer-node TUI theme override layered on top of the global TUI config, useful for assigning distinct registered enterprise variants to static nodes.
speech.io.inputsNamed audio input sources for STT routing (map of name → alsa, pulse, or ssh_pulse source)
speech.tts.langOrdered list of preferred TTS language codes. The first non-blank entry is the default synthesis language; later entries are alternates/fallbacks surfaces may offer. An omitted or all-blank list resolves to en-US. The resolved default is persisted in TTS daemon runtime state so it survives daemon restarts for session resumption alongside the active voice and speed. Node-level speech overrides win over the global value on merge.
speech.tts.fallback_5xx_thresholdProvider-5xx threshold for implicit substitute-model fallback. 0 is authoritative disabled policy: it wins fail-closed over missing or stale positive process environment, preserves the configured primary model/voice, and uses the bounded TTS-down notification instead. Positive values explicitly opt into fallback; inspect effective model/provider/threshold/source and process generation with caco tts status --json.
speech.tts.voice_filterSox-based post-TTS voice filter: preset, custom sox chain, or rotation pool
speech.tts.nMaximum simultaneous agent-speech playback slots for the TTS daemon. Default/effective 1; values <=1 keep sequential FIFO playback, values >1 allow that many concurrent slots.
speech.tts.overlap_secsOptional cap on intentional overlap between concurrent TTS daemon clips. Omitted allows full-slot overlap, 0 disables intentional overlap, and positive values such as 7 delay new playback until active clips are near their expected end.
speech.tts.profilesNamed runtime-selectable TTS profile/effect bundles. Each profile uses the ordinary speech.tts shape; command-template profiles advertise profile.model or the map key in audio capabilities and are selectable with caco tts set-profile --profile <name>.
speech.tts.personasNamed speak-event categories used by caco msg speak --persona <name>. Persona defaults may provide profile/model/voice/speed/filter/director-note fields, while explicit per-event overrides still win; unknown persona names are rejected.
speech.tts.hrtfDisabled-by-default decoded-audio processing seam. Processing requires enabled, dataset path, and license/provenance metadata; missing or unknown backends are no-ops.
speech.{tts,stt}.command_templateShell command template for ad-hoc speech adapters. TTS templates receive env vars such as BODY/OUTPUT_FILE; STT templates receive AUDIO_FILE/TRANSCRIPT_FILE.
speech.stt.languageDefault STT language hint for backends that support it (for example en).
speech.stt.vadTop-level VAD config for generic STT chunking. Applied before any STT backend (whisper, scribble, etc.). Scribble inherits this if its own vad block is at defaults.
speech.stt.{agent,terminal,scratchpad,text_field,bead}Per-target STT overrides for rewriting, smart append, auto-Enter, and VAD sensitivity
tui.graphics.batchUploadsBatch kitty graphics upload writes within one pass; set false to restore legacy per-surface flushing
tui.graphics.detailedPerfTelemetryEnable richer cache-category and estimated kitty wire-traffic telemetry; set false to restore coarse perf accounting
tui.graphics.imageSourceCacheCache decoded/scaled background image intermediates beneath the PNG cache, keyed by path plus file identity so same-path replacements invalidate stale cached bitmaps; set false to restore legacy decode/resize behavior
tui.graphics.surfaceUploadDedupeSkip unchanged kitty surface reuploads even when a fresh allocation is registered; set false to restore pointer-only invalidation
tui.graphics.activeAnimationOnlySkip idle animation redraw wakeups unless at least one visible kitty surface is currently animated; set false to restore the legacy always-wake behavior
tui.graphics.disableOnResizeSuppress kitty graphics during terminal or frame-geometry changes while the ratatui text baseline continues; default true
tui.graphics.resizeDebounceMsMilliseconds to wait before re-enabling kitty graphics after resize/frame geometry stabilizes; default 1000
tui.graphics.renderScaleControl kitty bitmap raster scale; default 2 preserves HiDPI fidelity, and fractional values such as 0.5 enable half-scale rendering when throughput matters more than sharpness
tui.graphics.scaledBackgroundCacheBucket procedural/scalable background render sizes so solid, gradient, glow, and scanline backgrounds reuse high-resolution cached assets across nearby panel dimensions
tui.graphics.scaledBackgroundCacheMaxPxLongest edge in pixels for scaled procedural background cache buckets; default 1280
tui.graphics.sparklineKitty graphics sparkline rendering style, opacity, and quantisation levels
tui.graphics.buttonButton chrome style layers for reusable Button widgets
tui.graphics.buttonPaddingButton padding (uniform integer or per-side map)
tui.graphics.spanGlowBleedX/YExtra columns/rows for span-level glow bleed beyond text cells
tui.graphics.gpuRenderingEnable headless wgpu compositing for eligible bitmap surfaces; falls back to CPU compositor on init failure
tui.graphics.gpuBackendGPU backend: default (Metal/Vulkan/DX12 per platform), cpu, vulkan, metal, dx12
projects[].test.maxRuntimeSecsDefault hard runtime ceiling for queued test jobs (default: 600). Jobs exceeding 80% emit a warning; Cargo/Rust commands append target-lock/process diagnostics, and hitting the limit kills the isolated child process group with an explicit timeout error.
projects[].build.maxRuntimeSecsDefault hard runtime ceiling for queued build jobs (default: 600). Build jobs share the same 80% warning and Cargo/Rust silence diagnostics, while request-level caco build run --timeout <seconds> can override one submission.
agents.heartbeat / projects[].agents.heartbeat / nodes[].agents.heartbeatGlobal defaults with project, node, and per-agent overlays. Fields are enabled, selector (all, persistent, workers, none), interval_secs, jitter_secs, max_backoff_secs, allow_runtime_opt_out, conditions, and overrides.<agent>. Conditions are OR timer gates: claimed_beads (authoritatively owned in-progress/merging work), unmerged_prs (locally linked open/returned PR work), and pending_inbox (visible unread inbox items, without consuming them). Omission inherits; [] clears inherited conditions for unconditional controller/task timers. Each daemon schedules only local agents; existing enablement/runtime controls and lifecycle exclusions still apply. heartbeat on does not bypass conditions. Unmatched/unknown ticks advance the normal interval/jitter without a wake or feed/inbox event. Runtime status includes conditions and the historical last-due-tick matches/unknowns, alongside controls, suppression and schedule.
agents.agent_directory_warn_age_secsPositive integer warning window for caco doctor to flag old local managed-agent directories (default 604800 seconds / 7 days). CACO_AGENT_DIRECTORY_WARN_AGE_SECS can override it for one doctor run. This is diagnostic-only and points operators to prune previews; it is not a retention or cleanup policy.
projects[].agent_defaults.resourcesOptional per-agent ceilings. vcpu and memory constrain host-backed agents where supported and act as admission/enforcement inputs for isolation backends; disk caps writable work disks or overlays for isolation backends.
projects[].agent_defaults.warmup_cache_injection / profile warmup_cache_injectionDisabled-by-default advisory prompt-injection policy for warmup-cache startup hints. Fields are exactly enabled, max_age_seconds (default 86400, clamped to at least 1), and max_chars (default 2048, clamped to at least 1); unknown keys are rejected during config/profile validation. When enabled, only the newest valid cache artifact matching project/profile/model may contribute a clearly labeled, bounded advisory block; missing, invalid, stale, or disabled cache data is a non-fatal skip.
projects[].agent_defaults.completed_retention_bytesParseable byte-size string capping disk retention of completed agent checkouts
updates.launcher_archive_countNumber of previous launcher binaries to retain for rollback (default: 1). On failed health checks, caco restart walks the archive newest-first; live archived launcher binaries are retained beyond the cap until their process exits.
updates.androidConfigured Android companion APK update targets for caco update android; entries name an adb serial plus optional host node, remote-host installs copy the APK to that adb host before installation, and installs remain explicit/dry-run by default.
interactive_defaultsDefault runtime type, profile, provider, model, and goal for caco claude / caco pi interactive sessions
dynamic_nodesTemplate-based node definitions for container/cloud deployments. Each template defines services, relay config, and peer allowlists for nodes that register dynamically.
Per-node health_expectationsMark expected peer-health drift (expected_unreachable, expected_mismatch, expected_degraded) with an operator-facing note. Probing stays intact; these are shown separately in health summaries and demoted in caco doctor so advisory peers do not look like unexplained incidents.
Per-project agent_defaults.persistent_agent_idle_timeout_secsIdle timeout before the daemon auto-restarts a frozen persistent agent
projects[].checkout_bootstrapShell commands to run inside fresh project checkouts (e.g. install git hooks)
services.cacophony.configTemplateEnvHome Manager: non-secret values consumed by config.yaml template expressions
services.cacophony.extraEnvHome Manager: runtime-only process environment variables
error_loggingError logging configuration. auto_file_beads (default false) controls whether the audit watcher files draft beads for daemon-level errors and crashes. default_project sets the target project for those beads. Optional oracle_dispatch is another disabled-by-default gate for future oracle dispatch adapter invocation; enabled defaults false and adapter only names the future adapter implementation.
filesProject file-cache limits and image defaults. max_file_size defaults to 5MB and validates the final stored blob; image_handling.default_behavior is preserve, compress, or reject, with optional auto_compress, compression_format (jpeg, png, webp), and compression_quality (1-100). caco file add can override these defaults for one file, while caco file sync publishes/pulls the cache through the project state branch.
default_projectDefault project for node-scoped persistent agents, daemon-level operations, and project-scoped CLI commands that omit --project after explicit CLI/env resolution. Individual agents can override via their project: field. Omitted project flags resolve to one project rather than global aggregation; cross-project results require an explicit command opt-in such as --all-projects. Interactive runtime shorthand commands remain stricter and do not silently use default_project.

State Sync Trigger Controls

sync.state separates local snapshot persistence from mesh traffic. The daemon may continue writing local snapshots for UI health even when all peer push triggers are disabled, but it only sends peer full-state pushes for enabled sync.state.push triggers such as push.on_connect, push.on_peer_connect, or push.periodic. Request-driven catch-up is similarly trigger-keyed: sync.state.pull.on_connect.enabled governs startup/rejoin full-state pulls, while sync.state.pull.periodic.enabled is the switch that starts the periodic incremental pull loop.

Periodic pull mode changes the request shape. mode: full omits per-peer since watermarks and requests a full supported-family snapshot; mode: delta sends watermarks so peers can filter supported history families by timestamp. Use the generated sync schema page for the full trigger/family matrix.

Node Resolution

The current node is resolved in this order:

  1. --node flag
  2. CACO_NODE environment variable
  3. config.node.default
  4. Error

Port Conventions

If cluster-facing ports are omitted:

FamilyRange
Local service APIs111xx
Local sidecars112xx
Cluster service APIs121xx
Cluster sidecars122xx

Config Validation

caco config validate

Validates:

The source-repository checks belong to caco config validate rather than Rust tests that read the live .cacophony/ tree. This makes them run for config-only reintegrations; the merge gate keeps new findings fatal while --demote-preexisting-vs <target-ref> demotes findings already present on the target.

Strict Validation

caco config validate --strict

Treats null-resolving template expressions as errors, catching undefined variables that would silently resolve to empty strings in normal mode. Intended for pre-merge smoke tests in agent before_reintegration hooks.

Project Config Directory Override

caco config validate --project-config-dir /path/to/checkout/.cacophony/
caco config show --project-config-dir /path/to/checkout/.cacophony/ --section projects

Validates or inspects config using a project config directory from an agent checkout instead of the live daemon checkout. This lets agents validate and review their config changes before reintegration without modifying the real project config. Both commands validate that the override directory exists before loading config, so a typo cannot silently fall back to the operator's default config. For config show, if the project config directory contains config.yaml or config.yml, that file is rendered directly so the output matches --config /path/to/checkout/.cacophony/config.yaml; config show still rejects mixing --config and --project-config-dir because the source would be ambiguous. Agents should make project config edits only in their managed checkout and land them through reintegration, not by editing $CACOPHONY_DIR/config.yaml, ~/.cacophony/config.yaml, or $CACOPHONY_DIR/daemon/checkouts/<project> directly; stale, dirty, or broken canonical checkouts are operator/controller recovery evidence, not a prompt for worker-side resets or hand-written live-config repair. Can also be set via the CACOPHONY_PROJECT_CONFIG_DIR environment variable.

Resolved YAML Output

caco config validate --show-resolved

Prints the fully template-resolved YAML with line numbers for debugging template expressions.

Template expression inspection

caco config eval 'values.providers.default'
caco config eval --strict 'deepMerge values.base values.overlay'
caco config values --json

caco config eval evaluates one template expression against the same resolved values and globals context used while rendering config and reports the stable YAML value type plus any null-resolution diagnostics. --strict turns those null diagnostics into a command failure. caco config values dumps the merged values: namespace with the same diagnostics, which is useful when imported value files and inline overrides interact.

Materialized Config Output

caco config validate --show-materialized

Prints the fully materialized config after template rendering. Useful for inspecting the final config the daemon would see. When combining --show-materialized with --project-config-dir, the CLI warns which source won: with no explicit --config it materializes the project overlay from CACOPHONY_PROJECT_CONFIG_DIR; with both flags present it materializes the explicit --config content and tells you to point --config at <project-config-dir>/config.yaml if you want that wrapper rendered directly.

Config Comparison

caco config diff

Compares the local config hash against hashes reported by peer daemons. Reports mismatches so you can identify nodes with outdated or divergent configuration. A mismatch is an operator-visible drift signal rather than automatic proof that the peer daemon API is unreachable: when the lightweight API probe is healthy, remote forwarding and read-only inspection should still try the real request, while unreachable or degraded API probes return structured peer-unreachable failures. If a peer's /api/v1/config/hash probe is still slow or failing but a lightweight data-plane probe such as /api/v1/node succeeds, peer visibility should promote from first-probe-pending or unreachable to reachable/degraded-with-diagnostics instead of claiming the node is still merely settling.

The same command also has a local config VCS mode: caco config diff <tag> compares the current local config repository against a checkpoint tag/ref and cannot be combined with --node. caco config history --limit N lists recent config snapshot commits plus config/... tags, caco config checkpoint --reason "..." [--tag TAG] commits and tags the current local config VCS state, and caco config restore TAG --yes restores tracked local config files from a checkpoint and commits the restore. Restore is intentionally confirmation-gated; after restoring, inspect caco config diff and restart/distribute only if that local config state is the intended runtime configuration.

Execution Modes

Mode rule when expressions support boolean combinators (all, any, not), comparisons, and named predicates. Temporal counter predicates add rolling-window checks for automation policies: rate_exceeds: [counter, threshold] compares the counter rate over the default five-minute window in units per minute, trending_up: counter and trending_down: counter classify the rolling trend over the default fifteen-minute window, and stable: counter requires samples to remain within the default tolerance over the five-minute stability window. Validation rejects unknown counter names or malformed temporal operands, and the daemon records counter samples before evaluating each mode cycle so these predicates react to trends rather than just the instantaneous snapshot. The daemon now also feeds resource, cluster, validation-queue, failure/retry, certificate, and UTC time signals into the mode state: examples include cpu_available_parallelism, system_load1_milli, known_peers, reachable_peers, cluster_degraded, is_beads_primary, test_queue_queued, build_queue_running, validation_queue_busy, agent_recovery_active, certificate_expiry_warnings, utc_hour, utc_weekday, is_weekday, and is_business_hours.

Config Generation

caco config generate

Emits a valid YAML config to stdout. The default generated config uses the resolved node name as node.default with a single-node setup including valid listener ports.

Credential Management

Security API tokens and auth tokens must never be stored inline in config.yaml, flake.nix, or committed profile files. Use secret file paths suitable for secret managers like sops-nix.

The daemon reads secret files at startup, trims trailing newlines, and exports the resolved tokens as environment variables for child processes. Secret contents are never persisted to config hashes, feed events, logs, or frontend APIs.

Voice Filters

Post-TTS voice filters apply sox audio effects to the synthesized speech output before playback, giving each agent or session a distinctive sound character.

Filter Modes

Only one mode should be active. Priority: rotation > preset > custom.

Preset

speech:
  tts:
    voice_filter:
      preset: telephone
PresetEffect
telephoneBandpass 300–3400 Hz, slight distortion, compression
hallLarge reverb, slight echo, wet mix ~40%
lofiBitcrush, mild saturation, narrow bandpass
underwaterHeavy low-pass, slow flanger, reverb
radioBandpass 500–4000 Hz, compression, slight overdrive
whisperSoft compression, high-pass, reverb tail
caveLong reverb, low-pass, echo
megaphoneHard compression, bandpass, overdrive
robotRing modulation, flanger, slight reverb
vintageVinyl crackle, narrow EQ, tape saturation

Custom

speech:
  tts:
    voice_filter:
      custom: "reverb 50 highpass 300"

Raw sox effect chain string, passed directly to the sox processor.

Rotation

speech:
  tts:
    voice_filter:
      rotation:
        presets:
          - telephone
          - hall
          - lofi
          - radio
          - cave
        combine_with_voice: true

Assigns filters from the pool to agents in rotation. When combine_with_voice is true (default), filter rotation is paired with voice rotation to create unique (voice, filter) identity pairs per agent.

Per-Target STT Overrides

STT behavior can be customized per transcription target type. Each target type supports the following overrides:

FieldDefaultDescription
rewritingfalseEnable smart rewriting of transcribed text
smart_appendfalseEnable per-chunk cleanup when appending
enter_after_completedfalseSend Enter keypress after transcription completes (agent/terminal targets only)

Target Types

TargetDescription
agentAgent tmux panes
terminalTerminal/shell panes
scratchpadScratchpad notes
text_fieldText fields and chat compose areas
beadBead create form fields
speech:
  stt:
    agent:
      rewriting: false
      enter_after_completed: false
    scratchpad:
      rewriting: true
      smart_append: true
    text_field:
      rewriting: true

TUI Theme Registry

tui:
  theme_name: high
  themes:
    enterprise-blueprint:
      imports:
        - themes/enterprise-blueprint.yaml
      colors:
        foreground_color: "#eceff4"
        background_color: "#0f1318"
        color0: "#2e3440"
        # ... color1 through color15
      graphics:
        # optional GraphicsConfig overrides
nodes:
  - name: ms-mac
    tui:
      theme_name: high
      graphics:
        footer:
          background_color: "#5e81ac"

The current theme contract is an explicit registry of named themes. Prefer explicit palette slots (foreground_color, background_color, and color0–color15) plus optional graphics overrides over older preset-only colorscheme snippets. Node-local tui.theme_name values select from the same registry without duplicating theme definitions; node-local tui.graphics.footer.background_color can tint the status/footer chrome for node identity. Use nord-avatars for avatar-image backgrounds, while the default quality tiers keep avatar image lists empty.

Graphics Configuration

Background caches

tui.graphics.imageSourceCache defaults to true and reuses decoded/resized image intermediates beneath the PNG cache. Image background render keys and decoded/resized source caches include the file length and modification-time identity in addition to the path, so replacing an asset in place invalidates stale cached bitmaps without disabling the cache. Procedural backgrounds also default tui.graphics.scaledBackgroundCache to true, with tui.graphics.scaledBackgroundCacheMaxPx (default 1280), to render solid, gradient, glow, and scanline backgrounds once per high-resolution aspect bucket and scale them into nearby panel sizes; image backgrounds keep exact sizing for crop/anchor correctness. Set scaledBackgroundCache: false only for rollback or exact-raster diagnostics.

Sparklines

The TUI can render telemetry sparklines as kitty graphics bitmaps instead of Unicode block characters. Configured under tui.graphics.sparkline:

tui:
  graphics:
    sparkline:
      style: gradient_fill_line    # bar | line | gradient_fill_line
      opacity: 0.8                 # Line/fill opacity (0.0–1.0)
      fadeOpacity: 0.0             # Bottom gradient opacity (gradient_fill_line only)
      levels: 10                   # Quantisation levels for segment atlas
StyleDescription
barFilled bars per cell, height proportional to data value
lineLine graph connecting consecutive values
gradient_fill_lineLine graph with gradient fill fading from line to bottom (default)

Requires kitty graphics capability. Falls back to standard ratatui Sparkline widget when graphics are unavailable. The segment atlas is pre-computed on first render and re-uploaded on theme change.

Button Chrome

Reusable button widgets support configurable chrome via tui.graphics.button (standard GraphicsStyleLayers fields) and padding via tui.graphics.buttonPadding:

tui:
  graphics:
    buttonPadding: 1               # Uniform padding (all sides)
    # Or per-side:
    buttonPadding:
      top: 0
      left: 1
      bottom: 0
      right: 1

Span Glow Bleed

Controls how far glow effects extend beyond text cell boundaries:

FieldDefaultDescription
spanGlowBleedX2Extra columns on each side
spanGlowBleedY1Extra rows on each side

Minimum Provider Environment Variables

ProviderBase URLSecret
OpenAI-compatibleOPENAI_BASE_URLOPENAI_API_KEY
Anthropic-compatibleANTHROPIC_BASE_URLANTHROPIC_AUTH_TOKEN