Skip to content

Migration notes ​

What an operator changes when a release breaks something: one section per such release, newest first, headed by the bare version (## 1.14.0). After the public launch a breaking change is a major — a title with ! after its type — and the title check refuses such a PR until the section for the release it will cut exists; the first breaking PR of a cycle creates it, each later one adds its lines (the rule). Until the launch the 1.x line moves by minors, the next release is pinned in release-please-config.json, no title carries !, and a breaking cleanup writes its section under the pinned version. The changelog's ⚠ BREAKING CHANGES entry, when there is one, says what broke; the section here says what to do about it.

A section says, in this order: what no longer works as it did, what replaces it, and the smallest edit that gets an installation from one to the other — a config key to rename, a command to re-run, a secret to add. Nothing else: history and reasons live in the changelog and the decision records.

1.296.0 ​

  • Ordinary MCP confirmation now uses form elicitation inside supported clients, without opening a browser or signing in again. The exact saved action, current user/credential permissions, organization floor and one-use execution remain enforced. Unsupported clients retain reads, routine permitted writes and already-approved continuations; fresh confirmation returns a named unavailable result.
  • Deploy the state Worker first: it records the consent source in the existing confirmation row. Older approved browser rows remain readable. Browser connection setup and retained web forms keep their existing protections.

1.295.0 ​

  • Remote command writes now honor the same confirmation policy as chat. An action at or above boundary.confirm returns HTTP 409 confirmation_required or an MCP approval continuation instead of executing. The dashboard displays the saved action and submits Confirm or Cancel by id, including token and loopback installations. MCP uses authenticated browser approval, then the original credential resumes by saved id. Model-supplied confirmation flags have no authority. Routine writes can use an authorized boundary.confirm: destructive scope; destructive actions still require consent. Existing local CLI operator and named schedule authority remain bounded by normal authorization. A command declaring fresh consent always requires a supported approval surface.

  • Deploy the state Worker with its existing-table approval routes before the bot that uses them. Missing approval support refuses before execution. Personal credentials keep an immutable verified user and must satisfy both user and credential policy; a broader token cannot override a revoked user right. Legacy personal credentials retain their verified Access subject when no Slack identity exists, so reads continue without reconnecting.

  • Installations extending builtin now default Review to anthropic/claude-opus-5-5, with Standard's effort and harness. Declare the anthropic provider and supply its API key. To keep Review on the installation's existing work model, set agents.review.profile: standard before upgrading, including installations written by an older switchboard init --model <name>. Explicit Review model/profile overrides keep their selections. Configs written by this release's init --model include the Review pin.

1.260.0 ​

  • The readers' router is retired. A door turn that ends without a tool call is re-asked once; a second no-call turn binds general itself with no_decision on the run's operator record, so there is no second classifier model. Remove routing.model, routing.effort, routing.auto and routing.answer from config.yaml; a load that still carries one fails with the migration sentence. The thread-reply gate's model now falls straight back to defaults.models.general.

1.250.0 ​

  • The bot says less by default (routing-and-config item 28). What used to be every message is now the verbose level of a three-step ladder — quiet (the new default: answers, verdicts, refusals, questions, results, the card's progress), verbose (plus every acknowledgement: a follow-up folded into a live run, 📌 Noted for unit …, 🧭 Handed to the plan runner., the routed command's routed: receipt, the workspace and budget notes on the card) and debug (plus the router's reason, route reason: <reason>, in place of the card's routed: <reason> line, and untracked by the ledger). Removed for everyone: the routed card's wrong preset? reply agent:<preset> … footer — a reply with a directive still runs the request on that preset. To hear what you heard before: config set me --verbosity verbose (or config set channel --verbosity verbose for a channel, defaults.verbosity: verbose in config.yaml for the installation); verbosity:debug on one message for the router's reason. The reply that hands a ship ask to the pipeline is bullets now, one per fact; the pipeline's id moved under it at debug.
  • More of the standard mode is quiet: the routed write offer's footer (confirmation required by …) is gone, the config set reply is a sentence in config show's words instead of a JSON dump, and four more messages moved to verbose — the live card's — thinking (35s since last tool) suffix and the running command's code block (at quiet the caption → bash alone), the head a pull request's body was re-rendered at in the 🔀 PR updated note, and the carried-review note after a rebase-only head move (routing-and-config item 28).
  • The pipeline's own thread messages follow the same ladder: at the default a unit thread hears the outcome, the verdict, the findings left below the gate, the declined ones and what to do next; the level in force, the grant, the write-up pointer, the budget split and the 🔁 budget-renewal line are verbose. The board issue and the unit row keep the full report. Set verbosity before the ship ask (config set me --verbosity verbose, or verbosity:verbose on the ask): the level is written on the pipeline at the hand-over and does not change mid-plan.

1.247.0 ​

  • The budget table moved: the coding preset asks 90 minutes (was 45), the coding and fix rounds' floor is 15 (was 10), and the ship preset asks 240 (was 120) (harness-pi item 15, agent-ship item 8). The fit a deployment's ship block must hold rose with it — ship.maxMinutes needs at least 163 at maxRounds: 3 and 189 at maxRounds: 4 (was 108 and 129) — so a config.yaml whose ship.maxMinutes sits between the old sum and the new one loaded before this release and fails the load after it, naming both numbers (ship.maxMinutes 180 cannot hold the loop ship.maxRounds 4 allows — 189 minutes are needed …). The edit: raise ship.maxMinutes to the registry's 240, or lower ship.maxRounds, before or with this deploy; a channel or user boundary under 163, or a budget: habit under it, now refuses an agent:ship request at the fork with the same sum, so raise those too. A standalone coding run simply gets the longer budget; a review's 25 is unchanged.

1.245.0 ​

  • ship.addressSeverity is gone; the severity to address is review.addressSeverity (agent-review item 5a), because it now gates every review's verdict — an approve carrying a finding at or above the level (default minor) is posted as Changes requested:, never LGTM: — and not only ship's merge-ready step. A config.yaml or a stored override still carrying ship.addressSeverity (org block, channel or user scope) fails the load naming the move. The edit: rename the key under review: (review:\n addressSeverity: minor), or re-run config set channel|me --review.addressSeverity <level> for a scope that had set it; a severity:<level> directive on a request is unchanged.

1.243.0 ​

  • The state Worker's POST /runs/usage-by-user route is gone; the bot asks POST /runs/usage for one row per run and does the arithmetic itself (run-history item 56). The bot and the memory Worker ship in the same release, so deploy all --affected carries both; a bot deployed against a memory Worker still on the old route fails its costs snapshot take with route missing (the status line and costs.snapshot.alertChannel say so, the previous snapshot keeps serving) until the Worker is deployed. A stored costs snapshot from before this release holds the old per-user rows and is refused on read, so the first minute after the deploy shows the costs page with no snapshot and then the loop takes a fresh one; nothing to edit.

1.241.0 ​

  • The dashboard has a new gated section, the chat at /threads (web-chat.md, dashboard-routes.md), and / now answers 302 /threads instead of 302 /runs. Under dashboard.auth: access the Cloudflare Access application lists path prefixes, and a prefix it does not list is served a bare 403 by the bot's own gate: add threads to the application's paths (beside runs, residents, costs, delivery, settings, api, mcp/connect) before or with this deploy, or every visit to the home page ends in that 403. Bookmarks to /runs are unchanged; the runs page keeps every operator control.
  • A browser sign-in (access:<sub>) now holds memory:write and mcp:write in its baseline beside every group's read (authorization.md item 9): what you type at /threads is a chat message, and memory remember and mcp add for your own scope work there as they do in Slack. The shared scopes are unchanged — an org or channel write still needs the grant it always needed. Nothing to edit; an installation that wants browser sign-ins without those two writes has no knob for it, as Slack users have none.
  • A run requested from the browser is recorded on the channel web:<sub> (a DM) and the thread web:<sub>:<conversation>, requested by the person the sign-in's email names when the dashboard link finds one, else by the sign-in; authenticatedAs carries the sign-in. The runs index labels it web. Nothing to edit.

1.229.0 ​

  • The native turn loop is gone: every preset runs on pi, in the run's container or as a child of the bot (harness-pi item 1). harness: in config.yaml is a retired key — a block of <preset>: pi still loads, a value of native fails the load by name (harness.<preset> must be pi — the native loop is deleted …). Remove the block. A process needs PUBLIC_BASE_URL for a preset with a workspace and PORT for one without; the CLI's ask opens its own loopback server for the run's pi. A follow-up in any thread now continues the agent of the thread's newest finished run and its conversation (session-log item 9) — before, only a preset on pi did — and an agent: in an earlier message no longer decides the agent by itself; model: and effort: stay sticky from the thread's messages. Two behaviours of the native loop have no pi counterpart yet, both carried as gaps in harness-pi: a resident container replaced under a live run fails the run instead of pausing it, and there is no stuck-loop guard.

1.219.1 ​

  • A follow-up in a coding thread now runs on the pull request the thread's newest run opened: the run record carries it (pr, run-history item 2) and the resolver binds the follow-up's ref to its head branch when the message names none (resident-repos item 29), so pushes land on the PR and a resubmitted description edits it instead of being refused on the repo default. A ref phrased in the message (on branch x) still wins, and a PR a person named in the thread keeps its rule. Nothing for an operator to change; records written before this release carry no pr, so an older thread behaves as before until a run in it opens or edits a pull request.
  • A run on the pi harness now survives a bot restart with its pi still running: the run's row carries the hash of the bearer pi was started with, and the generation that re-attaches honours that bearer on its model proxy (model-proxy item 2, harness-pi item 8). Before, the re-attach succeeded and pi's next model call was refused (401 unknown_bearer), failing every pi run in flight across a deploy. A row written before this release carries no hash: at the first restart after the deploy its pi is ended and restarted on the mirrored transcript with the new bearer, once. Nothing for an operator to change.

1.218.0 ​

  • Runs on the coding, review and explore presets have two more tools: recall, which searches the thread's whole conversation log (every turn of every run of that agent in the thread, compacted turns included) and reads a turn back, and notes, a notepad per thread and agent that rides the next run's system prompt and is steered to pi after every compaction (session-log item 10). Their prompts say what belongs in the notes. Nothing for an operator to change; the notepad lives in the conversation-log object the previous release created.

1.217.0 ​

  • A follow-up in a thread whose newest run is on the pi harness continues that run's agent and its conversation (session-log item 9): the agent is sticky by transcript, the router is not asked, and the model starts from the log's newest turns, the lines written since and the reply instead of the thread's Slack history; the record says seed: session. A thread on the native loop behaves as before. Nothing for an operator to change; a deployment with no pi preset (harness: unset) sees no difference.

1.215.0 ​

  • The pipeline's fix round is gone: after a review that requests changes the pipeline dispatches the findings into the unit thread as agent:coding (the findings step, agent-ship item 7) and every review round runs in a review thread of its own (item 5). A plan running across this deploy meets the change mid-flight: a unit sitting in a fix round replays against step names it does not know (<unit>/<n>/findings in place of <unit>/<n>/fix), its fix brief is refused 400 by the bot, and a re-review's prior.fixRunId is ignored, so that unit ends aborted or interrupted and its dependents block. Before deploying, let live plans finish or stop them; afterwards re-issue any plan that ended that way (agent:ship plan <path>.md units … reruns the units not merged, agent-ship item 16), and resume a task's review loop with agent:ship <pull request url> in its thread (item 10). Nothing stored changes shape: unit rows written before this release gain their review thread at the unit's next start or first review spawn.

1.213.0 ​

  • A plain message — no agent: directive, no sticky preset, no channel or user agent — now picks its own preset through the fast model on every installation, not only where routing: { auto: true } was set: the card reads routed: <reason> and ends with wrong preset? reply agent:<preset> to run it another way, the record carries a route event, a routed coding opens a pull request at once, agent: still wins, and ship is never routed (routing-and-config.md item 21). Each plain message in a fresh thread costs one small call to defaults.models.general (or routing.model). To keep every plain message on defaults.agent, add routing: { auto: false } to config.yaml — one line; a deployment that already set auto: true changes nothing. help in chat no longer lists the commands: it says how to ask; help commands lists them.

1.208.0 ​

  • ship.coordinator is gone from config.yaml, and the load refuses it by name (ship.coordinator is no longer a key — every agent:ship request runs on the plan runner; remove it). The in-process ship round loop it switched off no longer exists: every agent:ship request — a task, plan <path>.md [units …], or a ship pull request's URL to resume at review — runs as a pipeline, whose rounds are child runs of their own (agent-ship.md item 16). Delete the key from the ship block. A deployment that ran ship without the pipeline's machinery must now carry its prerequisites — a coordinator entry in SWITCHBOARD_INGRESS_TOKENS with grants.http:coordinator: { actions: [coordinator:step] } (add plan:merge for the pipeline to merge plan branches), PUBLIC_BASE_URL, and run history on the state Worker (Turn features on and off, the coordinator row) — or agent:ship is refused naming the missing one; nothing else about the request's shape changes.

1.15.0 ​

  • Run records written before span schema 2 (no schema field, or a lower one) show no timing: the run page's Timeline reads no timing data with no shape, and their stored friction diagnosis stands as written; the transcript, the call cards and runs get/runs events are unchanged. Nothing to edit — leave them, and they age out with runHistory.retentionDays. The turn and mcp_tool_use event kinds are gone; a saved JSONL capture that carries them is analyzed by friction analyze with those lines skipped and counted, and its model time comes from model.turn spans only.

1.14.0 ​

  • A config.yaml top-level key the document does not define — permissions: included — is an unknown key and fails the load by name; write who holds what as grants and what is closed as restrict (authorization).
  • A token entry's scopes in SWITCHBOARD_INGRESS_TOKENS is ignored like any field other than subject and channel; the token holds exactly its grants.http:<subject> / grants.mcp:<subject> entry (Ingress tokens are credentials, not grants).
  • A selfImprovement field other than repo, label, minRuns, top fails the load by name; the friction ledger is runHistory.