Skip to content

Operate the Chorus daemon

Use this guide after you have verified a foreground daemon connection. The commands apply to the supported resident backends: Claude Code, Codex, Kiro, and Pi.

Each --cwd registers one working directory as a separate target in Chorus:

Terminal window
chorus daemon \
--agent claude-code \
--cwd ~/work/project-a \
--cwd ~/work/project-b \
--chorus-only

With no --cwd, Chorus uses the directory where you start the command. Use repeatable --browse-root options only when users need to choose directories below a broader root. A browse root allows discovery; it does not register every directory as an online connection.

When you fix a working directory for a project’s agent, that path must be one the daemon serves — include it in this daemon’s cwds set so the fixed directory resolves to an online connection.

The daemon reads its settings from ~/.chorus/daemon.json. In the common single-agent case it is one flat JSON object holding the agent credential and the runtime options; to serve several agents from one daemon you instead list them under an agents array — see Run several agents in one daemon. Either way it is written with 0600 (owner read/write only) using an atomic temp-file-plus-rename, so a crash mid-write never leaves a truncated file. The default path is ~/.chorus/daemon.json (resolved from your home directory). CHORUS_DAEMON_CONFIG_PATH can select a different file. When using an override, set it consistently for the CLI and daemon service; an export in your shell does not change an already-running service.

FieldTypeMeaning
urlstringChorus server URL.
apiKeystringAgent API key (cho_…).
agentUuidstringAuthenticated agent UUID (informational).
agentNamestringAuthenticated agent name (informational).
cwdsstring[]Working directories this daemon serves. Each path is one independent online connection.
browseRootsstring[]Roots exposed to remote working-directory discovery. A browse root does not create a connection.
agentstringLocal backend to wake: "claude-code", "codex", "kiro", or "pi".
sigintTimeoutMsnumberGrace window (ms) after SIGINT before a woken agent is force-killed. Default 10000.
agentsobject[]Optional. Serve several independent agents from one daemon; each entry can override shared runtime defaults, but args and env are never inherited. See Run several agents in one daemon.

A minimal file after chorus login plus chorus daemon install:

{
"url": "https://chorus.example.com",
"apiKey": "cho_REDACTED",
"agentUuid": "8a1c…",
"agentName": "Build Agent",
"cwds": ["/home/demo/work/project-a"],
"browseRoots": ["/home/demo/work"],
"agent": "claude-code",
"sigintTimeoutMs": 10000
}
  • chorus agents add is the usual way the file is first created: it validates each configured agent’s credential and writes it as an entry in the agents array. See Connect an agent runtime.
  • chorus login validates your URL and key, then writes url, apiKey, agentUuid, and agentName for a single flat agent.
  • chorus daemon install additionally writes cwds, browseRoots, and agent (it prompts for the served directories and backend when they are not already set).
  • A first chorus daemon run on a terminal completes any missing credentials interactively and writes them.

Every writer performs a shallow merge: the new fields are merged over whatever is already on disk, and unrelated fields are preserved. Re-running chorus login refreshes the credentials without clearing your cwds, agent, or sigintTimeoutMs; re-running chorus daemon install updates the served set without discarding the credential. A missing or corrupt file is treated as an empty object, so a re-login always produces a valid file rather than failing.

The runtime options below can be supplied three ways. Precedence is flag, then environment variable, then daemon.json, so a one-off flag or env override never has to be written to the file:

Concerndaemon.json fieldCLI flagEnvironment variable
Server URLurl--urlCHORUS_URL
API keyapiKey--api-keyCHORUS_API_KEY
Working directoriescwds--cwd (repeatable)CHORUS_DAEMON_CWDS
Browse rootsbrowseRoots--browse-root (repeatable)CHORUS_DAEMON_BROWSE_ROOTS
Backendagent--agentCHORUS_AGENT
SIGINT gracesigintTimeoutMs--sigint-timeoutCHORUS_DAEMON_SIGINT_TIMEOUT
Permission mode(not persisted)--yolo / --chorus-onlyCHORUS_YOLO / CHORUS_CHORUS_ONLY

The permission mode is deliberately not stored in daemon.json. Pass --chorus-only (or set CHORUS_CHORUS_ONLY=1) at start time; the installed service captures --chorus-only in its unit, not in the file.

One chorus daemon process can serve several fully independent agents at once — different personas, permissions, accounts, or even different backends — instead of running a separate daemon per agent. List them under an agents array; each entry is one agent:

{
"url": "https://chorus.example.com",
"sigintTimeoutMs": 8000,
"agents": [
{ "apiKey": "cho_alpha", "agentType": "claude-code", "cwds": ["/home/demo/project-a"] },
{ "apiKey": "cho_beta", "agentType": "kiro", "cwds": ["/home/demo/project-b"], "permissionMode": "chorus" }
]
}

Shared runtime settings such as url and sigintTimeoutMs provide defaults; an agent’s own value overrides that default. args and env are per-agent only, not shared defaults. Each agent gets its own identity (from its key), its own connections (one per its cwds), its own wake queue, and its own backend — so they are woken and run independently, and one agent’s failure never disrupts the others. On the server each appears as its own connection in Settings → Agents, keyed on agent, host, and directory. Agents may even share a working directory; the daemon does not serialize them, so avoid concurrent conflicting work in one git tree (use separate branches or worktrees).

FieldMeaning
apiKey (required)The agent’s cho_ key — determines its identity.
urlChorus server (may differ per agent — a different server or company).
agentTypeclaude-code, codex, kiro, or pi (backends may be mixed).
cwdsWorking directories this agent serves (one connection each).
permissionModeyolo or chorus.
maxConcurrencyThis agent’s own wake-concurrency cap (default 4).
sigintTimeoutMsInterrupt-escalation grace window (ms).
browseRootsDirectory-discovery allowlist.
argsOptional string array of extra backend CLI arguments; default []. See Set an agent’s model, thinking level, and environment.
envOptional object mapping environment names to string values; default {}.

Set an agent’s model, thinking level, and environment

Section titled “Set an agent’s model, thinking level, and environment”

Add the fields to the intended entry in ~/.chorus/daemon.json. This Pi example uses illustrative credentials and a non-secret environment value; replace the URL, key, and model with values valid for your setup. Merge it into your existing file, not over other agents:

{
"url": "https://chorus.example.com",
"agents": [
{
"agentName": "pi-worker",
"apiKey": "cho_REDACTED",
"agentType": "pi",
"args": ["--model", "anthropic/claude-sonnet-4-6", "--thinking", "high"],
"env": { "PROVIDER_REGION": "us-east-1" }
}
]
}

The model is an example, not a Chorus default. Use flags and values supported by your backend; there are no separate model or thinking fields. Both daemon wakes and chorus agents run --name pi-worker use this entry. offline agents stay non-wakeable.

  • No global inheritance: with a nonempty agents array, top-level args or env cause an error. Move them into each intended entry. Other agents and your shell are unaffected.
  • Literal values: each args element is one token, without extra shell quoting. $HOME, ${TOKEN}, and shell commands are not expanded. Chorus adds no dotenv loading, interpolation, secret lookup, or special handling for env values. env overrides the child’s inherited environment; Chorus-managed controls still take precedence. Windows env names are case-insensitive; POSIX names are case-sensitive.
  • Validation: null, non-string tokens/values, NUL characters, and env names outside [A-Za-z_][A-Za-z0-9_]* are rejected before launch. Empty string values are valid. Use options, not positional prompts or subcommands. For an unknown option, use "--future=value" if the backend supports it; "--future", "value" is ambiguous and rejected.
  • Protected controls: persistent args cannot change known session/resume, prompt, transport/output, cwd, managed MCP, or permission controls. Help/version, inspection exits, bare --, and stdin marker - are also forbidden. All CHORUS_* env names and the nested-Claude context names CLAUDECODE / CLAUDE_CODE_ENTRYPOINT are reserved, regardless of case. Configuring them causes an error instead of silently discarding them. Use dedicated Chorus settings or explicit foreground passthrough instead.
  • Windows command shims: .cmd/.bat launches reject configured empty tokens and tokens containing whitespace, quotes, or command metacharacters. Use a native executable for those values; explicit foreground passthrough is not guaranteed shell-safe through a shim.

Env can contain plaintext provider credentials. Protect the file (chmod 600 ~/.chorus/daemon.json on POSIX) and never commit secrets. Chorus omits configured values from validation/launch messages, but the child program or OS inspection may expose them; there is no encryption or vault. If you set a custom home such as CODEX_HOME or PI_CODING_AGENT_DIR, configure the Chorus integration there too.

Apply changes: run chorus daemon restart. The daemon reads these settings at startup, not on each wake; editing the file changes neither running children nor later wakes until restart. Each new chorus agents run rereads the profile, without changing an already-running child. Remove args/env to restore the uncustomized behavior.

Legacy flat files: without a nonempty agents array, top-level args/env apply to the sole daemon agent. Foreground selection requires agents[]. To share settings, move the flat URL, key, identity, args, and env into an entry, renaming agent to agentType. Adding an agent with chorus agents add or chorus login --add also moves an existing flat credential profile and its args/env into agents[0], removing those top-level keys. An incomplete flat profile without its key must be completed or converted manually first.

See the CLI reference for exact one-off override and subcommand rules.

  • chorus login --add validates a new key and appends it as another agent. The first --add on a flat file migrates the existing credential into agents[0] and adds the new key as agents[1]; a duplicate key is refused, and an existing agent is never overwritten.
  • chorus daemon install --add loops the install wizard so you can add several agents in one pass (terminal only).
  • Hand-editing ~/.chorus/daemon.json is always supported.

Restart the daemon after editing the file (chorus daemon restart), then confirm each agent appears in Settings → Agents.

Each agent authenticates with its own key, but how that key reaches the woken subprocess depends on the backend:

  • Claude Code — automatic. The daemon writes a per-wake MCP config carrying that agent’s URL and key. Nothing to configure.
  • Kiro — automatic, via the environment. The installed mcp.json references ${CHORUS_URL} and ${env:CHORUS_API_KEY}, which the daemon exports per wake, so each Kiro agent authenticates with its own key.
  • Codex — automatic, via the environment. chorus agents add configures Codex keyless: it writes CHORUS_URL / CHORUS_API_KEY / CHORUS_AGENT_PROFILE into ~/.codex/.env (which Codex loads at startup) and sets bearer_token_env_var = "CHORUS_API_KEY" in config.toml, so Codex reads its key from the environment rather than a baked literal. The daemon injects each agent’s own key per wake, so several Codex agents in one daemon each authenticate as themselves.
  • Pi — automatic, via the environment. The daemon exports each agent’s CHORUS_URL / CHORUS_API_KEY / CHORUS_AGENT_PROFILE into the woken pi session, which the chorus-pi extension reads, so each pi agent authenticates with its own key.

On Linux, install the verified configuration as a user service:

Terminal window
chorus daemon install \
--agent claude-code \
--cwd ~/work/project-a \
--chorus-only

The installer starts the service immediately and configures it to start when the user logs in. Run chorus agents add (or chorus login) first so the service can read the saved credential — or pass --daemon-autostart to chorus agents add to install this boot service in the same step.

The daemon supplies the Chorus connection (CHORUS_URL, CHORUS_API_KEY, and CHORUS_AGENT_PROFILE), not model-provider credentials automatically. If your backend reads provider credentials from env, supply them in the agent’s env or in the daemon’s inherited environment. A systemd --user service does not inherit exports from your login shell. To supply them through the service, use a drop-in and restart:

~/.config/systemd/user/chorus-daemon.service.d/provider-env.conf
[Service]
Environment=EXAMPLE_PROVIDER_API_KEY=
Terminal window
systemctl --user daemon-reload && systemctl --user restart chorus-daemon

Backends that read provider auth from a file instead (for example Claude Code and Codex under ~/.claude / ~/.codex) only need the daemon’s HOME set correctly, which the installed unit already does.

Use these commands for routine operation:

Terminal window
chorus daemon status
chorus daemon logs
chorus daemon restart
chorus daemon stop
chorus daemon uninstall

After changing credentials, working directories, backend, or permission mode, reinstall or restart the service and verify the connection in Settings → Agents.

For a short-lived connection, keep chorus daemon in the foreground. Detached mode is also available:

Terminal window
chorus daemon -d --agent codex --cwd ~/work/project-a --chorus-only

Manage it with the same status, logs, restart, and stop commands. Do not combine detached mode with a separately written process supervisor.

Use both command-line and web status:

  1. Run chorus daemon status.
  2. Review recent output with chorus daemon logs.
  3. In Settings → Agents, confirm each expected working directory is Online.
  4. Start a small test session and confirm it targets the intended directory.

Logs can contain paths, prompts, and command output. Remove sensitive values before sharing them. For failures, continue with Troubleshoot agent connections.