ADR 0237: Hosted bodies update themselves to official releases
Status: Accepted (2026-10-06). Amends the hosted-image rule in commit 05f17fd1
("hosted images never update themselves"). Builds on
ADR 0183 and
ADR 0055.
Decision
A hosted body (managed or self-run) installs official Clankie releases itself,
with the same mechanism a Mac release install uses: download the verified
archive, stage it beside the running release, switch current, health-check the
new services and switch back if they do not come up. Source checkouts keep
following origin/main; this decision changes only release installs.
- Linux release archives. Every official release publishes
clankie-linux-arm64.tar.gzandclankie-linux-x64.tar.gzwith.sha256files beside the macOS archive, built byscripts/build-release.mjs --hosted.release.jsonrecords itstarget; the updater only accepts an archive for its own target. - A writable release root. The image's
/opt/clankiebecomes the seed, not the running install. On boot the entrypoint seeds/state/install/releases/<version>from it when absent and points/state/install/currentat the newer of the seed and the current release, so a new image (base or OS update) is never shadowed by an older self-installed release. The launcher and service run fromcurrent. - Private provider compatibility. A managed image installs its runtime
provider outside the replaced tree.
RuntimeProviderdeclares the provider API version it implements andrelease.jsondeclares the version a release expects. The updater refuses (provider-api-unsupported) a release the installed provider cannot serve, so an update never silently drops managed model, quota or credit policy. - One fleet-wide brake. A managed body asks the fleet for its approved
release (a signed
POST /fleet/v1/body/release, like every body route) and installs nothing newer.nullholds every body in place; an unreachable or refused answer installs nothing. A self-run body (no hosted bootstrap) follows the latest official release. - When it runs. Hosted bodies check on a schedule and on request
(
update_runtime,clankie update). A scheduled install waits until the body is idle: no running captain turn, voice session or hired worker. Owners can turn scheduled installs off (clankie update auto off,/updatein the TUI); managed bodies keep them on. Recheck enabled/idle state after staging for the exact accepted operation and running boot, before stopping services. Busy or disabled bodies retain the verified stage for a later idle check; a ready cutover closes turn admission before shutdown.
Why
A control-plane rollout (building a tenant image per release, replacing every body's root volume in batches, promoting the launch template) needs automated authority over images, instances and the production tenant stack, plus cleanup of a retained root volume per body per release. The body already has a release updater with per-body health rollback; hosted only lacked Linux archives, a writable install and a way to keep private policy compatible. "Hosted Clankie just works" then holds without new infrastructure: bodies stay current and nobody runs a deploy for an ordinary release.
Per-body rollback catches a release that fails health checks, not one that is broken some other way on every body. The approved-release answer is the single stop for that case, and lets the control plane stage releases by cohort later without the body changing.
Alternatives
- Control-plane rollout per release (the staged root replacement): kept for base-image and OS changes, rejected for every code release for the authority and cost above.
- No hosted updates; owners rebuild images: contradicts hosted just working and leaves managed bodies stale between operator deploys.
- Writable
/opt/clankie: mixes image-owned files with installed releases and loses the seed a new image provides.
Consequences
- Official releases build and publish three archives; a release missing one target leaves those bodies on their current version.
/stateholds up to two releases (current and the previous one kept for rollback); older ones are removed after a successful switch.- A managed provider that lags a release's provider API holds those bodies until a compatible provider image ships.
- The release archive's checksum comes from the same official GitHub release, as on macOS. A compromised release publisher reaches hosted bodies too.
- The public half (archives, release root, provider API check, approved-release cap, idle scheduling) lives here; the managed provider location and the fleet route live in the private hosted service.
Amendment: source checkouts batch their own updates, 2026-10-17
A self-hosted source checkout followed main by updating whenever asked, and
its lead asked after nearly every landing: 250 cutovers from 2026-10-06 to
2026-10-17, each restarting the service and the Discord bodies (VUH-2393).
A checkout now uses the same idea of idle as a hosted body, with one
difference: idle panes do not count, only a worker agent that is working.
An update that follows main applies at once when the body is idle or the
service has run for two hours (CLANKIE_UPDATE_BATCH_MINUTES). Otherwise it is
recorded in ~/.clankie/updates/deferred.json and the service applies the
newest main once idle or due, under its own authority, as a scheduled install
does here, and through the same holds, canary and rollback. clankie update now,
an explicit ref and a hold override apply at once; clankie update cancel drops
the batch.
A cutover also leaves the relay, activity surface and keep-awake running when the range changes none of the files their entrypoints can load, nor their packages' manifests and assets or the root lockfile. The Discord bodies still restart with the service: their live presence claim is bound to the service instance. A rollback restarts everything.
