Kaimahi is pre-1.0 and incubating. This page is the whole contract: how a version is numbered, how to install one, how to check what you got, how to upgrade, and what happens when an upgrade goes wrong.
As of 2026-09-10, the latest tagged release is v0.1.0 and predates the
Orka helpers. The current getting-started path uses a
build from main; @latest does not provide those new commands. Orka's own
installation and upgrade limits are separate: see orka.md.
The plane-upgrade sections below apply only to the retained legacy plane.
Nothing here claims a trademark or a package-manager namespace. The name's publication constraints remain in NAMING.md.
Tags are vMAJOR.MINOR.PATCH, and the tag is the source of truth — the
binary reports it, the release is named after it, and the notes come from
CHANGELOG.md.
| Below 1.0 that means | |
|---|---|
patch v0.1.0 → v0.1.1 |
fixes only: no schema change, no removed flag, no behaviour change an operator was relying on |
minor v0.1.0 → v0.2.0 |
everything else, breaking changes included — below 1.0 this is where they live, and the changelog says so under Breaking |
pre-release v0.2.0-rc.1 |
a candidate for the version it names. go install …@latest ignores pre-releases, so a candidate never becomes somebody's default by accident |
There is no 1.0 promise and no support window yet. What there is: CI refuses to publish a tag whose version has no section in the changelog, and refuses to publish a binary that does not report its own tag.
Two tags are pushed for each version, at the same commit:
v0.1.0 the repository, and the kmx binary
plane/v0.1.0 the plane, which is a separate Go module under plane/
Both are needed. kmx plane installs the plane through the Go module proxy at
kmx's own version, and Go resolves a nested module's version from a
plane/-prefixed tag. The release job refuses to publish without it.
The one-line installer for the latest tagged CLI:
curl -fsSL https://raw.githubusercontent.com/kaimahi-agents/kaimahi/main/install.sh | shIt resolves the latest tag, downloads the binary for your platform, verifies
its published sha256 before installing it, and puts it in ~/.local/bin
without sudo. --quickstart goes straight on to a running agent;
KMX_VERSION=v0.1.0 pins a version; KMX_BIN_DIR=DIR installs elsewhere.
The other route, if you have a Go toolchain:
go install github.com/kaimahi-agents/kaimahi/cmd/kmx@latest@latest is the newest tagged release. Pin instead when you want a build you
can name: @v0.1.0. Both go through the public Go module proxy and the Go
checksum database, so the bytes you get are the bytes the sum database
recorded — no namespace of ours is involved, and there is nothing new to
trust.
install.sh above does exactly this, and doing it yourself is a reasonable
preference. Every release carries binaries and a checksums.txt:
version=v0.1.0
base=https://github.com/kaimahi-agents/kaimahi/releases/download/$version
curl -fsSLO "$base/kmx-linux-amd64"
curl -fsSLO "$base/checksums.txt"
want=$(grep ' kmx-linux-amd64$' checksums.txt | cut -d' ' -f1)
got=$(sha256sum kmx-linux-amd64 | cut -d' ' -f1) # macOS: shasum -a 256
[ "$want" = "$got" ] || { echo "checksum mismatch"; exit 1; }
install -m 0755 kmx-linux-amd64 /usr/local/bin/kmxThe comparison is written out rather than left to sha256sum -c on purpose.
--ignore-missing is a GNU coreutils flag: macOS has no sha256sum at all
(it has shasum), and BusyBox — Alpine, most slim container images — has one
that rejects the flag. The instruction that used to be here failed on both,
which is a poor first impression from a project whose whole argument is
fail-closed verification. install.sh picks whichever of sha256sum,
shasum and openssl the machine has, and refuses to install if it finds
none.
Do not skip the digest check. This project verifies the pinned kagent CLI's digest before it will execute it, and now kind's, kubectl's and Helm's too (internal/kmx/toolchain); applying less care to its own binary would be indefensible.
Platforms: linux/amd64, linux/arm64, darwin/amd64, darwin/arm64.
kmx drives a Linux container runtime, so the machine running it is a Linux
host or a Mac running one in a VM — and those are the same four platforms the
pinned kagent CLI publishes, which kmx downloads for the same machine.
Windows is served through WSL, which is linux/amd64; a native
windows/amd64 build would be an untested claim rather than a platform.
Go is still a prerequisite for the governed half. kmx up, kmx agent,
kmx status and the operator verbs work from a downloaded binary alone.
kmx plane builds the plane's image on your machine and uses go install to
do it — see below.
$ kmx version
kmx v0.1.0 (release build)
kaimahi is pre-1.0 and incubating: minor versions may break behaviour, and say so in CHANGELOG.md
kagent 0.9.12
model qwen2.5:3b
plane kaimahi-proxy:p10, built from v0.1.0The first line is the binary's own identity and it names its source, because the same version string means different things depending on where it came from:
| First line says | You have |
|---|---|
v0.1.0 (release build) |
a binary from the release for v0.1.0 |
v0.1.0 (installed with go install) |
go install …@v0.1.0 — the same code, built on your machine |
v0.0.0-2026…-fb456eb (development build from a checkout) |
a go build from a clone; not a release |
v0.0.0-dev+fb456eb.dirty (development build from a MODIFIED checkout) |
a clone with uncommitted changes |
go install github.com/kaimahi-agents/kaimahi/cmd/kmx@latest
kmx versionkmx itself holds no state: it reads your kubeconfig and writes agent YAML you own. Re-installing is the whole upgrade. Read the changelog for the versions you skipped — below 1.0 a minor bump may change behaviour.
The cluster is a separate question. A newer kmx does not touch a running
cluster until you ask it to; kmx up is idempotent and re-applies the pinned
kagent chart and the agents.
The retained governance plane stores its ledger, budgets,
allowlists, approvals and grants in Postgres. This is not Orka's storage
or upgrade contract. Upgrading the legacy plane is
kmx plane again with the newer kmx:
kmx backup plane-before-upgrade.sql # take one; it is one command
kmx plane # builds and rolls out the newer plane
kmx ledger # the rows are still thereWhat happens under that:
- The new proxy runs the migrations at startup, under a Postgres advisory lock, so a rollout of N replicas is its own migration step and the replicas do not race each other.
- Migrations are additive. Every column added since the first schema has a default, which is what lets a backup taken before an upgrade restore after one.
- A rollout is a Kubernetes rolling update: a new pod does not take traffic until it is ready, and it is not ready until its migrations have applied.
Proven, not asserted. CI's plane-upgrade job
(scripts/plane-upgrade-probe.sh) installs
a plane several migrations old straight from the module proxy, seeds it through
its own admin API with a credential, a budget, a tool allowlist, a grant a
human approved and a priced ledger row, then starts the current plane on the
same database and asserts every one of those survived and that the upgraded
plane serves a fresh governed call.
They are different versions more often than not: go install …@latest upgrades
kmx and touches nothing in your cluster, so anyone who upgrades one and not the
other has skew. kmx asks the plane what it is before asking it for anything —
GET /admin/version, once per command, on the admin port — and says so:
$ kmx tools allowlist warehouse
kubectl -n kaimahi port-forward deploy/kaimahi-proxy 19091:9091 # (the admin port is on no Service)
plane v0.2.0 (admin contract 1)
…The version string is for you. The admin contract is the number kmx compares: a version string cannot be ordered across releases and development builds, and it does not tell a client which routes exist. The contract does, and it rises whenever the admin surface gains something a client can depend on.
Skew is decided per operation, not once for the whole session. An older plane is not a broken one, so kmx keeps working against everything that plane can serve and refuses only what it cannot — before sending it:
$ kmx workflow govern --file release.yaml
this plane is too old to check a workflow's `requires` against its upstream table — nothing has been applied.
plane: a plane too old to report its version (v0.1.0 or earlier; admin contract 0)
kmx: v0.2.0, and this needs admin contract 1
Upgrade the plane with the kmx you are already running:
kmx planeA blanket refusal on any skew would strand a working cluster over one feature
it does not have; a blanket warning would be the same bare 404 page not found
with more words in front of it.
A plane NEWER than kmx proceeds, with one note saying so. The state lives in the plane — the ledger, the grants, the approvals — and stranding it because the CLI is behind would be the more expensive mistake. That is safe because of the promise below, not because it is usually fine.
Within a major version the plane's admin surface only ever grows. A route that has been served is not removed, and its request and response shapes are not changed in place; new information arrives as new fields or new routes, and the contract number goes up. Break that and an older kmx talking to a newer plane breaks silently, which is the failure this whole mechanism exists to prevent.
Two consequences worth stating:
- A plane that answers
/admin/versionbut reports a contract no release ever served is a fault in the plane, not a version gap, and kmx says so rather than sending you to reinstall over something an upgrade cannot fix. - A plane between
v0.1.0and the release that added/admin/versionis treated as contract 0 even where it could in fact serve more. Those are unreleased revisions, the misjudgement is conservative, and the fix is onekmx plane.
Proven, not asserted. The same plane-upgrade job drives the current kmx
against the genuinely old plane it already has running, and asserts the version
gap is named rather than reported as a 404 — and, at the other end, that a plane
built from the checkout reports a usable contract.
Migration 00008 welded tool approvals to the exact call. Grants minted
before it carry no argument digest, and that class is closed: those grants
keep their old verb-level meaning — still bounded by the expiry and use count
their approver set — and the store will not mint another one. So an upgrade
neither widens an old grant nor silently voids it. Every grant minted after
the upgrade admits exactly one call.
Migration 00010 gave credentials an expiry. Credentials that predate it
carry a NULL one and are not expired by the upgrade — expiring a running
estate at migration time would be an outage, not a control. The class can only
shrink: every credential issued afterwards has a deadline, and
kaimahi_credentials_without_expiry is the gauge whose job is to trend to
zero. Renew or re-issue at your own pace (identity.md).
Both of these follow one rule, which is the rule to expect from any future migration here: an upgrade never silently widens or voids what an operator already had.
The plane does not start. That is the designed answer, and it is what you should expect to see:
- goose applies each migration in its own transaction, so a migration that fails is rolled back whole. Migrations before it stay applied; the schema version stops at the last one that succeeded.
- The proxy retries startup for 90 seconds and then exits non-zero
(
database startup failedin its log). Under Kubernetes the pod crash-loops. - Because the new pod never becomes ready, the rolling update does not retire the old replicas: the previous version keeps serving while you work out what happened.
- Nothing is half-served. The plane refuses traffic on a schema it could not migrate rather than guessing which columns exist.
To recover: fix the conflict, or restore the backup you took
(kmx restore plane-before-upgrade.sql) and roll back to the previous
version. CI proves this path too — the same probe seeds a second database,
makes a migration impossible, and asserts the plane never serves, exits
non-zero, and leaves the rows and the schema version untouched.
kmx plane builds the proxy image locally: it go installs the plane from
the module proxy at kmx's own version and packages the resulting static binary
onto a distroless base. No container image is published for this release, on
purpose:
- The provenance is already better than a tag. The Go module proxy and the checksum database stand behind that fetch. An unsigned image tag in a registry would be a weaker claim wearing a stronger costume.
- kind's side-load stays honest. The local path loads the image into the
kind node and
k8s/plane/proxy.yamlpinsimagePullPolicy: Never, so a locally built tag can never silently fall back to pulling a squattable public name (see scripts/plane-deploy.sh). Publishing an image is exactly the change that would put pressure on that pin. - A registry namespace is a namespace. No trademark opinion has been obtained; claiming a distribution namespace remains a separate decision.
The cost, stated plainly: Go remains a prerequisite for kmx plane even if
you installed a downloaded binary. Registry-backed clusters (AKS) already have
their own road — the operator builds the image into their own registry
(aks.md) — and that is unchanged.
This is a decision for this release, not a principle. The case for publishing gets stronger the moment someone needs the plane on a machine with no Go toolchain.
For maintainers. The point of this list is that the second release is cheaper than the first.
-
Write the section. Move what is under
## Unreleasedin CHANGELOG.md into a## vX.Y.Z — <date>heading, and leave## Unreleasedempty behind it. Anything breaking goes under Breaking with what to do about it; anything that changes an operator's day goes under Upgrading. -
Merge that to
main. Releases are cut frommain. -
Tag both modules at the same commit and push:
git tag v0.2.0 && git tag plane/v0.2.0 git push origin v0.2.0 plane/v0.2.0 -
Watch the
releaseworkflow. It refuses to publish if: the version is not semantic,plane/vX.Y.Zis missing or points somewhere else, the changelog has no section, the built binary does not report the tag, or the checksums do not verify. -
Check the result:
go install …/cmd/kmx@vX.Y.Z && kmx version.
To rehearse without spending a version number, run the release workflow
manually (workflow_dispatch) from a branch: it builds and checksums exactly
the same artifacts, publishes nothing, and uploads them as workflow artifacts.