Nix & Home Manager

Cacophony ships as a Nix flake with a Home Manager module for declarative per-user deployment.

Flake Outputs

OutputDescription
packages.<system>.cacoThe wrapped caco package with bundled startup PATH (including tmux-cli, excluding optional browser/image/YAML/network helpers)
apps.<system>.cacoRunnable app (nix run) using the same wrapped launcher
packages.<system>.playwright-cliExplicit on-demand browser-observation helper package
devShells.<system>.defaultDevelopment shell with Rust toolchain, av, tmux-cli, playwright-cli, image helpers, and YAML convenience tooling
devShells.<system>.caco-runtimeLightweight managed-worker shell with Rust, pinned av, and runtime helpers
homeManagerModules.cacoHome Manager module

Development

# Enter dev shell
nix develop

# Build
cargo build

# Run
cargo run -p caco -- version --json

# Or run directly via nix
nix run . -- version --json

The flake-distributed caco launcher is wrapped with the startup runtime tool PATH Caco needs, including tmux-cli, so direct nix run and profile-installed launches do not depend on a login-shell environment to find required startup helpers. Browser, image, YAML, and legacy network helpers such as playwright-cli, rembg, yq, and nettools remain available on demand through the default dev shell or explicit packages where exposed, but are not included in the mandatory launcher or service startup closure; cold optional dependency realization should not block daemon or persistent-agent startup. Source-sensitive helpers such as caco config sparse validate warn when the running binary commit differs from checkout HEAD, because Nix/profile installs can lag a just-edited source tree.

Home Manager Module

The Home Manager module provides declarative per-user bootstrap. It installs the binary, materializes config, exports environment variables, and optionally enables a native user-service backend that runs a foreground Cacophony lifecycle supervisor.

Minimal Configuration

{
  services.cacophony = {
    enable = true;
    package = inputs.cacophony.packages.${pkgs.system}.caco;
    nodeName = "localhost";
  };
}

Full Configuration

{
  services.cacophony = {
    enable = true;
    package = inputs.cacophony.packages.${pkgs.system}.caco;
    nodeName = "localhost";
    cacophonyDir = "${config.home.homeDirectory}/.cacophony";

    providers = {
      openai = {
        baseUrl = "https://api.openai.com/v1";
        apiKeyFile = config.sops.secrets.openai-api-key.path;
      };
      anthropic = {
        baseUrl = "https://api.anthropic.com";
        authTokenFile = config.sops.secrets.anthropic-auth-token.path;
      };
      pi.githubCopilot.authFile = "~/.pi/agent/auth.json";
    };

    configTemplateEnv = {
      CACOPHONY_OPENAI_API_KEY_FILE = config.sops.secrets.openai-api-key.path;
    };

    autoUp = true;                  # Enable the managed lifecycle supervisor
    downOnDisable = true;           # Run caco down when disabled
    restartOnConfigChange = false;  # Default: log config drift; operator runs caco restart explicitly
    # Trusted LAN/VPN or SSH tunnel only; do not expose PulseAudio TCP publicly.
    extraEnv = {
      PULSE_SERVER = "<trusted-audio-host>:4713";
    };
  };
}

Module Options

OptionTypeDefaultDescription
enableboolfalseEnable Cacophony management
packagepackage—The caco package to install
nodeNamestring—Logical node name (sets CACO_NODE)
cacophonyDirpath~/.cacophonyRuntime directory (sets CACOPHONY_DIR)
providers.openai.baseUrlstring—OpenAI-compatible base URL
providers.openai.apiKeyFilepath—Secret file for OPENAI_API_KEY
providers.anthropic.baseUrlstring—Anthropic-compatible base URL
providers.anthropic.authTokenFilepath—Secret file for ANTHROPIC_AUTH_TOKEN
providers.pi.githubCopilot.authFilepath—Pi native GitHub Copilot auth.json copied into managed Pi workspaces
autoUpbooltrueEnable the managed lifecycle supervisor
downOnDisablebooltrueRun caco down when disabled
restartOnConfigChangeboolfalseOpt in to automatic caco restart when config.yaml changes; disabled by default to avoid restart loops from routine config writes
configTemplateEnvattrs{}Non-secret env exposed to {{ env ... }} config templates
extraEnvattrs{}Additional non-secret environment variables

Behavior

When Enabled

  1. Installs the caco binary
  2. Writes a valid config.yaml to $CACOPHONY_DIR
  3. Exports CACOPHONY_DIR and CACO_NODE into managed processes
  4. If autoUp = true: installs a native user service (launchd or systemd) that runs caco supervise --interval 30 from the live caco binary, so supervision policy updates with caco itself.
    • What restarts: process and listener liveness are the hard signals. A slow or backpressured daemon API is reported as degraded rather than restarted, unless the daemon stays alive but unresponsive past warmup and a bounded grace period. A stopped TTS service alone never restarts the daemon.
    • Config changes are detected but don't restart services by default; run caco restart, or set restartOnConfigChange = true.
    • Planned restarts: restart sentinels (including the one the health watchdog writes before exiting) make a respawn read as maintenance, and are cleared once services converge. Refresh helpers that install a new caco run caco service start afterwards so the supervisor stays the owner.
    • Not loaded: if the service is installed but not loaded, caco service status --json reports health_state: "not_loaded" with repair_command: "caco service load", and caco ops check raises it as a safe action. On launchd, caco service load bootstraps without a needless kickstart -k.
    • Manual startup: use caco service disable rather than raw launchctl/systemctl, then caco up --skip-update if services should still run unsupervised.
  5. Module-managed units load provider tokens from secret paths and export as environment variables
  6. When providers.pi.githubCopilot.authFile is set, managed Pi agents can launch with provider: github-copilot and a separate model; Cacophony translates that to Pi's native --model github-copilot/<model> selector and copies the configured auth.json into .pi-agent/

When Disabled

Service Managers

PlatformService Manager
LinuxUser-level systemd
DarwinUser-level launchd
nix-on-droidConditional supervisord

The native service manager keeps the foreground lifecycle loop alive; the loop itself remains the authority for daemon and sidecar lifecycle and does not supervise caco daemon directly.

Secrets Management

sops-nix Integration Provider secret file paths are designed for use with sops-nix. Use config.sops.secrets.<name>.path directly as the secret file option. Tokens are read at startup, never stored in config or committed files.