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

# Configuration

> The complete config.yaml reference

The worker reads its declarative configuration from `config.yaml` inside the [profile](/worker/auth)'s own
state tree — `~/.blobhub-worker/profiles/{profile}/config.yaml`, so each profile watches its own sessions.
`blobhub-worker whoami` prints the tree a given invocation resolves to; for what is running on this
machine and what it is doing, see [`instance ls`/`show`](/worker/cli/instance) instead. This page is the
complete reference for that file: the `polling`, `concurrency`, and `agents` blocks that tune the
worker globally, and the `sections` list that declares the work it runs. Edits take effect on the
next `blobhub-worker start` — there is no hot reload in v1.

The BlobHub API base URL belongs to the [profile](/worker/auth), not to `config.yaml`. It is stored alongside
the key in `~/.blobhub/credentials.yaml` (written by [`blobhub-worker login`](/worker/cli/login)) and can be
overridden per run with `--api-url` or `BLOBHUB_API_URL`. To point a worker at a different endpoint, log in
under a second profile name and run `start --profile <name>` — that gives the other endpoint its own
configuration and cursors rather than reusing this one's.

## Top-level shape

```yaml theme={null}
polling:
  interval_idle_ms: 1500
  interval_active_ms: 3000
  backoff_max_ms: 30000

concurrency:
  max_agents: 4

agents:
  claude_code:
    executable: claude            # default: "claude" for claude_code, "codex" for codex
    model: claude-sonnet-4-6       # harness-specific string; default unset
    effort: high                  # low|medium|high (portable) or a passthrough value; default unset
    permissions: approval         # autonomous|approval|guarded; default approval

policy:
  allow_autonomous: true         # default; the operator's ceiling on `permissions: autonomous`
  rules: []                      # evaluated BEFORE any thread policy; see "policy" below

sections:
  - name: backend-feature         # optional label
    job_type: session_agent_harness
    session:
      org_id: org_01J...
      blob_id: blb_01J...
      revision_id: rev_01J...
      session_id: ses_01J...        # a uuid, OR a session alias (resolved once at attach)
      session_prefix: ""            # optional; "" (default) = the whole session
```

Every top-level block is optional except `sections`. Defaults are shown above.

## `polling`

Adaptive cadence for any per-section poll loop that needs one.

| Key                  | Type | Default | Description                                          |
| -------------------- | ---- | ------- | ---------------------------------------------------- |
| `interval_idle_ms`   | int  | 1500    | Sleep between polls when the section is idle.        |
| `interval_active_ms` | int  | 3000    | Sleep between polls while the section is doing work. |
| `backoff_max_ms`     | int  | 30000   | Upper bound on exponential backoff after API errors. |

## `concurrency`

| Key          | Type | Default | Description                                                                                                                          |
| ------------ | ---- | ------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `max_agents` | int  | 4       | Maximum number of agent processes the worker runs simultaneously across all sections. Job types that don't spawn agents ignore this. |

## `agents`

A map of `agent_type` → per-agent settings. Job types that spawn coding agents (currently
[`session_agent_harness`](/worker/session-agent-harness/overview)) read this block to decide how to
launch the agent inside each thread's `work_folder`. Supported `agent_type` values for v1 are
`claude_code` and `codex`. (A `test_double` adapter exists for internal testing and is not
user-visible.)

```yaml theme={null}
agents:
  claude_code:
    executable: claude
    model: claude-sonnet-4-6
    effort: high
    permissions: approval
  codex:
    executable: codex
```

| Key           | Type   | Default                                             | Description                                                                                                                                                                                                                                                                                                                                                                         |
| ------------- | ------ | --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `executable`  | string | `claude` (for `claude_code`); `codex` (for `codex`) | Command name to resolve on `PATH`.                                                                                                                                                                                                                                                                                                                                                  |
| `model`       | string | unset                                               | Harness-specific model identifier passed to the agent. Leave unset to use the agent's own default.                                                                                                                                                                                                                                                                                  |
| `effort`      | string | unset                                               | Reasoning effort. Portable values `low`, `medium`, `high`, or any harness-specific passthrough string. Unset = agent default.                                                                                                                                                                                                                                                       |
| `permissions` | string | `approval`                                          | `autonomous` runs without an approval gate; `approval` routes every sensitive action to the interactive prompt bridge (a human replies from the web UI), denying it if no human is reachable; `guarded` routes sensitive actions to a rule set first (see [`policy`](#policy) below), falling back to that same human prompt only when a rule — or the absence of one — says `ask`. |

These settings are the **middle tier** of a three-tier, per-field resolution: a thread's own
`agent.*` block (set on the thread session object) overrides the matching `agents.<type>` key here,
which in turn overrides the codebase default. Only `permissions` has a non-empty codebase default
(`approval`); `executable` falls back to `claude`/`codex` by agent type. For the full precedence
rules and the per-thread overrides, see
[Job Session Object](/worker/session-agent-harness/thread-object).

## `policy`

The operator's rule set and ceiling for any thread running with `permissions: guarded` (see the `agents`
table above). This block is optional; omitting it is equivalent to `allow_autonomous: true` with no rules
— today's behavior for every existing deployment.

```yaml theme={null}
policy:
  allow_autonomous: true          # default; false forbids `permissions: autonomous` worker-wide
  rules:                          # evaluated BEFORE any thread policy, in order
    - id: confine_writes
      effect: write
      outside: work_folder
      action: deny
    - id: never_force_push
      effect: exec
      command: ["git push --force*", "git push -f*"]
      action: deny
```

| Key                | Type | Default | Description                                                                                                                                                                                                                                                                                                          |
| ------------------ | ---- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `allow_autonomous` | bool | `true`  | Ceiling on `permissions: autonomous`. When `false`, a thread requesting `autonomous` fails activation with `POLICY_AUTONOMOUS_FORBIDDEN` instead of silently running under a weaker mode.                                                                                                                            |
| `rules`            | list | `[]`    | Rules evaluated against every intercepted tool call on a `guarded` thread, **before** that thread's own `policy.rules` (set on the thread session object; see [Job Session Object](/worker/session-agent-harness/thread-object)). A machine owner's `deny` here can never be relaxed by anything a session supplies. |

### Rule shape

Each entry in `rules` — both here and in a thread's own `policy` block — has the same shape:

| Field     | Type   | Meaning                                                                                                                                    |
| --------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `id`      | string | Identifies the rule in `policy_applied` / `policy_denied` audit items and in activation warnings.                                          |
| `effect`  | string | `write` \| `exec` \| `read` \| `network` — the portable action a rule matches. See the per-harness mapping below.                          |
| `outside` | string | `work_folder` matches a call whose resolved path leaves the run's workspace. Leave unset to match regardless of path.                      |
| `command` | list   | Glob patterns (`fnmatchcase` — case-sensitive) matched against the command line. `exec` only; ignored for other effects.                   |
| `action`  | string | `allow` \| `deny` \| `ask`. `ask` routes to the same human prompt bridge `permissions: approval` uses; with no human reachable, it denies. |

Rules are evaluated **first match wins**, in this order: this section's `rules`, then the thread's own
`policy.rules`, falling through to the thread policy's `default` (`allow` | `ask` | `deny`) when nothing
matches. The worker config carries rules only — `default` belongs to the thread's own policy, because a
machine-wide default would be a ceiling on nothing in particular.

`effect` is a portable vocabulary, but the two supported harnesses do not see the same calls — a rule
written against `write` + `outside: work_folder` behaves differently on `claude_code` than on `codex`, and
a policy is a guardrail against mistakes, not a sandbox. Before writing rules for a sensitive workspace,
read [Job Session Object → Policy and guarded permissions](/worker/session-agent-harness/thread-object) for
the full evaluation model, the per-harness capability table, and what a policy cannot enforce.

## `sections`

A list. Each entry is a unit of work the worker runs. Every section has these generic fields:

| Key        | Type   | Required | Description                                                                                       |
| ---------- | ------ | -------- | ------------------------------------------------------------------------------------------------- |
| `name`     | string | No       | Human-readable label shown in the dashboard. The default depends on the job type.                 |
| `job_type` | string | Yes      | The [job type](/worker/job-types) that handles this section. Must be one of the supported values. |

Everything else under a section is determined by its `job_type`. v1 ships one job type,
`session_agent_harness`, documented below.

### `session_agent_harness` sections

A `session_agent_harness` section attaches the worker to one BlobHub session and drives a coding
agent for each thread session object handed off inside it.

```yaml theme={null}
- name: my-project                     # optional; defaults to session_id[:8]
  job_type: session_agent_harness
  session:
    org_id: org_01J...
    blob_id: blb_01J...
    revision_id: rev_01J...
    session_id: ses_01J...             # a uuid, OR a session alias
    session_prefix: "projects/"        # optional; "" (default) = the whole session
```

| Key        | Type   | Required | Description                                                                                     |
| ---------- | ------ | -------- | ----------------------------------------------------------------------------------------------- |
| `name`     | string | No       | Human-readable label shown in the dashboard. Defaults to `session_id[:8]` when omitted.         |
| `job_type` | string | Yes      | Must be `session_agent_harness`.                                                                |
| `session`  | object | Yes      | Identifies the BlobHub session and, optionally, the slice of its namespace to watch. See below. |

#### `session`

| Key              | Type   | Required | Description                                                                                                                                                                                                                                                                                                                                                                                                                          |
| ---------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `org_id`         | string | Yes      | Organization id.                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `blob_id`        | string | Yes      | Blob id.                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `revision_id`    | string | Yes      | Revision id.                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `session_id`     | string | Yes      | A session **uuid, or a session alias** — the worker resolves either, once, at attach (via `get_session`), and uses the resolved id for every call after that. An alias buys a stable, readable name in `config.yaml` and a session that can be re-created without editing config. A value that resolves to neither a live id nor a live alias refuses the section with `SESSION_NOT_FOUND`.                                          |
| `session_prefix` | string | No       | Scopes this section to one slice of the session's object namespace, e.g. `"projects/"`. Defaults to `""` — the whole session, today's behavior for every existing deployment. When set, it **must end in `/`** and may not contain a `.` or `..` segment; it is matched as a plain string prefix against each object's alias. See [Concepts](/worker/concepts) for what scoping a section this way actually changes about discovery. |

`job_id` — the on-disk state directory under the profile's own `jobs/` — is always derived from the
**resolved** session id, never from the configured spelling: a session re-created under the same alias is a
different session, and its threads must not inherit the old session's local state. See
[Filesystem Layout](/worker/session-agent-harness/filesystem).

Two sections may not name the exact same `(org_id, blob_id, revision_id, session_id, session_prefix)` tuple
(`DUPLICATE_SECTION_TARGET` at load; see [Validation](#validation) below) — but because `session_id` can be
spelled as either an id or an alias, one section naming a session by id and another naming the same session
by alias will not be caught by that static check. The worker repeats the check after resolution: whichever
section's reference resolves second refuses to attach with the same `DUPLICATE_SECTION_TARGET` code, while
the worker keeps serving every other section. See
[Session Agent Harness → Reference](/worker/session-agent-harness/reference) for that section-level form.

## Full working example

A complete `config.yaml` with the global blocks set, a `policy` ceiling, and one
`session_agent_harness` section watching a slice of a session named by alias.

```yaml theme={null}
polling:
  interval_idle_ms: 1500
  interval_active_ms: 3000
  backoff_max_ms: 30000

concurrency:
  max_agents: 2

agents:
  claude_code:
    executable: claude
    model: claude-sonnet-4-6
    effort: high
    permissions: guarded

policy:
  allow_autonomous: true
  rules:
    - id: confine_writes
      effect: write
      outside: work_folder
      action: deny
    - id: never_force_push
      effect: exec
      command: ["git push --force*", "git push -f*"]
      action: deny

sections:
  - name: backend-feature
    job_type: session_agent_harness
    session:
      org_id: org_01J3X4M5N6P7Q8R9S0T1U2V3W4
      blob_id: blb_01J4P5Q6R7S8T9U0V1W2X3Y4Z5
      revision_id: rev_01J7K8L9M0N1P2Q3R4S5T6U7V8
      session_id: work-prod                  # a session alias, resolved once at attach
      session_prefix: "projects/"            # only watch this slice of the session's namespace
```

A minimal single-section example — the same shape with a uuid `session_id`, no `session_prefix`, and
`permissions: approval` — needs nothing beyond the [Top-level shape](#top-level-shape) block above.

Save this file at `~/.blobhub-worker/profiles/default/config.yaml` (or wherever
`blobhub-worker whoami` reports), then run:

```bash theme={null}
blobhub-worker login           # one-time
blobhub-worker start --tui
```

Once running, prepare a **thread session object** inside that session and set
`instance.state = "pending"` to hand it off to the worker — see
[Handoff](/worker/session-agent-harness/thread-handoff).

## Validation

The configuration is validated on every `start`, before any section is dispatched.

| Error                      | Cause                                                                                                                                                |
| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `INVALID_CONFIG`           | The file is missing, unreadable, or not valid YAML — or a section's `session_prefix`, or the top-level `policy` block, fails validation (see below). |
| `UNKNOWN_JOB_TYPE`         | A section's `job_type` is not one of the supported values.                                                                                           |
| `MISSING_SESSION_KEYS`     | A `session_agent_harness` section's `session` block is missing one of the four ids.                                                                  |
| `DUPLICATE_SECTION_TARGET` | Two sections target the same `(org_id, blob_id, revision_id, session_id, session_prefix)` tuple.                                                     |

`session_prefix`, when set, must match `([A-Za-z0-9._@-]+/)+` — one or more path segments each ending in
`/`, with no `.` or `..` segment. The top-level `policy.rules` are checked against the same `effect` /
`action` vocabulary documented under [`policy`](#policy) above; an unknown value fails with
`INVALID_CONFIG` naming the offending rule.

Process-level codes (`INVALID_CONFIG`, `UNKNOWN_JOB_TYPE`, `MISSING_SESSION_KEYS`,
`DUPLICATE_SECTION_TARGET`) and their remediation are catalogued on
[Reference](/worker/reference). Section- and thread-level codes for `session_agent_harness` —
including `POLICY_MISSING`, `POLICY_INVALID`, `POLICY_AUTONOMOUS_FORBIDDEN`, and the section-level,
post-resolution form of `DUPLICATE_SECTION_TARGET` — are on the
[Session Agent Harness reference](/worker/session-agent-harness/reference).

## See also

* [`blobhub-worker start`](/worker/cli/start)
* [Reference](/worker/reference)
* [Job Session Object](/worker/session-agent-harness/thread-object)
* [Job types](/worker/job-types)
