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

## Upgrade daemon backends to 0.20.0

Update the Chorus CLI using [the upgrade command](/reference/cli/#upgrade-the-cli-and-plugins),
then restart the daemon when ongoing work allows. Host CLIs are updated separately.
Check these backend-specific requirements before the daemon first starts work:

- [Codex App Server](/reference/agents/codex/#daemon-behavior-in-0200): verified with
  codex-cli 0.157.1; daemon `args` accept only model options and permitted config overrides.
  In `--chorus-only` mode, verify the required MCP tool allowances in Codex configuration.
- [Claude Code stream-json](/reference/agents/claude-code/#daemon-behavior-in-0200): verified
  with Claude Code 2.1.283 and 2.1.284; interruption first uses the protocol, and background
  questions must use Chorus comments or elaboration.
- [Pi RPC](/reference/agents/pi/#daemon-behavior-in-0200): requires Pi 0.85.0 or newer
  (verified with 0.85.1); `--plugins` refreshes the Chorus packages but not Pi itself.

## Choose what the daemon can access

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

```sh
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](/guides/create-a-project/) 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.

:::caution[Limit filesystem access]
Register only directories the agent is allowed to read and modify. A remotely started
agent receives the filesystem and tool permissions of the user running the daemon.
:::

## Configure `daemon.json`

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](#run-several-agents-in-one-daemon). Either way it is
written atomically with `0600` (owner read/write only), so a crash mid-write never leaves a
truncated file. Set `CHORUS_DAEMON_CONFIG_PATH` to use a different file. If you use this
override, set it consistently for both the CLI and the daemon service; an export in your
shell does not change an already-running service.

### Fields

| Field | Type | Meaning |
| --- | --- | --- |
| `url` | string | Chorus server URL. |
| `apiKey` | string | Agent API key (`cho_…`). |
| `agentUuid` | string | Authenticated agent UUID (informational). |
| `agentName` | string | Authenticated agent name (informational). |
| `cwds` | string[] | Working directories this daemon serves. Each path is one independent online connection. |
| `browseRoots` | string[] | Roots exposed to remote working-directory discovery. A browse root does not create a connection. |
| `agent` | string | Local backend to wake: `"claude-code"`, `"codex"`, `"kiro"`, `"pi"`, or `"dsh"`. |
| `sigintTimeoutMs` | number | How long (ms) to wait after an interrupt before forcing the process to stop; Claude Code, Codex, and Pi send a protocol interrupt first. Default `10000`. |
| `agents` | object[] | *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](#run-several-agents-in-one-daemon). |

A minimal file after `chorus login` plus `chorus daemon install`:

```json
{
  "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
}
```

### How the file is created

- `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](/guides/operator-onboarding/).
- `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.

### How the file is updated

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.

### Field, flag, and environment variable

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

| Concern | `daemon.json` field | CLI flag | Environment variable |
| --- | --- | --- | --- |
| Server URL | `url` | `--url` | `CHORUS_URL` |
| API key | `apiKey` | `--api-key` | `CHORUS_API_KEY` |
| Working directories | `cwds` | `--cwd` (repeatable) | `CHORUS_DAEMON_CWDS` |
| Browse roots | `browseRoots` | `--browse-root` (repeatable) | `CHORUS_DAEMON_BROWSE_ROOTS` |
| Backend | `agent` | `--agent` | `CHORUS_AGENT` |
| SIGINT grace | `sigintTimeoutMs` | `--sigint-timeout` | `CHORUS_DAEMON_SIGINT_TIMEOUT` |
| Permission mode | *(not persisted)* | `--yolo` / `--chorus-only` | `CHORUS_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.

## Run several agents in one daemon

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:

```json
{
  "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, and an agent's
own value overrides them. `args` and `env` are per-agent only, never shared defaults.

Each agent gets its own identity (from its key), its own connections (one for each of its
`cwds`), its own wake queue, and its own backend. Agents are therefore 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).

:::note[Back-compat]
With no `agents` array, the flat top-level `url` / `apiKey` / `cwds` are treated as exactly one
agent — existing single-agent installs run unchanged.
:::

### Per-agent fields

| Field | Meaning |
| --- | --- |
| `apiKey` *(required)* | The agent's `cho_` key — determines its identity. |
| `url` | Chorus server (may differ per agent — a different server or workspace). |
| `agentType` | `claude-code`, `codex`, `kiro`, or `pi` (backends may be mixed). |
| `cwds` | Working directories this agent serves (one connection each). |
| `permissionMode` | `yolo` or `chorus`. |
| `maxConcurrency` | This agent's own wake-concurrency cap (default `4`). |
| `sigintTimeoutMs` | How long (ms) to wait after an interrupt before forcing the process to stop. |
| `browseRoots` | Directory-discovery allowlist. |
| `args` | Optional string array of extra backend CLI arguments; default `[]`. See [Set an agent's model, thinking level, and environment](#set-an-agents-model-thinking-level-and-environment). |
| `env` | Optional object mapping environment names to string values; default `{}`. |

### 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 rather than
overwriting other agents:

```json
{
  "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, and other options
  that only print information and exit are also forbidden, as are a bare `--` and the stdin
  marker `-`. All `CHORUS_*` env names and the nested-Claude context names `CLAUDECODE` /
  `CLAUDE_CODE_ENTRYPOINT` are reserved, regardless of case. Configuring any of them causes an
  error rather than being silently discarded. 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 and 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](/reference/cli/) for exact one-off override and subcommand rules.

### Add another agent

- **`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**.

### How each backend receives its key

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 without
  storing the key in its config: 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 from the file. 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.

## Install the Linux service

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

```sh
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
service in the same step.

:::note[Why credentials live in the file]
A `systemd --user` unit starts in a clean boot environment and does **not** inherit the
`CHORUS_URL` / `CHORUS_API_KEY` you export in your shell. So `chorus daemon install`
resolves, validates, and persists your credentials into `~/.chorus/daemon.json` (`0600`)
before writing the unit. The unit references neither the file path nor the credentials —
it only sets `HOME` so the daemon can locate `~/.chorus/daemon.json` at boot. The served
`cwds` and chosen `agent` live in the same file; only `--chorus-only` is captured in the
unit.
:::

### Provide credentials a woken agent needs

The daemon supplies the Chorus connection (`CHORUS_URL`, `CHORUS_API_KEY`, and
`CHORUS_AGENT_PROFILE`), but not model-provider credentials. If your backend reads provider
credentials from environment variables, 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, add a drop-in and restart:

```sh
# ~/.config/systemd/user/chorus-daemon.service.d/provider-env.conf
[Service]
Environment=EXAMPLE_PROVIDER_API_KEY=…
```

```sh
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:

```sh
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**.

:::note[macOS and Windows]
On macOS, `chorus daemon install` installs a real launchd boot service, the same way it does
on Linux. On Windows it prints a service template and manual setup steps; use foreground
`chorus daemon` as the baseline when testing a connection.
:::

### Keep the Linux daemon running after logout

From Chorus 0.21.0, `chorus daemon install` tries to enable **systemd lingering** for the current user, so the user service survives the last logout and starts at boot without a login. The authorization attempt is non-interactive and time-bounded; it never waits for a password dialog.

If authorization is denied or logind is unavailable, service installation can still succeed, but Chorus warns that logout/boot persistence is not assured. Run `chorus daemon status` to check for `linger: yes`. Ask an administrator to run the printed `sudo loginctl enable-linger <user>` command if your host policy allows it.

Rerun `chorus daemon install` to repair an existing host. `chorus agents add` also checks lingering when installing the service or finding an existing service, even without `--daemon-autostart` or after declining auto-start. Pass `--no-linger` to either command to skip the attempt; it does not disable an existing setting. Without lingering, the daemon may stop after logout and not start until the next login. Uninstalling the Chorus service leaves lingering enabled because other user services may need it.

## Run without a service

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

```sh
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.

## Check operating health

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](/guides/troubleshooting/).
