Upgrades
How an AcasoOS deployment moves from one version to the next — on acaso's shared infrastructure and, especially, on a client's standalone instance. The goal is that an upgrade is always the same two moves: apply migrations, swap the image tag. No data export, no fork merge, no acaso engineer required on site. The in-repo ops counterparts are docs/runbooks/release.md and the Upgrades section of docs/runbooks/deploy-to-client-infra.md.
Versioning model
- Each package (
shared,datahub,agenthub,apphub) carries its own semver inpyproject.toml, with a per-packageCHANGELOG.mdgenerated from Conventional Commits. - An umbrella git tag
v<MAJOR>.<MINOR>.<PATCH>snapshots a tested combination of package versions. The container image for a release carries the same tag. - Two spellings, on purpose: bare semver (
0.2.0) inside the app and its config (pyproject.toml, theRELEASE_VERSIONenv var, HelmreleaseVersion);v-prefixed (v0.2.0) for git tags, image tags, and Helmimage.tag.
RELEASE_VERSION drives the version badge on each service's /docs Swagger UI and the Sentry release tag — it is how you verify which build is live. If the badge disagrees with the image tag you deployed, the env var didn't move with the image.
What a release contains
- The container image, built from the tagged commit — including every namespaced bespoke directory (
templates/custom/*,agenthub/custom/*), compiled and tested together with the core. Clients never receive code that wasn't part of the tested release. - Alembic migrations, append-only and re-runnable. CI's
make migration-checkproves upgrade → downgrade → upgrade works for every migration. Breaking schema changes don't ship without an ADR and an explicit migration path. - Release notes and per-package changelogs, generated from Conventional Commits between tags. Anything a standalone operator must do beyond the standard two moves (a new required env var, a renamed setting) is called out there explicitly.
Releases are cut from release/v<X.Y.Z> branches per docs/runbooks/release.md: version bumps across every source-of-truth file, staging smoke test, merge to main, tag, CI image build and publish, backmerge to develop.
Upgrading the shared deployment
acaso operates this continuously: the standard CI/CD path deploys the tagged image, and RELEASE_VERSION on the deployment environment moves to the bare semver at the same time as the image tag. Tenants ride along; per-tenant configuration (overrides, prompts, bindings, scopes) is data, so it survives upgrades untouched.
Upgrading a standalone instance
The client SRE runs this on their own cadence, with no acaso involvement:
- Read the release notes for every version between the running one and the target. Confirm whether any env var changes apply.
- Snapshot the database. The backup is the rollback path of last resort.
- Pull the new image:
docker pull <REGISTRY>/acaso-os:v0.2.0(or build it from the tag:git checkout v0.2.0 && docker build ...). - Apply migrations BEFORE swapping any service, using the NEW image and the privileged database role:
docker run --rm \
-e DATABASE_URL="postgresql+asyncpg://acaso:...@<DB_HOST>:5432/acaso_os" \
<REGISTRY>/acaso-os:v0.2.0 alembic upgrade headMigrations are written so the previous app version keeps working against the upgraded schema — that is what makes the next step a rolling update instead of downtime.
- Swap the image tag on each service (DataHub API, AgentHub API, worker) — ECS task definition, K8s Deployment, or
helm upgrade --set image.tag=v0.2.0. Old and new tasks may coexist briefly. - Move
RELEASE_VERSIONto0.2.0(bare semver) on the services, together with the tag. - Verify:
/healthand/readyreturn 200; the/docsbadge on both APIs reports0.2.0;./scripts/smoke_test.shpasses; one agent runs end-to-end; the worker drains an ingest event.
Skipping versions is supported — alembic upgrade head replays every migration in order — but read each skipped release's notes.
The compatibility contract
What makes the two-move upgrade safe, and what each side must hold up:
| acaso guarantees | The client/operator guarantees |
|---|---|
| Migrations are append-only, re-runnable, and never break the previous minor without an ADR + explicit callout | Migrations run before the image swap, never after |
Bespoke namespaces (templates/custom/<slug>/, agenthub/custom/<slug>/) are never modified by core changes | All bespoke changes go through the monorepo, never patched onto a deployment |
| Env var renames and new required config are called out in release notes | Release notes are read before upgrading; RELEASE_VERSION moves with the tag |
| Every release is tested as one unit — core plus all bespoke code — by the full CI gate set | The instance runs tagged releases only, never branch builds |
Rollback
- Revert services to the previous image tag (and
RELEASE_VERSION). Because migrations are backward-compatible by policy, the old code runs against the new schema. - Only if a migration itself is the cause, run the previous version's
alembic downgrade -1— rare by design, and the reasonmigration-checkexists. - Open an incident per
docs/runbooks/incident-response.mdand report it to acaso so the release gets fixed for everyone.
Cadence
Track every MINOR within a sprint or two; apply PATCH releases promptly — security fixes ship as patches. The further an instance trails, the more release notes pile up for one jump; the process supports it, but short hops keep each upgrade boring. acaso notifies standalone clients on every release with the changelog and any callouts.