Self-update
spt-core keeps itself current without ever interrupting your agents, and without trusting anything unsigned.
The invariant
No endpoint process terminates or suspends during a self-update. The daemon’s broker (holding PTYs, child processes, sockets) stays up; the brain (all logic) swaps under it. A hosted session’s process id and byte stream are identical before and after.
The trust chain
- Every release ships
SignedReleasemetadata: an Ed25519 signature over the release’s artifact digests. - Every binary embeds the two-key trusted set — an active primary and a never-used offline recovery key. Verification requires a valid signature from a trusted key and a matching artifact digest; an unverified binary never reaches the apply step.
- Losing the primary key is a non-event: the next release is signed with the recovery key (already trusted by every deployed binary) and rotates in a fresh primary.
- Adapters sign their own content. A
file_pulladapter update is verified against the adapter author’s key from its manifest; adelegatedupdate is trusted only when the manifest attests the delegated updater verifies its own content (self_verifies). spt-core’s release keys never vouch for adapter bytes.
One command: spt update
Bare spt update is the primary form (since v0.32.0): it fetches and
installs the latest signed core release, then updates every release-shipped
adapter — the whole node current in one command. When the core is already
current, only the adapters update. The invoking session survives a bare
spt update by construction: installing cycles only the daemon’s coordinator
process, never the hosted terminals — so it is safe to run from inside an
spt-hosted session. --core-only (-c) skips the adapters leg.
Update completion messages and consent notifications point to this node’s locally
hosted changelog.html. When documentation lands, UPDATE_DOCS_LANDED points to
the local index.html. Both links use the running listener’s discovered port,
not a guessed default or configured port. If live discovery is unavailable, the
changelog retains the release-channel link and UPDATE_DOCS_LANDED retains the
installed directory path.
spt update --restart is the full-cycle form: fetch, update adapters,
then finish by restarting the daemon onto the new version as the final
step — after it, the whole node (coordinator and every live agent) runs the
new version. The finish restart bounces hosted sessions (they come back
automatically) — that consequence is why it is opt-in rather than the
default, and why it runs last: everything else has already completed, from
any invoking context, before the restart lands.
Missing web routes after an in-place update. If the installed release is
0.68.0 or newer but the resident network layer reports a version below 0.68.0,
spt node status and a successful in-place update both explain that
node-prefixed docs URLs and serve controls are unavailable. Loading those
routes requires a full daemon restart, which stops hosted sessions.
spt node refresh only refreshes the coordinator; it does not replace the
network layer. Ordinary compatible version skew does not need this notice,
and an unknown resident version is not grounds for prescribing a restart.
The update-completion check uses the signed release just applied, not the
old updater’s own compiled version; a failed or unanswered diagnostic query
does not turn a successful update into a restart instruction.
What the composite’s exit code means. spt update runs several legs, and
its exit is the worst leg outcome by precedence — never simply the last
one that failed:
| exit | meaning |
|---|---|
0 | every leg succeeded (an already-current core counts as success) |
3 | a leg refused with nothing done — a guard declined and the box is exactly as it was — and no leg failed |
| other nonzero | at least one leg failed; the code is the first failure’s |
A failure always outranks a refusal, whichever order they happen in. That
matters under --restart, the one form that runs a leg after the
failure-isolated adapters leg: if adapters fail and the finish then refuses,
the command exits with the failure, not the refusal. Scripts branching on
these codes can therefore treat 3 as “nothing changed, act on the guard’s
message” without a failure ever hiding behind it.
spt update adapters [<a>[,<b>…]] runs the adapters leg alone (an alias
of spt adapter update, which also stays; both accept a comma-separated
list). Names are validated before anything updates — a typo never leaves a
half-updated set — one adapter’s failure never stops the rest, each adapter
gets a summary line, and the exit is nonzero if any failed. A registered
adapter without a release channel (a local dev registration) is skipped
loudly, not failed.
How updates move
Peer-propagated: one node fetches a release; paired nodes offer/fetch staged
releases from each other, each verifying independently before staging.
Updating is consent-gated by default — a notification surfaces at your
most-recently-active endpoint, and spt update apply is the explicit ack
(it re-verifies the staged release before touching the live daemon).
Full-auto is an explicit opt-in.
The lower-level verbs remain for surgical control: spt update fetch pulls
the latest signed release from the origin and stages it, then
spt update apply installs it. spt update fetch --apply does both in one
step (and still installs when the latest was already staged). (--apply
since v0.18.0)
Prerequisite: the GitHub CLI. The release channel is a private GitHub
repository, and spt update fetch downloads releases through an
authenticated gh (since v0.32.0). A node without gh installed is refused
loud with the OS’s install command; gh installed but not logged in is refused
with a pointer at gh auth login. Signature verification is unchanged and
carrier-independent — bytes are verified after download exactly as before.
See Installing for the gh setup steps.
After self-updating, spt-core ripple-updates registered adapters through
each manifest’s declared [update] avenue — the same engine behind
spt update adapters and the bare composite’s second leg.
Composite adapter updates — a delegated post-step (since v0.16.0)
An adapter can run a second, adapter-owned step after its primary update
avenue resolves — under spt adapter update and under spt adapter add
(install is the first update, so a fresh install runs it too; since v0.19.0).
Declaring an optional [update.post] sub-table (command required; an
attestation-only self_verifies flag) lets one lever both pull the adapter’s
.spt (e.g. from gh_release) and run an in-harness sync (e.g. a plugin
updater). The post-step:
- runs unconditionally — even when the primary avenue was a no-op (its own idempotent check decides what changes);
- runs foreground and bounded — a child of the CLI, 120 s timeout, never
backgrounded or detached: when
spt adapter add/updatereturns, the step has finished or failed; - receives a published JSON line on stdin describing the just-resolved
update (
adapter_applied,version,previous_version,adapter_dir, …; additive keys only — ignore unknown); - decides the post-update notice via stdout — custom text supersedes the
static
[update].message, the reserved sentinel!!update-message!!fires the static message, empty prints nothing; - is failure-isolated and loud — a nonzero exit / spawn failure / timeout
prints
ADAPTER_UPDATE_POST_FAIL:<adapter>(with the step’s stderr detail) on stderr and makes the CLI exit nonzero; the committed pull is never rolled back, and the static[update].messagestill fires when the adapter applied — so check the exit code, and don’t let a static message promise what the post-step may have failed to do.
The exact stdin keys, sentinel, notice precedence, timing, and the
verify-then-notify pattern are in the
manifest [update.post] reference.
What changed in a release
The Changelog is a page of these docs, so the answer to
what does this update give me is on the machine you are updating — no
repository checkout, no network. It is generated from the project’s
CHANGELOG.md and drift-gated in CI, so a release note that exists anywhere
exists here, verbatim.
Commands
spt update · the consent notification flow (spt notif) —
CLI reference.