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.confirmreturns HTTP409 confirmation_requiredor 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 authorizedboundary.confirm: destructivescope; 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
builtinnow default Review toanthropic/claude-opus-5-5, with Standard's effort and harness. Declare theanthropicprovider and supply its API key. To keep Review on the installation's existing work model, setagents.review.profile: standardbefore upgrading, including installations written by an olderswitchboard init --model <name>. Explicit Review model/profile overrides keep their selections. Configs written by this release'sinit --modelinclude 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
generalitself withno_decisionon the run's operator record, so there is no second classifier model. Removerouting.model,routing.effort,routing.autoandrouting.answerfromconfig.yaml; a load that still carries one fails with the migration sentence. The thread-reply gate'smodelnow falls straight back todefaults.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
verboselevel 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'srouted:receipt, the workspace and budget notes on the card) anddebug(plus the router's reason,route reason: <reason>, in place of the card'srouted: <reason>line, anduntracked by the ledger). Removed for everyone: the routed card'swrong 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(orconfig set channel --verbosity verbosefor a channel,defaults.verbosity: verboseinconfig.yamlfor the installation);verbosity:debugon 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 atdebug. - More of the standard mode is quiet: the routed write offer's footer (
confirmation required by …) is gone, theconfig setreply is a sentence inconfig show's words instead of a JSON dump, and four more messages moved toverbose— the live card's— thinking (35s since last tool)suffix and the running command's code block (at quiet the caption→ bashalone), the head a pull request's body was re-rendered at in the🔀 PR updatednote, 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 areverbose. The board issue and the unit row keep the full report. Setverbositybefore the ship ask (config set me --verbosity verbose, orverbosity:verboseon 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
shipblock must hold rose with it —ship.maxMinutesneeds at least 163 atmaxRounds: 3and 189 atmaxRounds: 4(was 108 and 129) — so aconfig.yamlwhoseship.maxMinutessits 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: raiseship.maxMinutesto the registry's 240, or lowership.maxRounds, before or with this deploy; a channel or user boundary under 163, or abudget:habit under it, now refuses anagent:shiprequest 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.addressSeverityis gone; the severity to address isreview.addressSeverity(agent-review item 5a), because it now gates every review's verdict — anapprovecarrying a finding at or above the level (defaultminor) is posted asChanges requested:, neverLGTM:— and not only ship's merge-ready step. Aconfig.yamlor a stored override still carryingship.addressSeverity(org block, channel or user scope) fails the load naming the move. The edit: rename the key underreview:(review:\n addressSeverity: minor), or re-runconfig set channel|me --review.addressSeverity <level>for a scope that had set it; aseverity:<level>directive on a request is unchanged.
1.243.0
- The state Worker's
POST /runs/usage-by-userroute is gone; the bot asksPOST /runs/usagefor one row per run and does the arithmetic itself (run-history item 56). The bot and the memory Worker ship in the same release, sodeploy all --affectedcarries both; a bot deployed against a memory Worker still on the old route fails its costs snapshot take withroute missing(the status line andcosts.snapshot.alertChannelsay 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 answers302 /threadsinstead of302 /runs. Underdashboard.auth: accessthe Cloudflare Access application lists path prefixes, and a prefix it does not list is served a bare403by the bot's own gate: addthreadsto the application's paths (besideruns,residents,costs,delivery,settings,api,mcp/connect) before or with this deploy, or every visit to the home page ends in that403. Bookmarks to/runsare unchanged; the runs page keeps every operator control. - A browser sign-in (
access:<sub>) now holdsmemory:writeandmcp:writein its baseline beside every group's read (authorization.md item 9): what you type at/threadsis a chat message, andmemory rememberandmcp addfor 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 threadweb:<sub>:<conversation>, requested by the person the sign-in's email names when the dashboard link finds one, else by the sign-in;authenticatedAscarries the sign-in. The runs index labels itweb. 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:inconfig.yamlis a retired key — a block of<preset>: pistill loads, a value ofnativefails the load by name (harness.<preset> must be pi — the native loop is deleted …). Remove the block. A process needsPUBLIC_BASE_URLfor a preset with a workspace andPORTfor one without; the CLI'saskopens 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 anagent:in an earlier message no longer decides the agent by itself;model:andeffort: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 nopr, 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,reviewandexplorepresets 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, andnotes, 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
fixround is gone: after a review that requests changes the pipeline dispatches the findings into the unit thread asagent:coding(thefindingsstep, 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>/findingsin place of<unit>/<n>/fix), itsfixbrief is refused400by the bot, and a re-review'sprior.fixRunIdis ignored, so that unit endsabortedorinterruptedand 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 withagent: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 useragent— now picks its own preset through the fast model on every installation, not only whererouting: { auto: true }was set: the card readsrouted: <reason>and ends withwrong preset? reply agent:<preset> to run it another way, the record carries arouteevent, a routedcodingopens a pull request at once,agent:still wins, andshipis never routed (routing-and-config.md item 21). Each plain message in a fresh thread costs one small call todefaults.models.general(orrouting.model). To keep every plain message ondefaults.agent, addrouting: { auto: false }toconfig.yaml— one line; a deployment that already setauto: truechanges nothing.helpin chat no longer lists the commands: it says how to ask;help commandslists them.
1.208.0
ship.coordinatoris gone fromconfig.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: everyagent:shiprequest — 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 theshipblock. A deployment that ran ship without the pipeline's machinery must now carry its prerequisites — acoordinatorentry inSWITCHBOARD_INGRESS_TOKENSwithgrants.http:coordinator: { actions: [coordinator:step] }(addplan:mergefor the pipeline to merge plan branches),PUBLIC_BASE_URL, and run history on the state Worker (Turn features on and off, thecoordinatorrow) — oragent:shipis refused naming the missing one; nothing else about the request's shape changes.
1.15.0
- Run records written before span schema 2 (no
schemafield, or a lower one) show no timing: the run page's Timeline readsno timing datawith no shape, and their stored friction diagnosis stands as written; the transcript, the call cards andruns get/runs eventsare unchanged. Nothing to edit — leave them, and they age out withrunHistory.retentionDays. Theturnandmcp_tool_useevent kinds are gone; a saved JSONL capture that carries them is analyzed byfriction analyzewith those lines skipped and counted, and its model time comes frommodel.turnspans only.
1.14.0
- A
config.yamltop-level key the document does not define —permissions:included — is an unknown key and fails the load by name; write who holds what asgrantsand what is closed asrestrict(authorization). - A token entry's
scopesinSWITCHBOARD_INGRESS_TOKENSis ignored like any field other thansubjectandchannel; the token holds exactly itsgrants.http:<subject>/grants.mcp:<subject>entry (Ingress tokens are credentials, not grants). - A
selfImprovementfield other thanrepo,label,minRuns,topfails the load by name; the friction ledger isrunHistory.