A tiny, copy-pasteable convention for giving any command-line tool a update
command that checks a server endpoint, downloads the latest binary or bundle,
verifies it, and atomically swaps it in place — with a passive nudge when a new
version is available.
It is deliberately small — one GET route, one CLI subcommand, one content hash —
so an agent can add it to a new tool in a few minutes, and so tools written in
different languages all follow the same pattern.
myapp update --check
# -> {"ok":true,"local":"a1b2c3d4e5f6","remote":"f7e8d9c0b1a2","up_to_date":false}
myapp update
# -> [update] a1b2c3d4e5f6 → f7e8d9c0b1a2; downloading…
# -> [update] updated a1b2c3d4e5f6 → f7e8d9c0b1a2 (backup: ~/.myapp.bak)
# -> {"ok":true,"updated":true,"from":"a1b2c3d4e5f6","to":"f7e8d9c0b1a2","backup":"~/.myapp.bak"}Agent-operated CLIs have no app store, no auto-updater, and no human checking for new versions. A stale CLI misses bug fixes, selector updates, and security patches — and the operator may not know for weeks. This convention gives every tool the same self-update pattern with almost no code: a content-hash version (no semver discipline needed), a verify-then-swap flow (no bricked binaries), and a passive nudge (no silent updates).
| File | For | What it is |
|---|---|---|
| PROTOCOL.md | implementers | the normative contract (MUST/SHOULD): version endpoint, update command, verification, swap, nudge |
| RECIPE.md | implementers | the 4-step how-to with reference snippets (bash + Go) |
| llms.txt | agents | the machine-first entry point — fetch this first |
| version.schema.json | everyone | JSON Schema for the version endpoint response |
| SKILL.md | agents | drop-in skill: "add self-update to this CLI" |
| AGENTS.md | agents | coding guidelines for maintaining this spec |
- Version is
sha256[:12]of the distributed artifact (binary or tarball). Identical bytes = same version = no update. No version-bump discipline needed. GET /versionreturns{ok, version, download, sha256}. Public (no auth).<tool> updatedownloads to temp, verifiessha256[:12]matches the server's advertised version, smoke-tests the new binary (<tool> versionruns), atomically swaps (current →.bak, new → in place), writes the new version stamp.<tool> update --checkreports without updating (exit 5 = update available).<tool> update --forcere-downloads even if the version matches (repair).- Nudge: on any server-hitting command, a best-effort stderr nudge appears when a newer version is available (throttled to once per hour). Never blocks, never fails.
- No silent auto-update: the default is manual (nudge only). Auto-update is opt-in.
- Self-installation (independently adoptable):
<tool> install [--prefix DIR]/<tool> uninstall [--prefix DIR]let a binary already in hand relocate or remove itself — default~/.local/bin, never sudo, idempotent install, no-op uninstall.
Full details in PROTOCOL.md.
These tools implement the client side across several languages and architectures — each is a worked example:
| Tool | Language | Artifact | Adoption shape |
|---|---|---|---|
| mago | Go | binary | content-hash + syscall.Exec in-place swap + relay-pushed version |
| automaintainer | Go | binary | panel-driven WebSocket update + SHA256 + .bak rollback + systemd restart |
| grepapi | machin + shell | tarball | GET /v1/version + grepapi update + version nudge |
| chatsnip | machin | binary | GET /version + chatsnip update + version nudge + .bak rollback |
| remotecmd | Go | binary | GET /version + rcmd update + version nudge + .bak rollback |
That the same contract fit a Go binary with in-place re-exec, a panel-driven worker update, and a bash CLI with tarball swap — without changing shape — is the reason it's written down here.
MIT — see LICENSE. This is a convention; copy it, fork it, adapt it.