<!--
Sitemap:
- [Installation](/installation)
- [Upgrading](/upgrading): Version-specific steps for upgrading an existing Bento install.
- [Concepts](/concepts)
- [Build your first pipeline](/tutorials/pipeline-args)
- [Target a specific issue or PR from a URL](/tutorials/url-targeting)
- [Keep state across runs](/tutorials/pipeline-state)
- [Fire a pipeline on a schedule or on demand](/tutorials/schedule-and-fire)
- [Deploy a box to Railway](/tutorials/deploy-to-railway)
- [Operate a hosted daemon](/tutorials/operate-a-hosted-daemon)
- [Configuration](/configuration)
- [Members](/members)
- [Knowledge base](/knowledge-base/)
- [Method and delivery](/knowledge-base/modes)
- [Config](/knowledge-base/config)
- [MCP](/knowledge-base/mcp)
- [Pipeline configuration reference](/pipelines/config)
- [Filters](/pipelines/filters)
- [Triggers](/triggers/)
- [GitHub trigger](/triggers/github)
- [Linear trigger](/triggers/linear)
- [Webhook trigger](/triggers/webhook)
- [Schedule trigger](/triggers/schedule)
- [Manual trigger](/triggers/manual)
- [Traces](/pipelines/traces)
- [Slack](/integrations/slack)
- [Public access](/public-access)
- [Context engineering](/context-engineering)
- [Best practices](/best-practices)
- [Troubleshooting](/troubleshooting)
- [Architecture](/architecture/vision)
- [Workspaces](/workspaces)
- [Authentication](/authentication)
- [Identity](/identity)
- [Security](/security)
- [References](/references)
- [Changelog](/changelog): Bento release history.
- [CLI reference](/cli/)
- [Setup](/cli/setup)
- [Secrets](/cli/secrets)
- [Lifecycle](/cli/lifecycle)
- [Sandbox image](/cli/image)
- [Sandboxes](/cli/sandbox)
- [Observability](/cli/observability)
- [Diagnostics](/cli/diagnostics)
- [Triggers](/cli/triggers)
- [Workbench](/cli/workbench)
- [Auth](/cli/auth)
- [Knowledge](/cli/knowledge)
- [Evals](/cli/evals)
- [Bento](/index)
- [Runtime wrapper](/architecture/runtime-wrapper)
- [Skill evolve](/architecture/skill-evolve)
-->

# Upgrading

Most releases need no action beyond the standard upgrade:

1. `bento update` — replaces the binary (the running daemon stays on its current version).
2. Apply any configuration migration listed for your target version below.
3. `bento doctor` — resolve any configuration errors.
4. `bento daemon restart` — then confirm `bento daemon status` is reachable.

See [CLI setup](/cli/setup) for the full command reference.

The following sections list releases that require a manual change, newest first. A version not listed needs no action beyond the standard upgrade.

## 0.12.14

**The `fast` queue lane is removed.** Remove `queue.fast` from `.bento/daemon.yaml`, and remove `lane` and `priority` from every `.bento/pipelines/*.yaml`. Validation rejects all three fields. At startup, the daemon moves any pending `fast` jobs to `host` capacity and resets stored job priorities; it preserves job IDs, payloads, queue keys, attempts, and ready times. Historical traces keep their original lane labels. `host.concurrency` now limits every local backend, including just-bash, and `remote.concurrency` limits Daytona and Cloudflare runs.

## 0.12.12

This release pins Cloudflare bridge 1.3.0. Run `bento sandbox update --provider cloudflare` after `bento update`.

A pipeline `env:` grant, or credentials the daemon injects into a setup step (for example `GH_TOKEN`), now travels in the sandbox exec request instead of a command script written to the sandbox's disk. A daemon that still points at a bridge older than 1.3.0 refuses any command carrying such a grant, with the message "lacks exec\_env; update the managed bridge", instead of running it. Update the bridge before or together with the daemon.

## 0.12.11

This release pins Cloudflare bridge 1.2.0. Run `bento sandbox update --provider cloudflare` after `bento update`.

The update writes the complete variable set to the Worker. It removes a variable you set by hand on the Worker. Set the variable again after the update, if you need it. The update also renames the variable `BENTO_INSTANCE_NAME` to `BENTO_DAEMON_NAME`.

A snapshot that sets `instanceType` or `max_concurrent` needs bridge 1.2.0 or later. The daemon refuses to build it on an older bridge, with the message "update the managed bridge". A snapshot with neither setting builds on any bridge version.

`bento sandbox status --provider cloudflare` and `bento doctor` now report a bridge registration that this daemon does not track. An earlier database, or an incomplete removal, can leave one behind. Remove it with `bento sandbox snapshot prune --provider cloudflare --orphans`.

Do not point two daemons with the same `name` and different databases at one bridge. Each daemon then reports the other daemon's registrations as orphans, and prune removes the idle ones.

## 0.12.8

Pipeline `env` commands now share the snapshot resolver’s 60-second timeout. Commands that exceed it fail resolution: required entries stop the invocation, and optional entries are withheld.

Rename `repos.<name>.sandbox.snapshot.build_env` to `env`, and replace the snapshot CLI option `--build-env` with `--env`. Existing variable-name lists keep the same meaning. `env` also accepts pipeline-style `command` and `secret` entries. The old names are rejected. Recreate manually registered snapshots that stored `buildEnv` in their build options; repository-managed declarations enqueue replacements after the configuration changes.

## 0.12.2

A `secret_ref` value is a full secret name, `<kind>/<leaf>`. Change `secret_ref: github` to `secret_ref: webhook/github` on each webhook source. Validation fails with a message that names the full form until you change it. `bento setup webhook <name>` stores `webhook/<name>`, and `bento secrets list` shows every stored name.

A secret name is lower-case letters and digits, joined by single hyphens, in each segment. A webhook secret that you stored under a name with `_` or upper case stays in the table, and you can list and delete it, but no `secret_ref` can name it. `bento doctor` lists each such name. Delete it with `bento secrets rm <name>`, then run `bento setup webhook <new-name>` with a name that matches the grammar, and point `secret_ref` at `webhook/<new-name>`.

`secrets.source` in `daemon.yaml` is `secrets.instance`. The values `postgres` and `env` do not change.

## 0.12.1

`bento daemon reload` and SIGHUP configuration reload are removed. Replace reload commands with `bento config validate && bento daemon restart`. All configuration changes require a restart, including pipelines and lifecycle hooks. The process uses one configuration snapshot from startup. The daemon ignores SIGHUP so old reload commands do not terminate it.

`bento queue status` shows only active, waiting, and delayed work by default. Add `--verbose` to scripts that need failed counts or rows. This flag selects failed history; it does not restore the job-ID listing removed in 0.10.0. Use `--since`, `--limit`, and `--offset` to filter that history. See [queue status](/cli/observability#bento-queue-status).

This release changes only an install with a `linear` pipeline principal. An install without one needs no action.

Linear credentials now live encrypted in Postgres, not in a local file. The daemon reads them through a credential key file instead of `linear.client_id`/`linear.client_secret` in `daemon.yaml`.

1. Generate the credential key, then keep a secure backup of the printed path.

   ```sh
   bento secrets key --generate
   ```

2. Set `LINEAR_CLIENT_ID` and `LINEAR_CLIENT_SECRET` as environment variables, then re-run setup. This moves the Linear credentials into Postgres.

   ```sh
   bento setup linear
   ```

3. Remove `linear.client_id` and `linear.client_secret` from `.bento/daemon.yaml`. The daemon no longer reads them at boot.

`bento setup rotate-credential-key` is removed. Use `bento secrets key --rotate` instead.

## 0.11.0

**An authentication failure disables a credential instead of cooling it.** A 401, a 403, a revoked token, or an expired one no longer parks the credential on a cooldown. Bento marks it dead: it leaves every queue, never earns a probe, and stays dead across restarts. None of the 0.10.0 self-clearing applies to a dead credential, because the throttled probe covers the cooling lane only. Watch the daemon log for `credential dead` after this upgrade — on an install with one credential per runtime, a dead mark stops dispatch for that runtime until an operator acts.

To revive one, replace the secret, delete its row from `credential_cooldowns`, and restart the daemon. Left alone, Bento drops the row on the first restart more than 30 days after the failure.

This release adds a `dead_at` column to `credential_cooldowns`. The migration runs at startup and needs no action.

**The `agents.<name>.watch` field is removed.** An agent no longer polls Linear for work assigned to it. A pipeline gets Linear work through `trigger.linear` on an agent session event. A `daemon.yaml` that still sets `agents.<name>.watch` fails to load. Delete the block.

## 0.10.1

This release changes only an install that sets `knowledge.retrieval.engine: chroma`. An install on the default `qmd` engine needs no action.

1. **Install `uv`.** The Chroma embedder now runs on the host through `uvx --from chromadb==1.5.9`, in place of the bundled `@chroma-core/default-embed` package. Without `uv` on the PATH of the daemon, the index build fails with `cannot start the Chroma host embedder; install uv so uvx is on PATH`. Check the PATH the service runs with, not only the PATH of your shell.

   ```sh
   curl -LsSf https://astral.sh/uv/install.sh | sh
   uvx --version
   ```

2. **Start Chroma from the service stack.** `services.yml` now defines a `chroma` service on port 8000, in place of a hand-started `npx chroma run`. Chroma data lives in `./.resources/chroma`, which `docker compose down -v` does not delete.

   ```sh
   curl -fsSL https://install.getbento.sh/services.yml -o docker-compose.yml
   docker compose up -d
   ```

## 0.10.0

This release replaces the pipeline output surface, moves notes into the database, and adds three floors that stop the daemon booting. Work through the steps in order: step 1 happens before you upgrade, steps 2 through 8 are configuration the daemon hard-errors on, and steps 9 through 11 run after the daemon is back.

### Before you upgrade

1. **Check three floors.** Each one refuses to boot rather than degrading.

   * **Postgres 15 or later.** A migration uses `NULLS NOT DISTINCT`, which earlier versions reject with a syntax error. The bundled stack is `postgres:17-alpine`, so only a self-managed database needs attention.

   * **QMD 2.8.3 or later.** A missing or older `qmd` was a warning that disabled retrieval; it is now fatal whenever Bento discovers knowledge sources. Check the PATH the service runs with, not only your shell's.

     ```sh
     npm install -g @tobilu/qmd@latest
     qmd doctor   # must exit 0
     ```

   * **A broker `scope` claim is a string.** A `scope` minted as an array, number, or object previously coerced to empty and authenticated as a scope-less principal. It is now refused, so every request from that broker fails at the middleware. Mint `scope` space-delimited: `"workloads:read workloads:write"`.

### Configuration migrations

2. **`output:` was replaced by `result:` and `outputs:`.** A pipeline declares the shape its agent returns under `result.schema`, then binds each publication to a named handler under `outputs.<key>`. A leftover `output:` fails the load with `pipelines/<name>.yaml: "output" is removed; migrate to "result" and "outputs"`. A `defaults.output` block in `daemon.yaml` is dropped from the merge instead, so post-back stops silently — remove it too.

   ```yaml
   # before
   output:
     github: comment

   # after
   result:
     schema:
       type: object
       properties:
         comment: { $ref: 'bento://schemas/github/comment/v1' }
       required: [comment]
   outputs:
     comment:
       handler: github.comment
   ```

   The property named `<key>` must `$ref` exactly the handler's schema id, and `outputs:` without `result.schema` is refused. Map each old spelling to its handler:

   | Before | `handler:` | `$ref` |
   | --- | --- | --- |
   | `output.github: comment` | `github.comment` | `bento://schemas/github/comment/v1` |
   | `output.github: review` | `github.review` | `bento://schemas/github/review/v1` |
   | `output.github: review_comment_reply` | `github.review-comment-reply` | `bento://schemas/github/review-comment-reply/v1` |
   | `output.github: review_replies` | `github.review-replies` | `bento://schemas/github/review-replies/v1` |
   | `output.slack: { bot, channel }` | `slack.message` | `bento://schemas/slack/message/v1` |

   A Slack output carries its addressing under `outputs.<key>.config: { bot, channel }`, where `bot` names an entry in `channels.slack.bots`. The GitHub handlers reject a `config` block.

   Four old spellings have no replacement. `output.github: upsert_comment` and `output.diff` are removed outright. `output.github: { mode: orchestrated, allowed: [...] }` is removed because an orchestrated run now constrains its publications through `result.schema`. `output.linear` is removed — an agent that writes to Linear does so through its own tools.

3. **`ask.targets` is required.** Knowledge retrieval validates its route at startup, so a `daemon.yaml` with no `ask:` block no longer boots. Set an ordered route, and replace the scalar `ask.target` with a one-name list. `ask.runtime` and `ask.model` are refused the same way.

   ```yaml
   # before
   ask:
     target: claude-high

   # after
   ask:
     targets: [claude-haiku-low, codex-low]
   ```

4. **`knowledge.retrieval.rewrite.target` became `targets`.** The rewrite step routes across a pool rather than a single target, so a leftover `target` fails as an unknown key. Replace it with an ordered list.

5. **A circuit-breaker Slack escalation needs a full address.** `queue.circuit_breaker.escalate.slack` no longer accepts a bare channel name, because a bot name alone cannot address a message.

   ```yaml
   # before
   escalate:
     slack: eng-bento

   # after
   escalate:
     slack: { bot: <channels.slack.bots entry>, channel: <slack channel id> }
   ```

6. **A pipeline `env:` manifest cannot declare a `BENTO_*` name.** The daemon sets those variables about the run itself and injects them after the manifest, so such an entry was always inert. It now fails the load with `env "<NAME>" is reserved`. The rule covers the whole prefix. Run `bento config validate` to list every offending file.

7. **Skill frontmatter `version:` moved under `metadata:`.** Move the SemVer value to `metadata.version` in every `SKILL.md` that declares one. A definition that keeps the top-level key is rejected with a message naming the replacement, and `bento doctor` lists every file to change.

8. **A token reaches only the workloads it created.** `workloads:read` and `workloads:write` no longer cross the owner boundary, so a token that reads or cancels a workload another token created now receives the response an absent identifier receives. Grant `workloads:admin` to any token that needs cross-owner reach, or converge on a single token. `admin` does not cross the boundary — it is daemon lifecycle authority. The `*` wildcard holds both, so a local operator and a token from `bento token issue` are unaffected.

### After the daemon is back

9. **Import existing notes.** Notes moved from `notes.jsonl` to the database, and the migration does not carry the history across. The import is idempotent and leaves the source files in place.

   ```sh
   cd services/daemon
   bun scripts/import-notes.ts --dry-run
   bun scripts/import-notes.ts
   ```

   Skip it and every thread's prior-state slot comes back empty, so agents lose their accumulated context. Any custom skill that tells an agent to write `/workspace/notes.jsonl` must use the `submit_note` tool instead.

10. **Rebuild and republish the sandbox images.** The agent CLI versions moved from `latest` to hard pins, and this release adds the `opencode` and `pi-google` runtimes, which no earlier image carries. Publish `agent:default` first, then run `bento image pull` and `bento doctor` on each daemon host. `bento image pull` covers `agent:default` and `studio:default` only — `agent:mergenet`, `agent:codegraph`, and `agent:tlc` are pulled or built on the host that runs the pipelines requesting them.

11. **Repoint anything that parses CLI output.** Several commands changed shape.

    * `bento doctor --json` and `bento trigger inspect --json` now emit real JSON. Both previously printed the human table despite the flag, so a script parsing that table breaks.
    * `bento trace` no longer resolves a short prefix. After the `trg_` / `run_` / `inv_` / `tsk_` kind prefix, at least six characters are needed; an ambiguous prefix lists candidates and exits 1. It also writes `resolved <input> → <full-id>` to stderr when the two differ.
    * `bento queue status` reports `failed: <recent> in the last <N>h, <total> on the queue` when the count is non-zero, so a field-position read now returns the windowed count rather than the queue total.
    * `bento queue status` always prints a job table after the counts and no longer prints a `completed:` line; the `-v`/`--verbose` flag is removed. On the wire, `GET /queue/status?verbose=1` returns `details.<state>` as job objects (`task`, `lane`, `pipeline`, `trigger`, `workspace`, `changedAt`, `attempts`, `maxAttempts`) rather than id strings.
    * `bento ask --fast` and `bento ask --method` are removed. The local BM25 path is gone, so `bento ask` always needs a reachable daemon, and the configured retrieval engine decides the method. `--target` is now repeatable and builds an ordered failover route.

### Behaviour that changed without an error

**Retrieval defaults to hybrid.** `knowledge.method` is `hybrid` rather than `bm25` unless pinned, so a pipeline that relied on the implicit default now runs lexical and semantic retrieval together. Pin `method: bm25` to keep the previous behaviour. If you set `knowledge.retrieval.engine: chroma`, start a reachable Chroma server before the daemon and retune `minScore` — Chroma scores are not comparable to QMD's.

**A dispatch naming an unknown agent or skill is discarded, not errored.** Such triggers no longer appear under `bento trigger list --status error` and fall on the shorter discarded retention tier. Find them with `bento trigger list --status discarded --code unknown_agent_reference`.

**Credential cooldowns survive a restart.** Restarting the daemon no longer clears a wrongly parked credential. A lifted cap self-clears, because a seeded cooldown still earns a throttled probe. To clear one by hand, delete its row from `credential_cooldowns` and then restart the daemon. The delete alone changes nothing: the pool seeds its in-memory map from that table once at startup and is authoritative from then on.

## 0.9.3

**A setup step refuses a template placeholder.** A `{{ … }}` placeholder in a `setup:` step's `run` reached the shell as literal text. Configuration validation now rejects it, and the daemon does not load a pipeline that declares one.

Replace each placeholder with an environment variable. Every setup step receives `WORKTREE`, `RUN_DIR`, `SURFACE_DIR`, `REPO`, `SHA`, `BRANCH`, `EVENT_NAME`, `EVENT_SOURCE`, and `PAYLOAD_JSON`. Read any other event field from `PAYLOAD_JSON`:

```yaml
# before
setup:
  - run: git fetch origin {{event.repository.default_branch}}

# after
setup:
  - run: git fetch origin "$(echo "$PAYLOAD_JSON" | jq -r .repository.default_branch)"
```

## 0.9.2

**Token scopes are enforced.** An earlier daemon parsed a scope and never read it, so any valid token reached every route. Every route except `/` and `/health` now requires one. A token that lists a scope outside the vocabulary below matches nothing and is refused everywhere.

Migrate `auth.tokens.<name>.scopes` in `.bento/daemon.yaml`. The earlier `pipelines:*` and `agents:*` spellings are not in the vocabulary:

```yaml
# before
auth:
  tokens:
    ci:
      scopes: ["pipelines:*", "agents:*"]

# after
auth:
  tokens:
    ci:
      scopes: ["pipelines:invoke", "triggers:read"]
```

| Scope | Grants |
| --- | --- |
| `workloads:read` | Read workloads, their invocations, and run artifacts |
| `workloads:write` | Create and close workloads, submit and cancel invocations |
| `workloads:admin` | Cross-owner workload access |
| `triggers:read` | List and inspect triggers, read `/trace` lineage |
| `pipelines:invoke` | Fire a pipeline, replay a trigger, dispatch a URL, run a schedule now |
| `knowledge:read` | `POST /ask` and the knowledge listing |
| `repos:read` | The configured repos and their pull requests |
| `evals:write` | List and curate evals, and read eval statistics and cohort comparisons |
| `admin` | Drain, queue status, resume, reconcile, purge, and the doctor probes |
| `*` | Every scope |

Grant `*` to restore the previous reach of a token. A token issued by `bento token issue` already carries `*`, and the loopback client keeps it, so a local install with no `auth.tokens` block needs no change.

**Protocols are now policies.** The concept is unchanged: a reusable behavioral block that bento injects into the system prompt of an agent. Only the names change.

1. **Rename the directory.** `protocols/` becomes `policies/`. The file contents and their `name` / `description` frontmatter are unchanged.

   ```sh
   git mv protocols policies
   ```

2. **Rename the frontmatter key** in every agent `PERSONA.md` and skill `SKILL.md` that declares one. The values are unchanged.

   ```yaml
   # before
   protocols:
     - commit-aware-retry

   # after
   policies:
     - commit-aware-retry
   ```

   A definition that still declares `protocols:` is rejected with a message naming the replacement, and `bento doctor` lists every file to change. The key is not silently ignored, because a definition that loses its behavioral contracts without an error is worse than one that refuses to load.

3. **If you gate on diagnostic codes**, match `unknown_policy_reference`. `unknown_protocol_reference` still exists and is deprecated, so an existing matcher keeps working until a major version removes it.

## 0.9.0

Steps 1 and 2 are config changes the daemon hard-errors on until migrated — apply them to `.bento/daemon.yaml` before restarting. Step 3 changes a JSON shape and a command flag.

1. **Webhook credentials move to one `sources` map.** `webhooks.secret`, `webhooks.linear`, and `webhooks.tokens` were removed. Each inbound source is an entry under `webhooks.sources`, keyed by its route name, carrying the check its requests pass:

   ```yaml
   webhooks:
     sources:
       github:
         verify: github
         secret: ${GITHUB_WEBHOOK_SECRET}
       linear:
         verify: linear
         secret: ${LINEAR_WEBHOOK_SECRET}
       deploy:
         verify: bearer
         secret: ${BENTO_DEPLOY_TOKEN}
   ```

   * `webhooks.secret` becomes a `verify: github` source.
   * `webhooks.linear` becomes a `verify: linear` source.
   * each `webhooks.tokens.<name>` becomes a `verify: bearer` source named `<name>`.

   A source named `<name>` serves `/webhooks/<name>`, and `/events` is an alias for the `github` route. `verify`, not the key, decides which trigger reaches a source: `verify: github` and `verify: linear` match `trigger.github` and `trigger.linear` under any route name, and `verify: bearer` matches `trigger.webhook: [<name>]`. A trigger pointed at a source that verifies a different way is refused at startup.

   A route with no source now returns `401`. A sender that posted to an unverified `/webhooks/<name>` needs a source before it delivers again.

2. **Host tools declare an executable and argv.** `orchestrator.tools` was removed — move the definitions to a top-level `host_tools` block. Inside each tool, `run:` was removed; declare `exec` and `args`:

   ```yaml
   host_tools:
     deploy_status:
       description: Check deploy status for a service
       input:
         service: { type: string, required: true }
       exec: ./scripts/deploy-status.sh
       args: ["--service", "$service"]
   ```

   Each `args` entry becomes exactly one argv element after `$name` substitution, and no shell interprets it. Pass structured input through `stdin` and secrets through `env`. `exec` names an explicit path and references no input.

   Each tool registers as `host_<name>`, not `orchestrator_<name>`. To call one, the token of the caller holds the scope `host:execute:<name>` or `*`; `mcp:write` does not authorize a host tool. The token the daemon issues to a sandboxed agent is refused whatever scopes it carries.

3. **A failed trigger carries a `diagnostic`, not an `error` string.** Repoint anything that reads the old fields. Existing rows are backfilled on first start, with no manual step.

   * `triggers.error` (free text) becomes `triggers.diagnostic` — nullable `{ code, message }`.
   * `TriggerStatus` no longer has a `timeout` value. A timed-out trigger is `status: error` with `diagnostic.code: agent_timeout`.
   * `bento trigger list --status timeout` was removed. Use `bento trigger list --code agent_timeout`, or `bento trigger stats --status error --by code` for the breakdown.

## 0.8.0

Steps 1 and 2 are config changes the daemon hard-errors on until migrated — apply them to `.bento/daemon.yaml` and every `.bento/pipelines/*.yaml` before restarting. Step 3 changes a JSON shape and fails silently instead.

1. **`trigger.manual` was removed.** You fire every pipeline by name, so delete the key. `trigger` is optional as a consequence, and a pipeline that only runs on demand declares no `trigger` block at all. Declared `args:` remain the gate on a fire's inputs. A pipeline still carrying the key fails with `pipelines/<name>.yaml: "trigger.manual" was removed — every pipeline is fireable by name, so delete the key`.

2. **`observability.phoenix` was removed.** Delete the block and drop the `PHOENIX_API_KEY` it referenced. `observability.langfuse` is the tracing sink. A leftover `phoenix:` block fails the load with `.bento/daemon.yaml: observability.phoenix was removed`. The in-process orchestrator loop is no longer traced. Spawned agent runs still reach Langfuse unchanged.

3. **A failed invocation carries a `diagnostic`, not a `failure_reason`.** Repoint anything parsing the old key — it is absent rather than rejected, so a consumer reads `undefined` with no error:

   * `bento trace --json`: `invocations[].failure_reason` becomes `invocations[].diagnostic_code` plus `invocations[].diagnostic_message`.
   * `GET /trace/:id` and `GET /invocations/:id`: `failureReason` (a nullable string) becomes `diagnostic` — nullable `{ code, message }`, optionally carrying `target`, `path`, `field`, `metadata`, and nested `details`.

   Existing rows are backfilled automatically on first start, with no manual step.

If you tracked `develop` between 0.7.1 and this release, two keys that came and went inside that window also hard-fail the load: move `channels.slack.identities` out of `daemon.yaml` into a `.bento/members.yaml` member whose `identities` are `{ github: { handle }, slack: { id: U123 } }`, and change any member's `kind: bot` to `kind: agent`. A 0.7.1 install has neither.

## 0.7.1

**Every pipeline needs a `version`.** A pipeline inherits it from its governing skill when its skill closure resolves to exactly one skill. A pipeline with no skill (agent-only), or with more than one — a governing `skill` plus `companions` — declares `version:` explicitly as a SemVer string. Startup validation hard-fails otherwise: `pipelines/<name>.yaml: no single-skill version to inherit — declare an explicit "version" field`, and a non-SemVer value gives `"version" must be a SemVer version`. Add `version: 1.0.0` to each affected `.bento/pipelines/*.yaml`, and bump it when you change the pipeline — posted comments carry it for traceability.

**`retain_checkout` must be a boolean.** The daemon now rejects a non-boolean value at startup with `pipelines/<name>.yaml: "retain_checkout" must be a boolean`. An earlier release accepted any value without a check.

**`bento trace` now requires a running daemon.** Lineage resolution (trigger → workload → invocations → tasks) moved from a direct Postgres connection to a `GET /trace/:id` call on the daemon. `bento trace` previously worked with only Postgres reachable. It now needs a running daemon, and without one it fails with the standard "daemon not reachable" message. It resolves against whichever daemon `BENTO_DAEMON_URL` points at, local or remote. The top-level keys of `bento trace --json` — `trigger`, `workload`, `invocations`, and `tasks` — are unchanged. A single `artifacts` object keyed by invocation id replaces `agent_logs`, `attempt_logs`, and `sub_tasks`. Each entry mirrors the artifacts manifest and adds the rendered text of each artifact. Any script parsing the old keys needs to read `artifacts` instead.

## 0.7.0

Apply these to `daemon.yaml` and every `.bento/pipelines/*.yaml` before restarting the daemon — config validation hard-errors on the old shapes until migrated.

1. **Orchestrator `strategy` is required. `assess` and `orchestrator: false` are removed.** In each `orchestrator:` block (top-level and per-pipeline):
   * `orchestrator: false` → **remove the field**. Omission now means direct execution (no orchestrator).
   * Any `orchestrator: { … }` object → add `strategy: { type: auto }` (mandatory, `type` is `auto`). This includes `orchestrator: { targets: [...] }` and `orchestrator: {}`.
   * `assess:` → **`guidance:`**.
   * Remove any other keys under `orchestrator` — unknown fields now error.

2. **Skill principal bindings are enforced.** The built-in skills declare required principals: `agentic-review` requires `pull-request-reviewer`, `pr-approved-merge` requires `pull-request-author`. Any pipeline whose `skill:` or `companions:` reference such a skill declares matching bindings, for example `principals: { pull-request-reviewer: github.principal.<login> }` (form `<provider>.principal.<id>`). For direct (non-orchestrated) execution with such a skill, also set `principal: <binding-name>`. An observer or read-only pipeline never selects a `principal`. Remove any `sandbox.mounts` that mount a shared `~/.config/gh` — it is rejected when principals are configured.

3. **MCP tool calls are now scope-gated.** MCP write tools require `mcp:write`, read tools require `mcp:read`, `*` satisfies both. Ensure every MCP client token — `daemon.yaml` `tokens[].scopes` and any OAuth-issued tokens — carries the scope it needs. Under-scoped clients now get `Forbidden`. Loopback and anonymous local clients are unaffected (`*`).

4. **`GET /events` is removed. Use `GET /triggers`.** Repoint any integration calling `/events`, `/events/stats`, `/events/:id`, or `/events/:id/fields` to the `/triggers` equivalents (or `bento trigger list`). List rows omit the payload — fetch `/triggers/:id` for it. The `POST /events` webhook receiver is unchanged.

5. **`workspaces.retentionDays` was split into two tiers.** Rename it to `workspaces.checkoutRetentionDays`, which governs only the regenerable `checkout/` (default 30 days). Run transcripts are kept on the separate `workspaces.transcriptRetentionDays` tier (default 90), and notes are stored in the database beyond workspace retention. `transcriptRetentionDays` is clamped to at least `checkoutRetentionDays`, so a transcript never expires before the checkout it describes. A leftover `retentionDays` fails the load with `.bento/daemon.yaml: workspaces.retentionDays was renamed to workspaces.checkoutRetentionDays`.

## 0.6.5

Pipeline and orchestrator routing fields `runtime`, `model`, `effort`, `credential`, and `fallbacks` were replaced by named **targets**. Define reusable targets under a top-level `targets:` map in `daemon.yaml`, then reference them with `targets: [name]` on the pipeline or orchestrator. Validation hard-errors until migrated.

## 0.6.1

`sandboxes.forwardEnv` and per-pipeline `sandbox.forwardEnv` were removed. Declare any secret a run needs in that pipeline's `env:` manifest. Skill and agent frontmatter `env:` no longer injects on its own.

## 0.5.1

`knowledge.mode` split into `knowledge.method` + `knowledge.delivery`. Migrate:

* `mode: qmd` → `method: hybrid, delivery: prompt`
* `mode: mount` → `method: none, delivery: file`
* `mode: ambient` → `method: none, delivery: search`
* `mode: catalog` → `method: none, delivery: browse`

## 0.5.0

* `prompt:` → `instructions:` in every `.bento/pipelines/*.yaml`. Hard rename, no alias. Validation hard-errors until changed.
* Custom agents only: rename each authored `agents/<name>/SOUL.md` to `PERSONA.md`.

## 0.3.7

The `lifecycle.retrieve` pipeline block was removed. Move retrieval tuning (`topK`, `minScore`, `rewrite.*`) to `knowledge.retrieval` in `daemon.yaml`. A leftover `lifecycle.retrieve` key fails at startup.

## 0.2.4

Pipeline output type `inline_review` → `upsert_comment`. Rename it in any `output:` block that used it. In the current format, `output:` is rejected at startup. Declare the complete structured result under `result:` and route keyed namespaces through `outputs.<key>.handler`; pipelines with no `outputs:` retain opaque stdout, and actor-side provider actions remain in the agent workflow.

## 0.2.3

The `BENTO_MANAGEMENT_TOKEN` env-var fallback for CLI-to-daemon auth was removed. If you relied on it, authenticate with `bento auth setup` instead.
