> ## Documentation Index
> Fetch the complete documentation index at: https://docs.risolu.to/llms.txt
> Use this file to discover all available pages before exploring further.

# Configuration Reference

> Complete reference for all Risoluto configuration options — server, polling, tracker, agent, codex, workspace, sandbox, secrets, and notification settings.

Risoluto's configuration is layered with this merge order (last wins):

1. **Built-in defaults** — sensible out-of-the-box configuration
2. **Persistent overlay** — `~/.risoluto/config/overlay.yaml` (or `$DATA_DIR/config/overlay.yaml`)
3. **Environment variables** and `$SECRET:name` resolution at runtime

## Managing Configuration

<Tabs>
  <Tab title="API">
    ```bash theme={null}
    # View effective (merged) config
    curl -s http://127.0.0.1:4000/api/v1/config

    # View overlay only
    curl -s http://127.0.0.1:4000/api/v1/config/overlay

    # Update overlay (deep merge)
    curl -s -X PUT http://127.0.0.1:4000/api/v1/config/overlay \
      -H 'Content-Type: application/json' \
      -d '{"codex":{"model":"gpt-5.4","reasoningEffort":"high"}}'

    # Patch a single path
    curl -s -X PATCH http://127.0.0.1:4000/api/v1/config/overlay/agent.maxConcurrentAgents \
      -H 'Content-Type: application/json' \
      -d '{"value": 5}'

    # Delete an overlay key
    curl -s -X DELETE http://127.0.0.1:4000/api/v1/config/overlay/codex.personality

    # View config schema (JSON Schema)
    curl -s http://127.0.0.1:4000/api/v1/config/schema
    ```
  </Tab>

  <Tab title="Dashboard">
    Use the **System → Configuration** page in the dashboard to edit the overlay visually. Changes take effect immediately — no restart required.
  </Tab>

  <Tab title="YAML File">
    Edit the overlay file directly:

    ```yaml theme={null}
    # ~/.risoluto/config/overlay.yaml
    server:
      port: 4000
    codex:
      model: gpt-5.4
      reasoningEffort: high
    agent:
      maxConcurrentAgents: 5
    workspace:
      strategy: worktree
    ```
  </Tab>
</Tabs>

<Tip>
  Overlay changes via the API or dashboard are persisted to disk and survive restarts. You never need to restart Risoluto after a config change — the orchestrator reloads automatically.
</Tip>

## Configuration Sections

<AccordionGroup>
  <Accordion title="Server & Polling">
    | Key                  | Type   | Default | Description                              |
    | -------------------- | ------ | ------- | ---------------------------------------- |
    | `server.port`        | number | `4000`  | HTTP server port                         |
    | `polling.intervalMs` | number | `15000` | Tracker polling interval in milliseconds |
  </Accordion>

  <Accordion title="Tracker">
    | Key                      | Type      | Default                              | Description                                       |
    | ------------------------ | --------- | ------------------------------------ | ------------------------------------------------- |
    | `tracker.kind`           | string    | `"linear"`                           | Tracker provider (`"linear"` or `"github"`)       |
    | `tracker.apiKey`         | string    | `""`                                 | Tracker API key (prefer `$SECRET:LINEAR_API_KEY`) |
    | `tracker.endpoint`       | string    | `"https://api.linear.app/graphql"`   | Tracker GraphQL endpoint                          |
    | `tracker.projectSlug`    | string    | `null`                               | Linear project slug to watch                      |
    | `tracker.owner`          | string    | `""`                                 | GitHub owner (when `kind: "github"`)              |
    | `tracker.repo`           | string    | `""`                                 | GitHub repo (when `kind: "github"`)               |
    | `tracker.activeStates`   | string\[] | `["Backlog", "Todo", "In Progress"]` | States that trigger agent dispatch                |
    | `tracker.terminalStates` | string\[] | `["Done", "Canceled"]`               | States that mark issues as complete               |
  </Accordion>

  <Accordion title="Agent (Orchestrator)">
    | Key                                | Type      | Default   | Description                                              |
    | ---------------------------------- | --------- | --------- | -------------------------------------------------------- |
    | `agent.maxConcurrentAgents`        | number    | `10`      | Maximum simultaneous agent workers                       |
    | `agent.maxConcurrentAgentsByState` | object    | `{}`      | Per-state concurrency limits (e.g. `{"In Progress": 3}`) |
    | `agent.maxTurns`                   | number    | `20`      | Max conversation turns per agent run                     |
    | `agent.maxRetryBackoffMs`          | number    | `300000`  | Maximum retry backoff (5 min)                            |
    | `agent.maxContinuationAttempts`    | number    | `5`       | Max continuation turns after initial run                 |
    | `agent.successState`               | string    | `null`    | Linear state to transition to on success                 |
    | `agent.stallTimeoutMs`             | number    | `1200000` | Agent stall detection timeout (20 min)                   |
    | `agent.preflightCommands`          | string\[] | `[]`      | Shell commands to run before each agent turn             |
  </Accordion>

  <Accordion title="Codex (Agent Provider)">
    | Key                       | Type           | Default                    | Description                                         |
    | ------------------------- | -------------- | -------------------------- | --------------------------------------------------- |
    | `codex.command`           | string         | `"codex app-server"`       | Command to start the Codex agent                    |
    | `codex.model`             | string         | `"gpt-5.4"`                | Model identifier                                    |
    | `codex.reasoningEffort`   | enum           | `"high"`                   | `none`, `minimal`, `low`, `medium`, `high`, `xhigh` |
    | `codex.personality`       | string         | `"friendly"`               | Agent personality/tone                              |
    | `codex.selfReview`        | boolean        | `false`                    | Enable agent self-review before completing          |
    | `codex.structuredOutput`  | boolean        | `false`                    | Enable structured output mode                       |
    | `codex.approvalPolicy`    | string\|object | reject all                 | Tool call approval policy                           |
    | `codex.threadSandbox`     | string         | `"workspace-write"`        | Thread-level sandbox policy                         |
    | `codex.turnSandboxPolicy` | object         | `{type: "workspaceWrite"}` | Per-turn sandbox policy                             |

    **Timeouts:**

    | Key                      | Type   | Default   | Description                      |
    | ------------------------ | ------ | --------- | -------------------------------- |
    | `codex.readTimeoutMs`    | number | `5000`    | JSON-RPC read timeout            |
    | `codex.turnTimeoutMs`    | number | `3600000` | Max duration per turn (1 hour)   |
    | `codex.drainTimeoutMs`   | number | `2000`    | Drain timeout on shutdown        |
    | `codex.startupTimeoutMs` | number | `30000`   | Container startup timeout (30s)  |
    | `codex.stallTimeoutMs`   | number | `300000`  | Per-turn stall detection (5 min) |

    **Authentication:**

    | Key                     | Type   | Default      | Description                    |
    | ----------------------- | ------ | ------------ | ------------------------------ |
    | `codex.auth.mode`       | enum   | `"api_key"`  | `api_key` or `openai_login`    |
    | `codex.auth.sourceHome` | string | `"~/.codex"` | Path to Codex auth credentials |
  </Accordion>

  <Accordion title="Sandbox (Container)">
    | Key                             | Type      | Default                   | Description                        |
    | ------------------------------- | --------- | ------------------------- | ---------------------------------- |
    | `codex.sandbox.image`           | string    | `"risoluto-codex:latest"` | Docker image for worker containers |
    | `codex.sandbox.network`         | string    | `""`                      | Docker network name                |
    | `codex.sandbox.extraMounts`     | string\[] | `[]`                      | Extra bind mounts                  |
    | `codex.sandbox.envPassthrough`  | string\[] | `[]`                      | Env vars forwarded to containers   |
    | `codex.sandbox.egressAllowlist` | string\[] | `[]`                      | Allowed egress domains             |

    **Resources:**

    | Key                                         | Type   | Default  | Description       |
    | ------------------------------------------- | ------ | -------- | ----------------- |
    | `codex.sandbox.resources.memory`            | string | `"4g"`   | Memory limit      |
    | `codex.sandbox.resources.memoryReservation` | string | `"1g"`   | Guaranteed memory |
    | `codex.sandbox.resources.memorySwap`        | string | `"4g"`   | Swap limit        |
    | `codex.sandbox.resources.cpus`              | string | `"2.0"`  | CPU limit         |
    | `codex.sandbox.resources.tmpfsSize`         | string | `"512m"` | tmpfs mount size  |

    **Security:**

    | Key                                       | Type    | Default | Description                        |
    | ----------------------------------------- | ------- | ------- | ---------------------------------- |
    | `codex.sandbox.security.noNewPrivileges`  | boolean | `true`  | `--security-opt=no-new-privileges` |
    | `codex.sandbox.security.dropCapabilities` | boolean | `true`  | Drop all Linux capabilities        |
    | `codex.sandbox.security.gvisor`           | boolean | `false` | Enable gVisor runtime              |
    | `codex.sandbox.security.seccompProfile`   | string  | `""`    | Custom seccomp profile path        |

    **Logging:**

    | Key                          | Type   | Default       | Description             |
    | ---------------------------- | ------ | ------------- | ----------------------- |
    | `codex.sandbox.logs.driver`  | string | `"json-file"` | Docker log driver       |
    | `codex.sandbox.logs.maxSize` | string | `"50m"`       | Max log file size       |
    | `codex.sandbox.logs.maxFile` | number | `3`           | Max number of log files |
  </Accordion>

  <Accordion title="Workspace">
    | Key                      | Type   | Default                    | Description                                                          |
    | ------------------------ | ------ | -------------------------- | -------------------------------------------------------------------- |
    | `workspace.root`         | string | `"../risoluto-workspaces"` | Root directory for issue workspaces                                  |
    | `workspace.strategy`     | enum   | `"directory"`              | Isolation strategy: `directory` (clone) or `worktree` (git worktree) |
    | `workspace.branchPrefix` | string | `"risoluto/"`              | Branch name prefix for agent branches                                |

    **Hooks:**

    | Key                            | Type   | Default | Description                            |
    | ------------------------------ | ------ | ------- | -------------------------------------- |
    | `workspace.hooks.afterCreate`  | string | `null`  | Shell command after workspace creation |
    | `workspace.hooks.beforeRun`    | string | `null`  | Shell command before each agent run    |
    | `workspace.hooks.afterRun`     | string | `null`  | Shell command after each agent run     |
    | `workspace.hooks.beforeRemove` | string | `null`  | Shell command before workspace removal |
    | `workspace.hooks.timeoutMs`    | number | `60000` | Hook execution timeout (1 min)         |
  </Accordion>

  <Accordion title="Notifications">
    Risoluto supports two notification config shapes. The single-Slack shape is the legacy path; the `channels` array is the current multi-channel path and is what you should use for anything beyond a single Slack webhook.

    **Legacy single-Slack:**

    | Key                              | Type   | Default      | Description                  |
    | -------------------------------- | ------ | ------------ | ---------------------------- |
    | `notifications.slack.webhookUrl` | string | —            | Slack incoming webhook URL   |
    | `notifications.slack.verbosity`  | enum   | `"critical"` | `off`, `critical`, `verbose` |

    **Multi-channel array:**

    | Key                                    | Type    | Default  | Description                                                                                |
    | -------------------------------------- | ------- | -------- | ------------------------------------------------------------------------------------------ |
    | `notifications.channels[]`             | array   | `[]`     | Delivery channels, evaluated in order                                                      |
    | `notifications.channels[].name`        | string  | —        | Unique channel label used in notification metadata                                         |
    | `notifications.channels[].enabled`     | boolean | `true`   | Disable without removing the entry                                                         |
    | `notifications.channels[].minSeverity` | enum    | `"info"` | `info`, `warning`, `critical` — only notifications at or above this severity are delivered |
    | `notifications.channels[].type`        | enum    | —        | `slack`, `webhook`, or `desktop`                                                           |

    **Slack channel** (`type: slack`):

    | Key          | Type   | Default      | Description                                                     |
    | ------------ | ------ | ------------ | --------------------------------------------------------------- |
    | `webhookUrl` | string | —            | Slack incoming webhook URL (prefer `$SECRET:SLACK_WEBHOOK_URL`) |
    | `verbosity`  | enum   | `"critical"` | `off`, `critical`, `verbose`                                    |

    **Webhook channel** (`type: webhook`):

    | Key       | Type   | Default | Description                                           |
    | --------- | ------ | ------- | ----------------------------------------------------- |
    | `url`     | string | —       | HTTPS endpoint to POST notification payloads to       |
    | `headers` | object | `{}`    | Extra HTTP headers (e.g. `Authorization: Bearer ...`) |

    **Desktop channel** (`type: desktop`): no extra fields — surfaces notifications in the dashboard notification tray only.

    ```yaml theme={null}
    notifications:
      channels:
        - name: ops-slack
          type: slack
          minSeverity: warning
          webhookUrl: $SECRET:SLACK_WEBHOOK_URL
        - name: oncall-pager
          type: webhook
          minSeverity: critical
          url: https://events.example.com/risoluto
          headers:
            Authorization: Bearer $SECRET:PAGER_TOKEN
        - name: dashboard-only
          type: desktop
    ```

    See the [Notifications guide](/guides/notifications) for event types and severity levels.
  </Accordion>

  <Accordion title="Webhook (Linear Inbound)">
    | Key                             | Type   | Default  | Description                                    |
    | ------------------------------- | ------ | -------- | ---------------------------------------------- |
    | `webhook.webhookUrl`            | string | —        | HTTPS-only webhook URL (enables webhook mode)  |
    | `webhook.webhookSecret`         | string | —        | HMAC signing secret for verification           |
    | `webhook.pollingBaseMs`         | number | `15000`  | Polling interval when webhook is healthy       |
    | `webhook.pollingStretchMs`      | number | `120000` | Polling interval stretched when webhook active |
    | `webhook.healthCheckIntervalMs` | number | `300000` | Health check interval (5 min)                  |
  </Accordion>

  <Accordion title="GitHub Integration">
    | Key                 | Type   | Default                    | Description                    |
    | ------------------- | ------ | -------------------------- | ------------------------------ |
    | `github.token`      | string | —                          | GitHub Personal Access Token   |
    | `github.apiBaseUrl` | string | `"https://api.github.com"` | GitHub API endpoint (for GHES) |
  </Accordion>

  <Accordion title="Multi-repo routing">
    Route issues to different repositories based on tracker metadata. Each entry in `repos` is a destination that the orchestrator matches against the incoming issue before launching a worker. See the [Multi-repo recipe](/recipes/multi-repo) for a worked example.

    | Key                        | Type           | Default  | Description                                                           |
    | -------------------------- | -------------- | -------- | --------------------------------------------------------------------- |
    | `repos[]`                  | array          | `[]`     | Repository routing entries                                            |
    | `repos[].repoUrl`          | string         | —        | Git URL used to clone or fetch the workspace                          |
    | `repos[].defaultBranch`    | string         | `"main"` | Branch agents branch from                                             |
    | `repos[].identifierPrefix` | string \| null | `null`   | Match issues whose identifier starts with this prefix (e.g. `"ENG-"`) |
    | `repos[].label`            | string \| null | `null`   | Match issues that carry this label                                    |
    | `repos[].githubOwner`      | string \| null | `null`   | GitHub owner used when the orchestrator opens the PR                  |
    | `repos[].githubRepo`       | string \| null | `null`   | GitHub repo name used for the PR                                      |
    | `repos[].githubTokenEnv`   | string \| null | `null`   | Name of the env var holding the PR-creation token for this repo       |
  </Accordion>

  <Accordion title="Merge policy">
    Controls the opt-in auto-merge engine. Defaults to disabled, so no PR is auto-merged until you turn `enabled` on.

    | Key                           | Type           | Default | Description                                                                               |
    | ----------------------------- | -------------- | ------- | ----------------------------------------------------------------------------------------- |
    | `mergePolicy.enabled`         | boolean        | `false` | Master switch. When `false`, Risoluto never requests an auto-merge                        |
    | `mergePolicy.allowedPaths`    | string\[]      | `[]`    | Path prefixes; when non-empty, every changed file in the PR must match at least one entry |
    | `mergePolicy.maxChangedFiles` | number \| null | `null`  | Maximum number of changed files. `null` disables the cap                                  |
    | `mergePolicy.maxDiffLines`    | number \| null | `null`  | Maximum combined additions + deletions. `null` disables the cap                           |
    | `mergePolicy.requireLabels`   | string\[]      | `[]`    | Labels that must all be present on the PR before auto-merge is attempted                  |
    | `mergePolicy.excludeLabels`   | string\[]      | `[]`    | Labels that block auto-merge when any are present                                         |
  </Accordion>

  <Accordion title="State machine (custom stages)">
    Override the default issue lifecycle stages and transitions. Only needed if your tracker uses non-standard states or you want to gate specific transitions.

    | Key                          | Type   | Default | Description                                               |
    | ---------------------------- | ------ | ------- | --------------------------------------------------------- |
    | `stateMachine.stages[]`      | array  | `[]`    | Ordered stage list                                        |
    | `stateMachine.stages[].name` | string | —       | Stage label that must match a tracker state name          |
    | `stateMachine.stages[].kind` | enum   | —       | One of `backlog`, `todo`, `active`, `gate`, `terminal`    |
    | `stateMachine.transitions`   | object | `{}`    | Map of stage name to an array of allowed next stage names |

    ```yaml theme={null}
    stateMachine:
      stages:
        - { name: Backlog, kind: backlog }
        - { name: Ready, kind: todo }
        - { name: In Progress, kind: active }
        - { name: Review, kind: gate }
        - { name: Done, kind: terminal }
      transitions:
        Ready: [In Progress]
        In Progress: [Review, Ready]
        Review: [Done, In Progress]
    ```
  </Accordion>
</AccordionGroup>

## Secrets Management

Secrets are stored in an AES-256-GCM encrypted store, separate from config:

<CodeGroup>
  ```bash Store a secret theme={null}
  curl -s -X POST http://127.0.0.1:4000/api/v1/secrets/SLACK_WEBHOOK_URL \
    -H 'Content-Type: application/json' \
    -d '{"value":"https://hooks.slack.com/services/..."}'
  ```

  ```bash List secrets theme={null}
  curl -s http://127.0.0.1:4000/api/v1/secrets
  ```

  ```bash Delete a secret theme={null}
  curl -s -X DELETE http://127.0.0.1:4000/api/v1/secrets/SLACK_WEBHOOK_URL
  ```
</CodeGroup>

<Tip>
  Keep secrets out of committed files. Use `$SECRET:name` references in config or environment variable expansion like `$LINEAR_API_KEY`.
</Tip>

## Environment Variables

| Variable                 | Required | Default       | Description                                                                                                          |
| ------------------------ | -------- | ------------- | -------------------------------------------------------------------------------------------------------------------- |
| `LINEAR_API_KEY`         | Yes      | —             | Linear API key                                                                                                       |
| `LINEAR_PROJECT_SLUG`    | Yes      | —             | Linear project to watch                                                                                              |
| `MASTER_KEY`             | Yes      | —             | Encryption key for secrets store                                                                                     |
| `OPENAI_API_KEY`         | No       | —             | OpenAI API key (alternative to Codex auth)                                                                           |
| `GITHUB_TOKEN`           | No       | —             | GitHub PAT for PR creation                                                                                           |
| `DATA_DIR`               | No       | `~/.risoluto` | Root directory for archives and config                                                                               |
| `RISOLUTO_BIND`          | No       | `127.0.0.1`   | HTTP bind address                                                                                                    |
| `RISOLUTO_WRITE_TOKEN`   | No       | —             | Bearer token for remote API writes                                                                                   |
| `RISOLUTO_READ_TOKEN`    | No       | —             | Bearer token required for remote reads when `RISOLUTO_BIND` is non-loopback. Unset when binding to `127.0.0.1`/`::1` |
| `LOG_LEVEL`              | No       | `info`        | Log verbosity                                                                                                        |
| `DISPATCH_MODE`          | No       | `local`       | `local` or `remote` dispatch mode                                                                                    |
| `DISPATCH_URL`           | No       | —             | Data plane URL (when `DISPATCH_MODE=remote`)                                                                         |
| `DISPATCH_SHARED_SECRET` | No       | —             | Shared secret for control/data plane auth                                                                            |

## What's Next

<CardGroup cols={2}>
  <Card title="Dashboard" icon="chart-mixed" href="/guides/dashboard">
    Explore the real-time dashboard views.
  </Card>

  <Card title="Security" icon="shield-halved" href="/guides/security">
    Harden your deployment with network controls and sandbox policies.
  </Card>
</CardGroup>
