Changelog
Generated from the repository
CHANGELOG.md(cargo run -p xtask -- gen) and drift-gated in CI — this page cannot disagree with the changelog. Do not edit by hand.
All notable user-facing changes to spt — what a person running the CLI
notices or does differently. The ## [<version>] section of each release
becomes that release’s GitHub Release notes verbatim (see
docs/RELEASE-RUNBOOK.md). This project follows
Keep a Changelog and semantic versioning.
Pre-1.0, choose the bump by what a user can notice: minor when a release
breaks something, or changes the observable behavior of existing surfaces
broadly; patch for fixes, and for additive opt-in capability — a new key,
flag, or page that no existing user can encounter without opting into it.
[0.72.0] - 2026-09-22
Fixed
-
Successful
;;…;;seal ceremonies now return the minted token in the author’sDISPATCH_RESULTSasseal minted seal=<token>, once per session. Older tokenless results remain readable without replaying previously seen outcomes. -
IO-compliant adapters no longer receive the deprecated
spt-shellscontext after post-spawn bind or clear/compact. Both paths now resolve the endpoint’s recorded adapter/profile, matching the existing listen path; other adapters retain their legacy shell context. -
File-access helpers now preserve quoted paths containing spaces on Windows and POSIX, including home-rooted paths, instead of looking for truncated fragments. Unquoted paths containing whitespace remain outside the supported contract.
-
File-access signals emit identical fetch commands only once within a poll. Reports of one prompt differing only in leading or trailing ASCII whitespace share the first receipt and its original deadline.
-
Handing the same live file to another endpoint adds that endpoint to the existing served entry’s visible audience and resurfaces the same URL, without a second entry or extending its original 24-hour deadline. Helpers appear once per receiving endpoint, session and filepath, even if the prompt is rephrased.
-
Remote file helpers normally arrive with the path-bearing prompt after a short bounded wait. Late owner answers remain available on the next poll; pathless prompts trigger no helper lookup and add no wait.
-
Peer-delivered text that was re-encoded with XML entities is no longer mistaken for the user’s own input, so a peer can no longer cause the controller’s node to serve one of its files; bare ampersands are not treated as evidence of peer origin. The protection covers deliveries received after the node’s daemon is restarted (restarting only its brain is not enough); deliveries received before that restart are not covered.
-
Self-update now checks access to its release repository with GitHub CLI’s effective credentials, so a stale stored login cannot veto a working
GH_TOKEN. Failed reads name a failed account only whenghreports one, without printing raw authentication output. A timed-out probe reportsGhProbeTimeoutseparately from an authentication/access refusal.
Known limits
- Rolling back to 0.71.0 while a served entry has multiple audience endpoints makes every served file on that node answer HTTP 500, including unrelated entries. The registry is preserved and access fails closed. Waiting 24 hours does not recover serving: the old binary cannot load the registry to reap the expired entry. Roll forward, or stop the old daemon and manually remove the multi-audience entry from a backed-up registry, preserving its name-allocation history. Do not delete the registry or unrestrict the entry. Ordinary single-endpoint audiences retain the previous on-disk format.
[0.71.0] - 2026-09-18
Fixed
-
An immediate
spt api io-eventspoll now includes messages that a listener has already displayed, rather than sometimes missing a newly delivered message. -
Starting or resuming an endpoint no longer incorrectly marks it offline while its session is still starting. This also covers resumes that fall back to a fresh session; genuinely dead sessions are still reported offline.
-
Attached sessions reached through
spt rcno longer pause during pairing or repeated router discovery on Windows. Automatic UPnP, NAT-PMP and PCP router mappings are disabled; relay-assisted connections and NAT hole-punching remain available. Updating only the background service is insufficient: the running terminal host must also be updated for these fixes to take effect. -
Engine-room sessions remain online during startup and receive their opening briefing reliably.
-
Served resources and their stable addresses no longer disappear when expired attachments are removed at the same time. Both the background service and terminal host must be updated for this fix to take effect.
-
Update completion messages and consent notifications link to the node’s locally hosted changelog, and successful documentation installs link to its local index. When the local documentation service is unavailable, the existing release link or installed directory remains the fallback.
-
Active
spt rcsessions resume input afterspt node refreshor an update’s service restart instead of freezing.
[0.70.0] - 2026-09-13
Added
- Quoting an absolute or home-relative filepath while controlling an agent through
remote
spt rccan now offer that agent a fetch command for the live file on the controller node. Access is limited to that agent for up to 24 hours; repeated reports neither extend access nor repeat the notice. Missing files and local or read-only input stay quiet. No adapter changes are needed.
Fixed
-
Opening a node’s root web address now opens its public documentation instead of a served-resources index. Redirects through another node retain the node label used in the original address.
-
Links from served files, attachments and local documentation now use the port the running service actually bound, rather than a configured or default port.
-
An unknown
spt shell cmdoperation now lists the available command operations and points to the adapter manifest for other input methods. -
Ctrl+B, thend, now detaches a read-onlyspt rc --viewsession without stopping the agent or disconnecting its controller. -
Older console clients now receive guidance to reopen or update when a known compatibility problem affects them, without forcing a disconnection. Version differences alone stay silent; client-only problems do not request a service restart.
-
Improved firewall setup for
spt serve lan --bootstrapon Windows, including hosts with many firewall rules. If verification fails after rules are written, the diagnostic states that they may already be present. -
Unlisted-peer presence checks in endpoint listings use a 2.5-second timeout instead of 10 seconds. A peer that does not answer remains unknown, not offline.
Internal
-
Improved diagnostic logging for attached sessions; refresh freezes and console pauses remain unresolved.
-
Message-arrival records now include successful delivery to hosted agents; delivery outcomes are unchanged.
[0.69.0] - 2026-09-11
Fixed
-
Cross-node messaging now recovers on its own instead of stalling until the service is restarted. Previously, one unanswered request could stop this node from advertising itself and reconnecting to peers, until other nodes could no longer reach it.
-
Sending a message to an agent on another machine no longer waits without limit when the far side accepts the connection and then never answers. Previously, such a send could hang indefinitely without reporting why.
-
Persistent shells now restart automatically after the background service restarts, once their owning agent is online. Previously, eligible shells could remain offline until relinked by hand.
-
A successful
spt shell relinknow reportsbindingwhile it waits for its handshake, instead of a successful launch reading asoffline. -
Agents on other machines that were already known are now remembered across a restart of the background service, rather than disappearing until peers advertise them again.
-
Service status and successful update completion now say when the running network layer is too old to serve node-prefixed documentation and serve controls, that loading them needs a full service restart, and that the restart stops hosted sessions. Successful updates no longer unconditionally advise restarting the service.
-
Stale-session cleanup no longer signals a process whose identity does not match the recorded session; the stale record is cleared instead. If the identity cannot be verified, the request declines and leaves the process alone.
-
Service status now reports the process id of the running service, taken from the running service itself; a recorded id that no longer matches is marked stale. Previously, status could display an obsolete process id from a file.
Internal
- Daemon logs now summarize healthy connection activity once a minute instead of recording every open and close.
[0.68.0] - 2026-09-08
Web serving. Files, directories, adapter documentation and the changelog are
reachable at node-prefixed addresses on the local server, and an address that
names another machine in the subnet is answered by the machine that owns it.
Messages can carry attachments the receiver pulls on demand, every message
gets a short ID to show or reply to, and a machine with no spt on it can be
handed the binary over the local network. The XFER access surface is retired.
Added
spt serve add,spt serve rm, andspt serve list --jsonmanage live file and directory references. Same-name registrations receive stable numbered suffixes; removing an entry never deletes its source. Only the same absolute path and kind may reclaim a retired name.- Every registered adapter gets a core-owned
web/output directory.[adapter].web_short_pathoptionally gives it a short URL alias. Removing an adapter stops serving its output without deleting the files. spt adapter addnames each manifest key it does not know on stderr (manifest: unknown key [<table>].<key> (ignored)) and still registers; a misspelled optional key is no longer visible only by its absence.WEBjoins the access-control vocabulary. It is open by default within the subnet, but explicit WEB denies still govern. Existing file transfer is unchanged.- A served resource’s URL now works from every machine in the subnet: a
request for
/<peer>/…on the local loopback server is answered by the owning node through the local daemon. The body streams and nothing is cached;HEADandRangerequests are honored by the owner. An owner that refuses answers 403 namingWEB; an owner that cannot be reached answers 502 naming the node, within a bounded time. spt send --attachment <path>sends a file with a message. The file’s bytes are captured as they are at send time, so later edits or a deletion do not change what arrives, and the receiver pulls them withspt fetchwhen it wants them.--ttlsets how long an attachment stays available (default 30 days, a unit is required); expired attachments answer 404 immediately.- Every message now carries a short ID.
spt msg show <id>prints a message by it, andspt send --reply-to <id>marks what a message answers. Messages from another machine resolve through the machine that holds them. spt serve lan --bootstraphands the spt binary to a machine that is not yet a node, over the local network on port 5470. It is off by default and off again after every daemon restart, serves only the binary, its release sidecar and an install command, and refuses to start by name when the applied update set is not signed or does not match. It prints a checksum per platform, andspt installgains--expect-sha256and--release-jsonso what was downloaded can be compared before it is run. The documentation port is untouched and stays on loopback.[adapter].docs_dirpublishes an adapter’s own documentation at thedocssegment of its address, beside the adapter’s output directory rather than in place of it. A missing key, an unreadable manifest, a path that escapes the adapter’s directory, and a directory that has since been removed all answer the same 404 naming what was asked for.- The changelog is now one of the pages the local documentation server offers.
- A harness is told when a message it receives refers to a file the reader cannot open — an attachment to pull, or a path that belongs to another machine — instead of leaving the reader to discover it. For a path on another machine, that machine can be asked to publish it and answers with a link; it honors such a request only for its own endpoints, so no third machine can have someone else’s file exposed.
Changed
- The loopback HTTP server now has a node-prefixed resource index with HTML
and
?jsonviews./redirects to/<node>/; canonical docs URLs live at/<node>/docs/, while existing bare docs paths remain compatibility aliases.
Removed
- The
XFERaccess surface is retired: attachments replaced the transfer it gated, so the entry leaves the access-control vocabulary along with the transfer itself. An existing rule that namesXFERis kept and reported at load rather than dropped silently; it no longer governs anything. The shell channel’s own transfer progress is unaffected.
Fixed
- Messages delivered by
spt api listennow reach the receiver’s incoming-message history and last-message state, for both queued backlog and live TCP delivery. A later hook poll does not record those deliveries again; filtered notifications are not recorded as delivered messages. - A node’s message and activity history reads back correctly once its log grows past a quarter of a megabyte. Positions no longer restart from the beginning, so a request for everything since a given point no longer comes back empty or repeats entries that were already seen, and an oversized position is answered with the current head instead of skipping a page.
- An agent’s own saved context is no longer overwritten, unread, by the automatic summary that follows it. The automatic summary is filed directly and never writes to the file an agent saves its own context to.
spt api bindno longer prints an engine-room probe line on a machine that has no engine room.
[0.67.1] - 2026-09-06
Documentation-only release. The manifest reference in the developer docs now
covers the [service] section — the always-on background process spt
supervises on an adapter’s behalf — which was previously described only in
the CLI reference’s spt adapter service entry.
Added
- Manifest reference —
[service]. The developer docs’ manifest reference now documents the[service]section: thecommand,startandstop_grace_mskeys; what registration reports when a service is declared; the environment a supervised process is started with (SPT_SERVICE_OPTION,SPT_SERVICE_DIR,SPT_BIN,SPT_HOME); thestop-requested,status-advisoryandstartup.capturefiles in its runtime directory; how a cooperative stop, an update hold and a startup fault behave; and whatspt adapter service listandspt adapter service statusreport. The adapter integration checklist gains a matching line.
[0.67.0] - 2026-08-30
The now-signal — the situational-awareness feed a harness injects at turn boundaries — now covers software updates, sealed-message education, and hints from shell adapters; shells can watch who is attached to their endpoint; and the automatic psyche-updating summaries called echo communes fire on a 15-minute cadence instead of at every turn end.
Added
- The now-signal gains an UPDATES category: when spt-core, the session’s harness adapter, or a registered shell’s adapter updates, the agent is told once, with the new version number.
- The now-signal gains a SEAL_BRIEF category: a two-sentence, once-per-session brief telling an agent what a sealed message proves and how to verify one.
- Shell adapters’
[hints]are now read. Previously only the harness adapter’s hints ever surfaced, so a shell’s hints were dead text. A shell instantiated to the endpoint surfaces its full hint; one merely installed surfaces a one-line teaser naming the newspt adapter hints <adapter>verb, which prints the full text. At most one hint renders per source per message — the harness and each shell adapter get their own once-per-session slot, so one adapter’s chatter can no longer silence another’s hint. - Shells receive attachment frames: when a controller or viewer attaches to or detaches from their owner endpoint, each linked shell is told the current attachment picture — whether the endpoint is controlled, from which node, and which nodes are viewing — plus which node just changed when exactly one did. Frames are current-state-carrying and ephemeral, like activity frames.
- An spt-hosted session that goes five minutes without any attached controller or viewer receives a one-time notice telling the agent to proceed but withhold user-aimed output until someone attaches; a short reciprocal notice fires when someone does. Endpoints without a hosted session are out of scope — nothing can attach to them.
Changed
- Echo communes now fire on an age gate: a turn end arms them, but they fire only once the oldest un-fired turn end is 15 minutes old, instead of at every turn end. Attention-change fires (detach, attention shift, suspend) stay immediate. A session boundary — clear or compact — now captures the departing session’s history before the session id rotates, so the delta the boundary interrupts is recorded rather than lost.
- A subnet-join line in the now-signal now names its subject: it reads
node <label> joined subnet <x>, and a member with no label renders aspubkey <prefix>so it cannot be mistaken for an endpoint name.
Internal
- The digest’s echo record kind is renamed from
echo_mirrortoecho_commune: the old name claimed a mirror into the agent’s running context that has never existed. Existing digest rows are unaffected.
[0.66.0] - 2026-08-29
A harness adapter can now see where one stretch of a session ends and the next begins, and can report agent output produced partway through a turn rather than only at its end. Diagnostic lines meant to be read by other programs now arrive whole.
Added
- Session boot, clear and compact are now reported in a session’s event feed as boundary events of their own, so a reader can tell where one stretch of a session ended and the next began. Previously these edges were not reported at all, and a reader could only infer them from the traffic around them. One event is reported per real edge: re-binding a session that is already bound is not an edge and reports nothing.
spt api state busyaccepts--mid, marking output an agent produced partway through a turn. Such output is reported as agent output like any other, with an added marker so a reader that cares can tell the two apart, and one that does not can ignore it and read the span as an ordinary end-of-turn report.--midatidle, and--midwith no payload, are refused by name rather than accepted and silently reinterpreted.
Fixed
- Starting a session while the machine is heavily loaded no longer occasionally starts a second one. Previously a start that took longer than two seconds was presumed to have died, and a duplicate was created alongside it; a slow start is now waited on for as long as it keeps making progress. A start that stops progressing entirely is still taken over, and says so.
- An endpoint whose configuration declares no resume role is now skipped with a status saying so, instead of being recorded as a repeated failure. Previously every attempt counted against the failure budget reserved for a declared role that genuinely fails, and surfaced as error churn in the status an operator reads to find real faults.
- The published harness-contract schema no longer carries internal tracker codes in its descriptions. An adapter author reading the contract was previously shown references that resolve to nothing outside the project.
- Diagnostic lines written for other programs to read now always arrive whole. Previously, when two of them were produced at the same moment, one could appear split with the other’s text inserted into the middle of it, leaving both unreadable to a program parsing them.
Internal
- Release and assembly tooling gained checks for build-cache free space and for the fidelity of changes carried between branches.
- Starting the background service now records how long each stage of its startup takes, so a slow start can be attributed rather than guessed at. The project’s own test and continuous-integration settings were adjusted to match the measured cost.
[0.65.0] - 2026-08-29
Persistent shells now come back with their endpoint instead of staying down until someone notices, and a harness adapter can read a session’s own input and output as an ordered feed it polls at its own pace.
Added
spt api io-events --session-id <sid>returns a session’s input and output events in order, oldest first, and remembers where that session stopped reading — so asking again returns only what has arrived since. A caller that tracks its own position can pass--after <n>instead. A call carrying neither is refused by name rather than answered with an empty result that would read as nothing happened. A session’s first call returns nothing and starts from that moment.--limitcaps one answer and says that it capped, deferring the rest to the next call rather than dropping it. The feed is authenticated likespt api poll, and for the same reason: it hands back the session’s own text verbatim.
Fixed
- A shell set to stay running is now restored when its owner endpoint comes
online, and not only when the daemon finds that owner already up as it
starts. Previously, whether restarting a node left such shells down came down
to ordering: an endpoint that came up after the daemon’s one-time pass was
never revisited, and its shells stayed down until a
spt shell cmdwoke them or they were relinked by hand. - The published harness-contract schema described resume-session selection as
triggered by a
--resume <session>argument that no longer exists — a spelling that is now refused outright — so an adapter author reading the contract could conclude the role was unreachable. It now names the real triggers (spt endpoint resume <id>,spt go <id>on an offline endpoint, and Resume from history in the picker), and states that resume always continues the most recent session, with no verb accepting a session argument. It also documents theRESUME_NO_HARNESS_SESSIONnotice, printed when no harness session is on record yet: a fresh session starts, and a declared resume template does not run at all. - When more than one endpoint has been bound from the same shell, spt no longer guesses which one a command came from. It says so and stamps the command with its origin instead, and points at binding each endpoint from its own session.
[0.64.0] - 2026-08-27
An agent session can now ask spt one question — spt api now-signal — and get
back only what changed since it last asked, instead of polling several commands
to piece the picture together. Harnesses that opt in can also hand spt the
turn’s text and let it handle message tags and seal requests itself, and a
commune that fails to save is no longer silent.
Added
spt api now-signal <id> --session <sid>answers what changed that I should know about: new messages, message-send outcomes, shell and endpoint changes, and matched keyword hints, printed as XML under a single<SPT-NOW-SIGNAL>block. It is delta-only per session — a call with nothing new prints nothing at all, not an empty block — so a harness can run it at every turn boundary cheaply. A new session is entitled to the full picture once; every later call is thin. Pass the turn’s text with--user-input/--agent-outputso the categories that read it can fire.- The picture can be narrowed and capped:
--spec-manifestreads the[io.now_signal]settings (only,without,max_lines) from the adapter manifest, and--spec-file <path>takes the same shape as a JSON file composed per call and wins when both are passed. A missing, unreadable or malformed spec falls back to the default picture instead of refusing, so a typo in one cannot break a running session. spt api state busy|idlecan carry the turn’s text:--payload-stdinor--payload-file <path>. Passing both is refused by name withSTATE_PAYLOAD_AMBIGUOUS. Sending no payload behaves exactly as before, so an already-installed adapter keeps working untouched.- Adapter manifests gain an
[io]section.compliance = truehands the parsing of an agent’s own turn text to spt;shortform = falsekeeps that parsing off while staying compliant. Both are off by default, so an adapter that declares neither sees no change. - With
[io] compliancedeclared, spt recognizes@<target body @>message tags and;;-marked seal requests directly in an agent’s output. Text inside fenced code blocks or backticks is invisible to that scan, so a tag can be quoted, documented or pasted as an example without sending anything. An unclosed or empty tag sends nothing rather than guessing.
Changed
- Saving a commune is now observable to a linked shell: both a commune being taken up and a commune failing to be taken up are reported. A failed save previously left nothing but a log line, so agents could lose context saves without anyone being told.
spt api hintis now a thin alias for the hint part ofnow-signaland shares the same once-per-session state, so an adapter injecting both is injecting the same line twice — inject one.- Sending a message now reports delivered and no perch as distinct outcomes, where an unreachable target, a refused target, an ambiguous target and an empty body all reported the same thing before. The exit codes an operator sees are unchanged.
- For adapters that declare IO compliance, the shells listing that arrived as
its own message at session start now arrives through
now-signalinstead. Adapters that do not declare compliance keep receiving it as before. - The harness-contract documentation, the integration checklist and the machine-readable documentation index now cover these surfaces; the frames page they are specified in was previously reachable only by direct link.
[0.63.0] - 2026-08-25
Sealing with Windows Hello now works on a real machine: enrolling this machine’s authenticator refused on every box before this, and the Hello prompt now appears as documented. A sealed send tells you the token it minted instead of leaving it on the terminal, and a commune no longer goes missing when several agents save their context at the same time.
Fixed
spt seal enroll-authenticatorcan now actually enroll on Windows. It previously refused on every machine withSEAL_AUTHENTICATOR_UNAVAILABLE: the Hello key could not be opened (NCrypt error 0x80090027)— not an environment problem and not something a Hello setup could fix: the backend asked the Microsoft Passport (NGC) key store for a plain key name, which that store refuses by design, so the “key not held yet, create it” path was unreachable and enrollment died at the first step every time. The backend now uses the supported Windows Hello application surface (WinRT KeyCredentialManager); the Hello prompt appears and the key is created and used as documented.
Changed
- The enrollment record’s
backend_kindfor Windows Hello is nowhello-kcm-rs256, and the previoushello-rs256token is retired unminted. No record anywhere can carry the old token: the code path that would have minted it never completed on any machine, and a census of this fleet found no enrollment records at all (only code-ceremony seals). A record claiming the retired token is refused by name as an unknown backend. Enrolled public keys are now the Hello store’s own DER SubjectPublicKeyInfo bytes, still carried as lowercase hex. - A sealed send now tells you the token it minted: the answer reads
SENT:doyle seal=n2czzem8hc. Before this, the only place the token appeared on the sending side was a line left behind in the terminal, which nothing could read back — a script or an agent had no way to learn what it had just minted. The token rides every answer a sealed send can give, including a queued or deferred one, because the ceremony happens before delivery is attempted: a message that only spooled still minted a seal you may need to cite. Once the ceremony admits, the overlay now clears without leaving that line behind; a ceremony that fails or is cancelled still says so. spt api seal describereads like something written for a person. The minter’s machine shows its name beside the key prefix —SPT_DEV:lia@HFENDULEAM (14efb80c…)— and the mint time reads2026-08-25 14:07 CESTin your own timezone instead of a raw number. The name is only shown when the record’s short key prefix matches exactly one machine this node knows; if it matches none, or several, the prefix stays as it was rather than naming the wrong machine. Both are rendered from your node’s roster and clock, so treat the output as something to read, not to parse — the seal record itself is still the machine-readable answer.spt api seal verifyno longer fails over a trailing newline.seal mintseals the trimmed text, so piping the same text back in withechoused to present different bytes and answer NOT-BOUND for no reason a person could see. Verify now checks the exact bytes first and, only if they miss, the same trim the mint applied — content deliberately sealed with its whitespace binds exactly as before, and anything differing by more than leading or trailing whitespace is still NOT-BOUND.- A verify verdict no longer repeats
token,content_hashandceremony_kind. The verdict line already carries the token, and a mismatch already prints both hashes; those fields belong todescribe, which still shows all of them. - Communes no longer go missing when several agents on one machine save their context at the same time. Their saves used to collide over the shared store and one could fail; a failed save left the file on disk but never reached the durable context, and after three tries the loss was silent. Saves now queue and wait for each other instead of colliding, and a save that still cannot go through leaves your file untouched rather than consuming it.
- When an agent’s context ingest is failing, the brief it resumes from now says so, naming the fault and how long it has stood, instead of presenting possibly-stale context as current.
spt updatenow exits with the worst outcome of its legs rather than the last one: a refusal (3) can no longer hide a failure. This was reachable underspt update --restart, where a leg runs after the adapters leg — if adapters failed and the finish then refused, the command reported the refusal. Scripts branching on3can now trust it to mean “nothing changed”.- An access rule about an endpoint is now honoured the same way by every local
delivery verb.
spt ringused to judge such a rule against whatever--fromlabel the caller typed, and notifications against a display label that no rule could ever match, so a rule an operator wrote could silently cover some verbs and not others.
Added
spt endpoint listnow shows where an endpoint’s commune must be written — as acommune drop dir:line under your own pin, and as adrop_dirfield on every local endpoint in--json(previously only your own). A commune written anywhere else is never picked up, and this is how you check. The self-update and echo-commune documentation pages carry the details.- Writing an access rule whose subject is a
<endpoint>@<node>label — the form shown in notifications — is now refused when you write it, naming both ways to say what you meant. Such a rule could never match anything.
[0.62.0] - 2026-08-24
A wax seal can now be authorized with the machine’s own presence check — Windows Hello — instead of a typed code, and the seal it mints carries a signature any member node can check for itself.
Added
- Sealing with a platform authenticator.
spt seal enroll-authenticatorenrolls this machine’s authenticator (Windows Hello) into a subnet’s security material, gated by the usual code ceremony. Once enrolled, minting a seal on that machine opens the operating system’s own presence prompt instead of asking for a code — completing the gesture is the ceremony. An enrollment is permanent in this release: a machine already enrolled on a subnet refuses by name rather than replacing its key. On Linux the command refuses by name, stating that its authenticator support arrives later. - Seals minted that way carry a signature covering the sealed content, its
minter and its mint time together, so
spt api seal verifynow checks the signature as well as the content, on any node of the binding subnet. A seal whose signature does not check answers NOT-BOUND, as does one that claims the ceremony without carrying a signature.spt api seal describeshows the ceremony used and whether a signature is present. Eon the code overlay enrolls and seals in a single ceremony when the machine is not yet enrolled on the binding subnet: the presence prompt sets up the key, and the one code entered afterwards both enrolls the machine and mints the seal — both or neither, never half of each. An already-enrolled machine is never offeredE.
Changed
- Code entry remains the fallback for every seal ceremony, on the same overlay. Dismissing the presence prompt returns to code entry rather than failing the ceremony, while Esc still cancels everything, minting nothing and spending nothing. A signature that cannot be checked refuses by name and does not count against the attempt limit. Controllers attached from another machine get the ordinary code overlay without an error, since the signing key stays on the machine where it was enrolled.
- The published CLI reference now documents every command at every depth.
Twenty-two commands were previously missing from it, among them
spt api seal verify,spt endpoint access allowandspt endpoint monic add.
[0.61.0] - 2026-08-24
Messages and decisions can now carry a wax seal — durable proof that a human authorized the exact content, checkable later on any node of the subnet the seal binds to.
Added
- Wax seals: a durable, citable proof that a human authorized specific
content.
printf '%s' "<text>" | spt seal mintseals a decision text after a human-presence ceremony — a TOTP code entered at the minter’s attached controller — and prints a short token made to be read aloud and retyped.spt api seal verifyanswers BOUND or NOT-BOUND against the actual content on any node of the seal’s binding subnet, andspt api seal describeshows the record. A seal is evidence, never authorization: verification recomputes the content hash, so a forged or misquoted token proves nothing. - Sealed messages:
spt send <target> --sealruns the ceremony over the exact bytes being delivered and attaches the seal token to the delivered message, so the receiver can verify the body arrived exactly as the human saw it. A ceremony that does not admit sends nothing. Adapters are expected to surface the seal attribute to the receiving agent; the envelope contract page documents the obligation. - The seal ceremony overlay shows the sealed content verbatim, names the binding subnet — and the destination, on sealed sends — before asking for a code. It scrolls on small terminals without shrinking the content, and Esc or ctrl-c cancels cleanly: nothing is minted, nothing is spent against the attempt limit, and a pending sealed send is dropped. Content is capped at 500 characters and must be valid text; anything over the cap or not displayable refuses with a named reason rather than being silently truncated.
- Seals bind to one subnet by a deterministic rule — an explicit
--subnetwins, otherwise the minter’s anchor subnet, otherwise the alphabetically first subnet shared with the destination — and the ceremony always names the chosen subnet before a code is entered. When no shared subnet exists, the mint refuses by name rather than minting a seal the receiver could never check.
[0.60.0] - 2026-08-23
Access rules now reach the agents on the node itself, a refused update says so instead of reporting a failure, and installs, updates and briefings stop leaving stale state behind.
Added
- Access rules can now name the node they are written on.
--node self(or the node’s own name) writes a rule for this node, so a machine’s own agents are governed by the same rules that govern remote ones. Displays render such a rule with the node’s name, like any other node rule. - When the access-rules file cannot be read, deliveries between this node’s own agents are still admitted, and each one now carries a notice that own-node rules are suspended — naming the unreadable file and how to repair it — rather than suspending them silently.
Changed
- A refused update now reports itself as refused rather than failed: it names the rule that held and exits with the refusal code (3) instead of the failure code (1). The documentation now states what each outcome exits.
- The engine room’s ruleset now renders as a markdown table, so it survives being relayed by an agent instead of reflowing into a run-on line. Rules that name a node show the node’s name; where no name is known, the full identity is shown rather than a truncated one.
Fixed
- Messages sent between agents on the same machine now pass the endpoint access rules. Previously the rules governed only traffic arriving over the network, so a same-node sender bypassed them entirely.
- An endpoint is now reported locked only when its rules actually refuse every surface that can be closed. Previously an endpoint could be reported locked while admitting everyone, and the JSON view disagreed with the human one. The locked report also names the surfaces that stay open by default, in both views.
- Removing an endpoint’s last access rule no longer leaves an empty record behind.
- The engine room’s opening briefing now belongs to the session it was prepared for. Previously briefings that failed to deliver accumulated, and the first working session received every stale briefing at once, oldest first.
- Updating the core and its adapters in one run now judges each adapter against the core being installed. Previously an adapter was judged against the core being replaced, so a combined upgrade could refuse the new adapter and leave the old one behind on the new core.
- On Windows, installing or updating now keeps the inbound firewall rule pointing at the binary it just placed. Previously the rule could be left pointing at a stale path, silently blocking inbound connections after an update.
- Warnings that suggest classifying a peer now teach commands that work: the
suggested
spt endpoint moniccommands carry the current syntax and the exact trigger the product itself would write. Previously one suggestion used a retired form, and following the other could silently replace a peer’s existing triggers.
Internal
- Hardened message-envelope encoding so a line break in a message’s metadata cannot split the envelope; an internal read primitive now refuses a time bound it cannot honor instead of silently ignoring it; new build-time checks for rendered operator text and duplicate binary names; register and documentation upkeep.
[0.59.0] - 2026-08-21
The engine room is now reachable as an agent from the moment it comes up, a session that ends always says so, and several access surfaces stop answering where they should refuse or report.
Added
- The precise
spt endpoint access allowform now says when the rule it just wrote changes no verdict — when the endpoint is unrestricted, the new rule allows nothing that was not already allowed. The rule is still written; only the silence is gone. Where the subject names no reachable origin, the notice names that absence instead.
Changed
- Bringing up the engine room now names the phase it reached when it fails, rather than reporting only that it did not come up in time.
- In
spt subnet status --json, the path-mismatch verdict reports the running binary underrunning_path. Every other verdict is unchanged.
Fixed
- The engine room now registers as an endpoint when it is brought up, on nodes
belonging to more than one subnet. Previously it appeared in no roster,
spt send engine-roomanswered that it had no perch, and its own opening briefing had nowhere to be delivered — permanently, not late. - The message reporting an undelivered engine-room briefing no longer promises that it will arrive at the next idle moment when there is nothing for it to arrive at. It says so only when that is true.
- An attached session that is killed at the same moment it exits on its own now always records that it ended. Previously such a session could be torn down silently, leaving anyone attached to it waiting out a full reconnection window for a session that could never return. Where the exit code cannot be determined it is now reported as absent rather than as zero.
spt subnet statusnow reports a path mismatch as a path mismatch. Previously the one condition that view exists to announce was displayed as unknown, so the check went quiet in exactly the situation it was built for.- The retired
spt endpoint access listandspt endpoint access rulesforms now refuse by name. Previously they were read as the name of an endpoint and answered that no access entities were ruled for it — a true sentence about any unknown name, and indistinguishable from a real result, so anyone still using the old form read it as a report and stopped looking. An endpoint genuinely namedlistorrulesis still viewable. - An answered access request from another machine is no longer rendered as unknown when this node can prove the answer arrived. Where it cannot, it now says that no answer has reached this node rather than implying the question is unanswerable.
Internal
- Test and traceability coverage only: an inertness pin for forged inbound mnemonics attributes, a cold-boot timing probe, and stderr-sink adoption in the end-to-end suite.
[0.58.0] - 2026-08-20
The engine room now opens already briefed, keeps its controls across a connection gap, and runs from a directory of its own — and an endpoint’s default-scope subnet is now called its anchor subnet.
Added
spt endpoint monic --helpnow names every trigger kind and marks the inert ones, drawn from the same table the feature itself reads, so the help and the behavior cannot drift apart.
Changed
- An endpoint’s default-scope subnet is now called its anchor subnet, and that term is used wherever that subnet is named.
- The engine room now runs from a working directory of its own. Bringing it up no longer writes anything into the directory it was started from.
spt subnet createnow labels each key it displays and says what that key is for, and the member key screen keeps its re-pairing QR hint.spt endpoint access allowhelp no longer describes the access chain as a whitelist, which it never was.- The notice announcing that sender rules had become active no longer appears. It asserted a rule history that could not be verified, and it repeated on every daemon start.
- Retired an obsolete term for an endpoint’s home from the text spt displays.
Fixed
- The engine room is now briefed as its session opens. Previously the briefing was prepared but never delivered, so the session began without any of it.
- The engine room is briefed once per session, rather than again every time a user attaches to it.
- Reattaching to the engine room after a connection gap no longer costs the controls. Previously the reattach was refused and the session was left with no controller, so recovering the controls meant bringing the engine room up again with a code.
- A briefing whose pending state cannot be read is no longer treated as absent.
- A trust warning now arrives on the message it is about. Previously it came as a separate message that had to be matched to the one it described.
Internal
- Improved diagnostics and test coverage across the engine-room, access, messaging and project-index surfaces.
[0.57.0] - 2026-08-19
Access rules now admit exactly what was named, an invite code reaches every subnet its maker belongs to, and the engine room arrives explained and with a role of its own.
Added
- The engine room now carries a role of its own, served by spt itself. It is fixed and has no editor, so it reads the same on every node.
spt knock new-code --subnetnow takes more than one subnet — a comma list, or the flag repeated.
Changed
- An invite code minted without
--subnetis now sealed to every subnet its maker belongs to, rather than to a single one. Naming subnets explicitly narrows who can redeem it, since a redeemer must share one of the sealed subnets to read the code’s route at all. - Expanded the engine room’s built-in guidance, and corrected passages that still described attaching to it as a remote-only arrangement.
Fixed
spt endpoint access allowwith the node given positionally now honors--surfaces. Previously the named surfaces were dropped and the node was admitted on every surface, and the confirmation normally required before admitting a whole machine was never asked for. Rules created with the positional spelling should be reviewed against what was intended —spt endpoint access <endpoint>shows the roster.- Access rules are no longer lost when the file holding them cannot be read. Previously an unreadable file was rewritten as an empty one by the next change, silently discarding every rule it held; that change is now refused instead.
- Replying to a message now works when no standing rule admits the sender. Previously the allowance that exempts a reply was never applied, so the reply was refused.
- Attaching to the engine room without supplying a code no longer consumes one of the limited attempts. Previously an omitted code counted as a wrong guess and could exhaust the budget before a code was ever entered.
spt rcno longer refuses to attach to an endpoint that has just been released. Previously the refusal could persist until the daemon next caught up with that endpoint’s state.
Internal
- Improved test coverage across the access, knock and engine-room surfaces, removed an unused access check, and pinned the Rust toolchain used to build releases.
[0.56.0] - 2026-08-18
Being found is now on by default, and closing a machine’s posture no longer
hides it — so a request to be let in can still reach the party who has to
answer it. Several access surfaces that used to accept a grant in silence now
say when it will not do what it appears to do. A machine that relied on a
blanket closed posture to stay unfindable must now close that surface by
name.
Changed
- A
closedposture no longer refuses discovery. Setting a posture toclosed— on an endpoint, on a machine, or on a subnet — used to make that target undiscoverable as well as unreachable. Asking to be admitted is the one exchange that depends on being findable first, so the target of a closed posture could not be asked to reconsider it: saying do not talk to me had silently also said and you may not ask, which was never the setting being chosen. Discovery is now withheld only where something names it — a deny rule that names the discovery surface, or a machine’s own setting for that surface — and both work at every level, in either direction. Anything relying on a blanketclosedto stay unfindable must now name the surface; the new command below is how a machine does that for itself. - Access views distinguish a default from a decision. A closed table now
reads
closed (DISCOVER open (default)), and where an explicit open has been written for that surface it readsDISCOVER open (pinned)instead — an open someone chose outlives a change to the default and is not the same fact as the default itself. A machine that has closed discovery readsclosed (DISCOVER closed). - A knock can now reach an endpoint that ordinary discovery would not resolve. A knock used to report the endpoint as not found the moment resolution failed, so the one door whose purpose is to be knocked on was the door that could not be. It now falls back to what this machine already recorded about that endpoint. A knock at an endpoint hidden by this machine’s own operator still refuses, and that refusal names the boundary rather than reading as a missing endpoint, so nobody goes looking at the far end for a decision made at this one. Peers running an earlier version stay unknockable where their own posture withholds discovery.
spt endpoint listno longer prints two explanatory lines beneath its UNLISTED heading. Both restated what the rows above them already said. The heading, every per-row cell and its wording, and the legend explainingunknownare unchanged — that legend stays because the word an operator is about to act on carries two meanings and the rows cannot say which applies.
Added
- A machine can turn a single control surface off for itself.
spt api access-node-surface-mode <SURFACE> <open|closed|unset> <id>sets one surface’s posture for the whole machine, run from that machine’s engine room. It exists because the blanket posture setting no longer reaches a surface that is on by default, which would otherwise leave no way to turn one off. Its third state,unset, removes the entry rather than pinning the surface open: a surface that is on by default becomes on by default again, and any other falls back to the machine’s overall posture.
Fixed
- Approving a knock, or having an invite code redeemed, told nobody. A code could be minted, redeemed by someone else, and the person who minted it never informed. Three courtesies — a knock being approved, an invite code being redeemed, and a knock arriving back the other way — were produced but could not surface anywhere. Each now reaches the one party it is owed to rather than whichever session was most recently active, and waits for that party when they are away instead of landing on a bystander. A re-sent answer replaces its earlier notice rather than arriving a second time. A refused knock still notifies no one.
- Granting fork access without discovery accepted a rule that could not work. Forking a remote endpoint takes both surfaces — one authorizes the operation, the other is what lets the grantee find the endpoint at all — so a fork-only grant was accepted, read back exactly as written, and then failed later somewhere else as an unresolvable target. The grant is still accepted, and now states the consequence once the rule lands; where the pair is needed, the knock listing prescribes both surfaces beside the command to grant them. For a rule covering a whole subnet, the members that still cannot resolve are named, alongside the number of members this machine can see.
- A rule disclosing this machine’s engine room could disclose nothing, and said so nowhere. Being told the engine room exists takes both an access rule and the engine room’s own list of who may be told, and only the first is writable this way. A grant against an empty list therefore read as policy in force while every peer saw only its own entries. The grant is still accepted — and still never edits that list — but now names which half is missing and reports the engine room’s current posture. Only a grant is examined: a refusal takes effect immediately, and saying otherwise would teach that a refusal is conditional when it is not.
- A per-surface posture could be stored twice under different spellings of the same surface name. Where that happened, which entry governed depended on how the surface was spelled at the asking site, and clearing the setting could report a change while leaving the other entry in force — a setting that had not moved, reported as one that had. Writing a posture now replaces every spelling of that surface and clearing one removes all of them, at both the machine and the endpoint level.
- A single use of an invite code could be answered with a refusal even though it had succeeded. The machine that issued the code could complete the exchange — recording the access and marking the code as used — while the party using it was told the attempt had been refused. The refusal was wrong: access had in fact been granted and the code was spent, and there was no way to tell that message from a genuine refusal. Repeating that same request from the same place now returns the answer it first gave, so a use that succeeded reads as succeeded. A code presented by anyone else, or from anywhere else, is still refused in the same words as any other refusal, and that refusal still says nothing about why.
[0.55.0] - 2026-08-04
The command for managing a machine gains a primary spelling, and a new command reports perch directories left behind by endpoints that no longer exist. Nothing retires in this release — existing invocations keep working unchanged.
Added
spt endpoint gcreports perch directories that outlived their endpoint. The command reports by default and removes nothing, so a first run is safe to read before anything is acted on. A directory the command declines to touch also covers everything beneath it: a candidate nested under a declined parent is reported as such and left in place, so what a run declines stays whole rather than being emptied from the inside.spt shell relink --forcerestarts a shell instance that is already running. Plainrelinkbrings a stopped instance back and refuses one that is already up.--forcecovers the other case — stop it, confirm it stopped, then relink — without removing and re-creating the instance, which would change its id and break every reference to the old name. The flag overrides that one refusal and nothing else; an instance that is already stopped takes the ordinary relink under the flag.
Changed
spt nodeis now the primary spelling for managing a machine, andspt daemonis a full alias of it. What the command reports is a machine’s state — its supervisor, the subnets it belongs to, the endpoints on it — sonodenames the thing being managed rather than the process that manages it. Both spellings resolve to the same command on every subcommand, so existing invocations and scripts keep working. Command output, error messages, and the documentation now use thenodespelling.
Fixed
spt subnet statuscounted reachable peers from one population and total peers from another. The fraction could read impossibly —7/1was the reported case — because the first number counted every peer ever connected while the second followed current membership, so leaving a subnet moved one and not the other. Both are now counted from the same population and recalculated when membership changes.- A peer that could not be reached was described in terms that indicted the local machine. An absent peer now has its own wording, rather than making the reachability line read as though this machine were degraded.
- An error message named a command that does not exist. When the supervisor
predated a bring-up step, the suggested remedy was
spt daemon restart— not a real subcommand, so following it produced an unrecognized-subcommand error and nothing else. The message now namesspt node stopfollowed byspt node start, and explains why the second step is required rather than leaving a stop to be undone automatically.
Internal
- A diagnostic line recording attach decisions was moved off a code path that
nothing calls, and renamed as part of the move.
RESUME_ATTACH_INTENTno longer appears;ATTACH_INTENT_CHOSENreports the same decision at the three paths that actually run, distinguished by asite=field. Two consequences for anyone reading logs: aRESUME_ATTACH_INTENTline in a 0.54.0 log is unreliable, because it sat in an uncalled function and could only ever report a single fixed value; and searching a 0.55.0 log for that old name finds nothing because the name changed, not because the activity stopped.
[0.54.0] - 2026-08-04
Two command surfaces change shape in this release — the endpoint lifecycle and knock directionality. Existing invocations and scripts will need updating.
Changed
-
spt endpoint runis retired, and the endpoint lifecycle now reads as one verb per step. A single verb used to mint an endpoint, start a session on an existing one, resume a prior session, open the picker, and set a startup default; which of those it did depended on which of nine flags were present. Each step is now its own verb:spt endpoint create <id>— mints the endpoint and starts no session.spt endpoint start <id>— starts a session on it.spt endpoint resume <id>— resumes its most recent session.spt endpoint auto-start <id>— sets the startup default (--offclears it).spt go <id>— brings it up and attaches.
Bringing a brand-new endpoint up is therefore two commands —
create, thenstartorgo— where the retired verb did both at once. That is a shape change for bring-up scripts, not a lost capability. -
The retired spellings refuse rather than fall through.
spt endpoint runanswers with the verb to use instead, and the flags that retired with it —--save, and--idand--subneton the later verbs — are refused rather than ignored. There are no deprecation aliases. -
Resuming is latest-only.
spt endpoint resume <id>resumes that endpoint’s most recent session. Naming a specific session to resume is retired, and a session argument is refused rather than quietly ignored. -
A desktop launcher left over from an earlier version is replaced, not merely refreshed. Writing a launcher over one that still carries the retired bring-up spelling now reports it as regenerated, so a launcher that was already broken is named as such rather than quietly refreshed.
-
Knock directionality is now declared by whoever asks for reach, and the two flags are renamed.
spt knock,spt knock sendandspt knock redeemnow require exactly one of--send-onlyor--send-receive. There is no default: a bare invocation refuses and names both. The flag describes the asking side —--send-receivepre-authorizes the reverse on that endpoint,--send-onlydeliberately declines it.spt knock approveandspt knock new-codeno longer take a directionality flag at all. Accepting is one side’s own act; reaching someone who has been approved means knocking them back.spt knock approve --mutual’s counter-knock is removed, not renamed. The way to ask for reverse reach is the knock verb itself.--mutualand--one-wayare parse errors wherever they used to be accepted. The refusal names the flag that replaced it and, atapproveandnew-code, the verb to use instead. There are no deprecation aliases.spt knock list --jsonrenames itsmutualkey tosend_receive. A script reading the old key gets nothing back rather than an error, so it is worth searching scripts for that key name. It is a contract change to note, not a break to repair.- The Claude Code adapter’s
/sptc:knockguidance still teaches the retired shapes (bareknock,--mutualonknockandapprove, bareredeem, and the mutual/one-way vocabulary throughout), so agents following it are refused the moment their node upgrades. That guidance is being updated to match. - Pre-authorizations armed under the old flags are untouched and still honored — an approval still opens the reverse it asked for.
-
The engine room’s bring-up says it holds the controls only once it does. Bringing the engine room up used to print “the engine room is up; taking its controls” — plus the empowerment line for an admin code — the instant the session started, before the attach was established, so a failed attach left that sentence on screen having claimed a seat nobody took. The bring-up now reports only that the room is up; “taking its controls” and the empowerment line follow once the attach is actually seated. The session’s own briefing moved with them, so it can no longer describe an empowerment for a controller that never arrived. Both sentences still appear, in the order the facts become true.
Fixed
spt rc engine-roombrings the room back up after it has been shut down. Quitting the harness inside the engine room’s session leaves the endpoint cleanly offline — and, until now, unreachable: the nextspt rc engine-roomasked for a code, then answered “offline — nothing to attach to” for the same command that had brought the room up minutes earlier. With a code in hand the engine room is now brought up from that state, which is exactly what its bring-up is for. Every other endpoint’s offline answer is unchanged.- Refusals point at the engine room’s real door. When
spt rcdoes refuse an engine-room attach, it now namesspt rc engine-room— which asks for a member or admin code — rather than a bring-up verb the engine room deliberately refuses. spt knockno longer reports success for a target it could not reach. Knocking an endpoint that resolved nowhere recorded the knock locally and answered “It is waiting in their inbox”, exiting successfully, while the named endpoint’s machine held nothing — so the knock read as sent when nobody had been told. Such a knock is now refused, and nothing is recorded locally.- A monic that cannot be read still counts as already filed. When a stored
monic was present but unreadable,
spt endpoint monic addtreated the id as free and overwrote it, whilespt endpoint monic updaterefused for having nothing to replace — the exact reverse of what each command is for, and in the one case where the existing content could not be read to judge what replacing it would cost. Both commands now treat the id as taken.
Internal
- Improved logging granularity for session bring-up and job teardown.
[0.53.0] - 2026-08-03
Monics are no longer only about peers. A monic is now a reactionary string — a set of triggers plus a body that is revealed when something in the session matches one of them. Classifying a peer is one thing a monic can do, not what a monic is.
This changes the spt endpoint monic commands, and existing invocations will
need updating. A monic is now addressed by its own id rather than by the peer
it was about, and its body is read from stdin:
spt endpoint monic addtakes the id of the monic to write, not a peer endpoint id, and refuses when that id is already taken — replacing one isupdate, so a typo or a re-run no longer silently overwrites the monic already stored there.spt endpoint monic updaterefuses when nothing is stored under that id; writing the first one isadd. A monic is stated whole.addandupdateboth accept--batchto write or replace several at once from a single stdin payload.spt endpoint monic clonecopies by monic id, or--allfor every monic the source holds. Copied monics are marked inherited, so the destination can tell its own from the ones it took.- A record that cannot be read is listed under its own id instead of being dropped from the listing.
The trust warning follows the same shift: it joins incoming messages from endpoints with no matching monic held for them, rather than ones never “classified”.
Fixed:
- A reported psyche failure keeps its kind across a restart, instead of coming back as a different kind.
- An endpoint’s engine-room empowerment survives a session-id rotation. Clearing a session no longer silently leaves the empowerment behind.
- An endpoint whose owner is long gone can be bound again. Previously the wrong owner could be identified, permanently refusing the bind.
- When a shutdown cannot reach everything it started, it now says so instead of reporting success.
spt endpoint --helpdescribes whatmonicandtrust-warningactually do.- Fixed a daemon that could consume whole processor cores indefinitely. Previously, each change to a machine’s network connection left behind work that never finished, and the cost accumulated over days until several cores were busy. Machines on wireless links were affected worst.
[0.52.0] - 2026-08-02
A closed subnet now enforces what it declared, everywhere its status is shown.
Persistent shells survive a machine restart, an admin bring-up proves its key
once, and spt endpoint list shows you your own nested perches.
Added
spt endpoint listrenders aYour nested perches:section for the calling endpoint: bound workers with their status, and the psyche as a named companion row without a status square (its record does not carry liveness). Other agents’ entries are unchanged, and the section never movesTotal:.- Bringing an engine room up with the ADMIN code now empowers that seat for the room’s home subnet at attach — the same credential, proved once instead of twice. A member-code bring-up grants nothing, and the session briefing opens with the grant when one was made.
- A commune that never reached the psyche now says so. An ingest killed at its
time budget dies before writing anything, so spt-core records the
expectation up front — and an expectation no drop ever satisfied surfaces at
the agent’s next resume as a
COMMUNE_NEVER_INGESTEDwarning naming the commune it lost, instead of resuming stale silently. Harness roles that are LLM turns can declareinvocation_budget_secs(default 90, clamped to 300). - Persistent shells are restored at boot after a machine restart: the boot sweep relaunches recorded persistent shells that a restart stranded, gated on the recorded birth stamp so nothing relaunches on a machine that never held them.
Changed
spt ring --timeoutis now denominated in MINUTES (an agent answers on agent time), and the default is 30. A bare number is minutes; an explicitsormsuffix sets the unit, so90sand2mboth work. Scripts that passed bare seconds now wait longer — passsexplicitly for sub-minute waits.- The line under this node’s name in
spt endpoint listsaysJoined subnets:— these are the subnets this node is a member of. Remote nodes keepShared subnets:, the subnets you have in common with them.
Fixed
- Stopping or replacing a hosted process no longer risks terminating an unrelated program that happened to reuse its process id: every lifecycle kill now authenticates the recorded pid against its birth stamp, and a kill it cannot prove is refused loudly — naming what it spared — instead of fired blind. (On Linux the check narrows the mis-fire window to a 10ms start-time tick rather than eliminating it; Windows closes it outright. Processes recorded before this update keep the old behaviour until they next restart — the check needs the birth record that only a fresh launch writes.)
spt subnet create --closednow captures the declared mode on the minting node, so a closed subnet is enforced where it was created instead of only where it was joined. Everysubnet statusview states the mode facts per row — and absence is readable: declared closed with no captured mode means enforcing open here. If the capture cannot be written after the subnet is created, the create succeeds and says loudly which declared mode is NOT enforced and thatspt api access-refresh <subnet>heals it.
[0.51.0] - 2026-08-02
Invitations that work across machines. A code minted on one machine can now be redeemed on another and opens reach in the direction it was meant to open, answering a knock has to say which way reach runs, and an engine room can be brought up on a node where nothing was hosting one yet.
Added
- Peers that were never met through the subnet still appear in
spt endpoint listwhen there is evidence of them, carrying the presence their own machine reported rather than a guess. A message can be sent to a peer known only that way.
Changed
- A knock asks for direct messages by default instead of for the whole machine.
Naming surfaces explicitly still works, and
spt knock <target>on its own is the short form ofspt knock send. - Answering a knock has to state the direction.
spt knock approveandspt knock new-coderequire exactly one of--mutualor--one-way; omitting both is refused, the refusal names both choices, and nothing is done until one is given. - The knock commands list the control surfaces they accept in their own help, so the accepted names no longer have to be found elsewhere.
Fixed
- An invite code can be redeemed on a machine other than the one that minted it. Presenting a code across machines previously resolved nothing, leaving no way to use a code that had been given out. A code is sealed to the subnet it was minted for, and only a member of that subnet can read where it leads.
- Asking for a mutual grant across machines arms the reverse direction and writes it once the redemption is proven, instead of opening one direction while reporting otherwise. A refusal disarms it; an unanswered request leaves it armed.
- An approval that opens both directions reaches the other machine. It previously landed only where it was made, so the half the approver could not see never happened.
- An engine room can be brought up on a node that is not hosting one yet. There was no way to start one: the attach found no session, and a provisioned engine room could not be reached by any means. Binding its subnet and adapter is a first-run ceremony that needs no elevation, and it refuses to run from an agent session.
- Endpoints hidden on this machine no longer hide peers on other machines that happen to share a name with them.
- A person at a terminal can redeem a message-only code they were given. Redeeming one previously reached no decision at all for a presenter who is not themselves an endpoint.
- A context update that fails to be taken in says so, instead of leaving an agent to resume from an older one with no sign that the newer update never landed.
[0.50.0] - 2026-08-01
Handrails on surfaces that were already there. An agent can no longer stop the service it is running on, a caution that was repeating itself now speaks once, several commands that printed or promised one thing and did another have been made to agree with themselves, and forking an endpoint reaches across machines.
Added
- An endpoint can be forked from another machine. Naming a source as
id@machinemakes the fork on the machine that holds it, seeded with a copy of the source’s mind exactly as a local fork is, and the source is left intact. It is permission-gated by the source’s owner, and needs both the permission to fork and the permission to see the endpoint — a fork permission on its own cannot be used, because the endpoint cannot be found without the second. A fork that goes unanswered is reported as unconfirmed rather than as done. Deleting the source stays a same-machine option: asked for across machines, the command refuses the whole request rather than quietly forking without deleting. spt subnet status --jsonreports the inbound-reachability answer in every state, including when the answer is that inbound is fine. The reading views still stay quiet unless something is wrong.
Changed
- The caution about a message from an unknown sender now appears once per session for that sender, rather than on every message they send. A batch of messages held while an agent was away surfaces one caution instead of one each, and it is heard again in the next session.
- Commands that stop the daemon refuse when an agent runs them.
spt daemon stopandspt update apply --finishnow decline, under every flag, when the caller is an endpoint the daemon hosts — stopping it ends the caller’s own session along with everyone else’s on that machine, so the one side that could report the outcome is the side that does not survive it. A person at the terminal keeps the existing behavior, including the override.
Fixed
spt ringwaits for the reply when the endpoint it asked is busy. It previously returned straight away in that case and discarded the address the answer needed, so the reply arrived nowhere and the same command behaved differently depending on whether the other side happened to be free.- Selection lists scroll. Moving the highlight past the last visible row left it drawn off screen, so the picker was taking decisions about an entry it was not showing. Every list that carries a highlight now follows it.
spt knock listprints an approval command that the CLI accepts. Surface names are now matched without regard to case, so a request namingmsgis answered as the single-sender grant it is instead of being refused as though it admitted a whole machine.
[0.49.0] - 2026-07-31
Access control gains a way to ask and a way to remember. An endpoint can now be asked for access rather than only granted it, an agent can record what it has concluded about a peer, and a message from someone it has concluded nothing about arrives with a caution attached.
Added
spt knockasks an endpoint for permission to reach it, and answers the asks that arrive:send,list,approve,deny. A knock is a request, not a grant — approving one is what writes the access rule. Requests land in a queryable inbox and are never pushed at the receiving agent, so an incoming ask cannot interrupt an agent mid-work; the inbox outlives the target being asleep, detached, or gone, and a knock reaches an endpoint on another machine. Only three events notify anyone, and each is invited by something the recipient did: an approval reaches the knocker, a counter-knock reaches the original knocker, and a redemption reaches the code’s minter.- Invite codes —
spt knock new-codemints one that grants the named surfaces when redeemed, andspt knock redeempresents one that was handed over. spt endpoint monicrecords what an endpoint has concluded about a peer:add,update,remove, and a bare view of everything held. A note travels with the agent, so a peer classified while running on one node stays classified wherever that agent runs next.- A trust warning now accompanies a message whose sender was admitted by an
access rule while the recipient holds no note about them. It states that the
sender arrived through a rule rather than through anything the recipient
decided, and advises care with secrets and state-changing requests until they
decide something.
spt endpoint trust-warningshows the caution and can reword it: the wording is configurable, the fact of an unclassified sender is not. - Access rules can be written node-wide —
allow,denyandremovenow operate for the whole node, with--forto scope one to a single endpoint.
Changed
- An endpoint’s access rules accept a subject rather than only a node, and gain
denybesideallow. A rule can be removed by restating it with the flags that created it (remove), alongside the existingrevoke. - Access listings show where a rule came from, so a rule can be traced to what granted it rather than only observed to exist.
Fixed
- Forking an endpoint no longer loses its role. Only one file of the forked identity’s durable material was carried across, so an endpoint’s statement of purpose was dropped on every fork — silently, and visible only as an absence. A fork now copies everything the source holds.
Internal
- Test fixtures that could not have failed were given inputs that can.
[0.48.0] - 2026-07-31
Reachability between sites is now reported honestly. A node whose stored state
was reset or corrupted repairs its own published address on the next start
instead of staying unreachable for good; sending to a peer no longer stalls on
an address that is known to be dead; node status no longer reports healthy while
most of the fleet is unreachable; and spt subnet status reports whether the
host firewall actually lets traffic reach the daemon, in the language of the
firewall tool in use.
Added
spt subnet status(and the coming-online banner) now reports whether the host firewall admits the inbound traffic spt needs. On Windows, a rule that no longer admits the running program is reported as such, and an elevated repair can correct it and re-check. On Linux the check is read-only: the firewall tool in use (ufw, nftables, or firewalld) is detected without administrator rights, the verdict names the port actually in use, and the exact command to check it is printed in that tool’s own syntax — nothing on the host is changed. Firewalls operated by a hosting provider are outside this check and are documented as a separate requirement.
Changed
- Node status now reports a partial-outage verdict, naming how many peers are unreachable and for how long. Previously a single reachable peer was enough for it to report healthy while the rest of the fleet was unreachable.
Fixed
- Restored cross-site reachability for a node whose stored state was reset or corrupted. Previously such a node could never publish a usable address again and stayed unreachable to peers at other sites indefinitely. It now repairs its own published address on the next start.
- Improved the speed of message delivery to a peer that has moved or gone away. Previously delivery could stall retrying an address that was already known to be unreachable; a node that moves to a new address is now found again instead of being retried at the old one.
Internal
- Improved logging granularity for peer connection attempts and their failures.
[0.47.0] - 2026-07-30
The fast-follow to 0.46.0: the governance seat that release created can now
actually be set up, and what the access layer decides is now something a person
can read. Eviction and subnet creation become interactive admin ceremonies:
subnet revoke requires the current admin code and re-surfaces the replacement
key behind a capture proof, subnet create proves admin-key capture before
minting — scripted eviction is intentionally gone.
Added
spt endpoint engine-room <subnet> --adapter <id>— the engine-room create/reset ceremony. First-time creation runs without elevation (the window closes at the first run — run it early); re-running against an existing engine room requires elevation; agents cannot run it at all.- Bare
spt rc engine-roomnow opens an interactive code prompt (Esc cancels);--coderemains for direct entry, with the caveat that command-line arguments are readable by other processes while the code is still valid. spt endpoint access [<endpoint>]is a roster: who has rules about the endpoint, grouped by subnet, node, and endpoint, with modes and rule counts. Exact rules for one of them via--endpoint-rules/--node-rules/--subnet-rules.spt daemon accessanswers the same for the whole node.spt subnet status <name>now states the subnet’s declared mode, the mode this node captured at join, and any declaration seen but not yet adopted.
Changed
spt endpoint access listandspt endpoint access rulesare retired; the roster views above replace them (allow/revoke/openare unchanged).spt subnet revokerequires a current admin code and, because eviction rotates both subnet keys, shows the replacement admin key exactly once — to the person who just proved the old one — behind the same capture proof as creation. A member code no longer suffices.spt subnet createshows the admin key first and asks you to prove your authenticator captured it before anything is minted; the member key is shown last.- New subnet names may not contain
:.
[0.46.0] - 2026-07-29
A release about who is allowed to reach an endpoint, and about giving each machine one seat a person sits in to decide that. Access rules stop being one blanket answer per endpoint and become per-capability; a subnet now has two keys instead of one; and a node’s own governance seat can only be taken by someone holding a code, at the machine, with an operating system prompt behind the ceremony that creates it.
Added
spt endpoint accessrules are now per-capability. A rule names the capability it answers for — messages, remote control, file transfer, wake and suspend, shell link, digest — so an endpoint can take messages from a node it will not hand a terminal to. Rules can name a sending endpoint, a node, or a whole subnet, and a node-wide rule covers every endpoint on the machine. Existing rules keep working: each one is read as covering the capabilities it covered before.- Remote control now distinguishes watching from driving. A rule can admit a
viewer and still refuse control or a takeover, because
spt rcsays which of the two it is asking for. - Endpoints you may not reach are no longer resolved or advertised to you, and the resources an endpoint lists are filtered to what the asking viewer is allowed to see.
spt subnet createmints two keys — a member key and an admin key — and asks whether the subnet starts open or closed (--open/--closedskip the prompt). A machine can join with either code, and a join copies both keys, so a member machine can later perform admin work without a second ceremony. No command ever prints the admin key.- Changing a subnet’s mode leaves an advisory note for members, so a machine that was offline learns the subnet’s stance the next time it looks.
- Each node now has one reserved engine room endpoint: a seat that governs
the machine’s access rules. Sitting down is
spt rc <engine-room-id> --code <code>, at the machine, with a current member or admin code for the node’s home subnet. A wrong code is refused without saying which key would have worked, wrong codes throttle with a doubling delay, and past the third failure the node raises a loud notice. - The engine room is deliberately hard to reach: it refuses all inbound except replies to what it sent, it is not advertised unless a node is explicitly whitelisted, it cannot be viewed remotely at all, and when the person driving it detaches it stops accepting inbound and drops the authority it was holding. A local takeover drops that authority too.
spt api empower <subnet> --admin-code <code>grants the engine room authority over a subnet’s access modes for the current session only. It is callable only from the engine room seat, and refuses for a subnet the node is not a member of.spt api access-refreshre-reads the subnet-level stance a node was told about. It is callable only from the engine room, and only ever changes those inherited fallbacks — never the node’s own rules.- Sitting down at the engine room delivers a briefing message: what the seat can
do, what it is responsible for, the machine’s exact current stance, any
advisory notes waiting to be adopted, and the full table of rules in force.
spt endpoint access rulesprints that same table any time. - Creating or resetting a node’s engine room requires an operating system elevation prompt, and binds the harness the seat runs. If that harness is later missing, the seat refuses to come up rather than coming up unbound.
Changed
- An access store that cannot be read now refuses unsolicited inbound instead of admitting it, and says so on every refusal, naming the file to repair. A first run with no store at all is not a failure: the node writes an empty one and stays open until rules are added. Replies to something an endpoint itself sent are never affected.
- Messages now carry a sender endpoint identity the daemon proves from the
session, so a rule that names a sending endpoint actually matches. A
--fromsupplied on the command line cannot satisfy such a rule; it remains what it always was, reply-routing information.
Fixed
spt ringno longer takes over a perch it did not create. It previously decided the caller had no perch from a marker that can be momentarily absent — during a busy turn, a re-bind, or on an endpoint whose harness owns the listener — and then deleted that live endpoint’s record, spool and directory on its way out. It now inspects the perch directory itself and refuses when anything suggests the perch is somebody’s.- While
spt ringheld such an adopted perch, it could also read the victim’s waiting messages and print them as its own reply — one agent reading another’s mail. Both halves are now refused and covered by tests. - An endpoint whose relay has died no longer stays listed as online because an earlier life had earned a capability stamp. Records now say what their recorded process is, and older records without that information are treated as unknown rather than assumed alive.
[0.45.0] - 2026-07-27
A release about commands that reported success they had not earned — a stop that stopped nothing, a launch that started nothing, an endpoint that stayed listed as online over a connection that had died — and about shells outliving the terminal they were started from.
Changed
spt endpoint stop <id>now fails, naming the id and the node, when nothing on that node knows the id. Previously it reported a completed stop for any text it was handed, so a typo looked like a successful stop. Ids the node does know still stop exactly as before, including endpoints that are only partly torn down. Scripts that counted on this command always succeeding will now see a failure for an unknown id.spt daemon statusnow reports the version that is actually coordinating work on the node, and keys its out-of-date warning to that version. Previously it reported a different component’s version, so the warning fired on nodes that had just updated correctly and recommended a full restart — which would have ended every session running on that node to fix nothing.
Fixed
- Shells started by
spt shell spawn, by a localspt shell relink, or by aspt shell cmdthat has to start the shell first now keep running when the terminal they were started from is closed. Previously, on some machines, closing that terminal ended the shell and everything it had started. - A shell launch that could not be handed off no longer reports success without having started anything.
- An endpoint whose connection dies while the program that owns it keeps running no longer stays listed as online, with an address nothing answers, until someone stops it by hand. It now settles to offline on its own, and only ever does so on evidence that the connection is gone — an endpoint whose state cannot be read is left alone.
- An endpoint created again after being stopped now comes up fully awake.
Previously it came up online while still recorded as suspended, so
spt endpoint suspendreported nothing to do on a visibly online endpoint until an explicitspt endpoint wakerepaired the record. spt send --active-onlyno longer starts a turn on an idle agent. The flag promises delivery through the agent’s own next poll and never an interruption, and that promise now holds for every kind of agent; such messages wait for the agent’s next active window, as documented.
[0.44.0] - 2026-07-26
A release that lets an adapter ship a background program spt keeps running for it, makes an adapter’s own programs launchable without putting them on the system search path, and fixes agent work being charged to an abandoned configuration profile until that profile ran out of budget.
Added
- An adapter can now declare a background program in its manifest, and spt keeps it running. It starts either with spt itself or the first time one of that adapter’s shells connects, is restarted if it exits, is held stopped while the adapter’s files are being replaced, and is stopped when the adapter is removed. Installing or updating an adapter brings the program up right away and prints what happened for each option — no restart of spt required — and a program that fails to start never causes the install or update itself to fail. A program that keeps exiting immediately after it starts is reported as a startup fault, together with the output it printed, instead of being restarted over and over in silence.
spt adapter service listandspt adapter service status <adapter>report what spt is keeping running: when it is set to start, whether it is running, whether an update is holding it stopped, any suppressed restart along with the startup output behind it, and the program’s own status line when it publishes one. Both are read-only, and both are answered by the running spt background process — when none is running they say exactly that instead of guessing.
Changed
- A shell or wake command declared by an adapter now finds that adapter’s own installed programs first: the program name is looked up in the adapter’s install directory before the system search path, and a wake command can use the adapter’s directory in its arguments the same way a shell command already could. Previously an adapter that shipped its own helper program could not launch it unless that program was separately placed on the system search path.
Fixed
- Fixed an agent’s work being charged to the wrong account after its configuration location changed. A setting measured once when an agent first connected was carried forward into every later session, even when those sessions no longer used it, so work kept being billed to the abandoned configuration until it hit its spending limit and every agent on the machine stopped answering. What the current session actually presents is now what counts, and moving an agent to a different configuration takes effect on its next connection.
- Fixed a failed agent turn reporting a bare exit code with no explanation. The output the agent’s program printed while failing is now carried into the failure message, so a cause such as an account being out of budget is readable instead of appearing as an unexplained number.
- Fixed
spt endpoint stopandspt endpoint shutdownon Linux reporting that a session had failed to shut down and offering a manual kill command for a process that had in fact already exited. Such a session is now recognized as a leftover record and cleared normally.
Internal
- Published the rules for reassembling a large message on the listener stream, so an adapter can be written correctly against the documentation alone. Previously the documentation named the mechanism without stating the rules, and an adapter that guessed could stop receiving anything at all after the first large message arrived.
- Documented the exit codes spt reserves in the harness contract, including a newly reserved code for an agent program declining to run for account or credential reasons.
[0.43.1] - 2026-07-26
A documentation-only release; no behavior changes.
Internal
- Recorded the design for supervised adapter services (a way for adapters to declare a background service that spt keeps running), ahead of its implementation in an upcoming release.
- Recorded the field-verification results for the v0.43.0 shell-liveness fixes.
[0.43.0] - 2026-07-25
A release that makes spt shell list tell the truth about shells whose
process died, and makes recovery from such a death work instead of being
refused.
Fixed
- Fixed shell instances reading as online forever after their process died
abruptly (a crash, a force-kill, an out-of-memory kill, a reboot — and also
a daemon restart, which previously left every bound shell’s record saying
online).
spt shell listnow reports such instances offline. - Fixed
spt shell relinkrefusing to recover a shell whose process died abruptly. Previously it answered that the shell was already online and there was no way back short of tearing the instance down and re-creating it — losing the instance’s identity and saved state. Relink now succeeds, and the recovered instance keeps the same name, bindings, and saved state. - Fixed
spt shell cmdsilently queueing a command to a dead local shell. Previously only a command arriving from another node woke an offline persistent shell; a local command was accepted and then waited forever. Both now wake the shell the same way. - Fixed queued shell commands being lost across a relink. Previously, commands queued while a shell was being relinked could be discarded on delivery instead of reaching the recovered shell.
[0.42.0] - 2026-07-25
A release that makes it visible when an endpoint is working and when it has stopped — pushed to the shells it owns, readable in the roster — and lets a finished turn be read without prompting the endpoint again.
Added
- An owned shell now sees when its owner starts and stops working. The state
arrives as an
activityframe on the link the shell already polls, and each frame carries the moment the state took effect rather than the moment it was read, so a tool that waits for “quiet for N seconds” measures from the transition itself. The current state is re-sent every time a shell links or re-links, so a shell that restarts catches up on its own. A singlespt api drive-pollcan now print two lines instead of one; each line names its own type, and the drive line still comes first. The new frame is documented alongside the rest of the shell frames. spt endpoint list --jsonandspt api endpoint-infonow report whether each endpoint isbusyoridle, so surveying many endpoints at once no longer means opening a link to each of them. The value is reported for endpoints on this machine; rows for endpoints on other machines, and directories with no endpoint bound to them, leave it out rather than guess.spt endpoint digest <id@node>now reads a digest from the machine the endpoint actually runs on, with--lastand--afterbehaving exactly as they do locally. The endpoint’s own machine builds the answer, so the content matches what someone standing on that machine would see, and reaching it needs the same permission as every other command aimed at another machine.--followstill works only on the local machine — poll with--afterinstead. A machine running a version of spt from before this release cannot answer the request; the command now says so in plain words and returns, instead of waiting indefinitely.
Fixed
- A finished turn can now be read as soon as the endpoint goes idle. A completed turn previously stayed marked partial, with no stable cursor, until the endpoint received its next prompt — so reading what an endpoint had just done meant sending it another command first, purely to close out the previous one. That workaround is no longer needed. Cursors anchor on the turn’s own input position, which does not move.
Known
- A stale “update available” notice can still reach an agent that is listening rather than attached. This is the same remaining case named in the v0.41.1 notes and is not addressed here.
[0.41.1] - 2026-07-23
A patch release that stops an out-of-date update notice from coming back.
Fixed
- An “update available” notice for a version that is already installed no longer keeps reappearing. Notices announcing an update the machine has since applied are now retired once it is running that version or newer, so they stop resurfacing every time a window attaches. Previously such a notice could return indefinitely — most visibly when it came from a machine on an older version of spt, in which case nothing could clear it at all.
Known
Some out-of-date update notices can still appear, in two limited cases:
-
A machine still running an older version of spt announces an update once when it first sees one, and that first announcement cannot be suppressed from the receiving side. It no longer repeats after that. Updating or retiring the older machine is what stops it at the source.
-
A notice can still reach an agent that is listening rather than attached. This narrows an earlier statement: the v0.41.0 notes said stale “update available” notices no longer arrive on machines that are already up to date, which holds for attached sessions and active windows but not for listening agents. That remaining case is not addressed here.
Internal
- Measured two suspected shutdown-related residuals and found no defect in either; recorded the timing of an unresponsive network peer’s recovery. Test and build tooling corrections. No change to behavior.
[0.41.0] - 2026-07-22
A release that makes stopping the daemon mean what it says, and corrects four ways a session could report or display something that was not true.
Changed
- Behavior change:
spt daemon stopnow stays stopped. Routine background activity could previously bring the daemon straight back up on its own, so on a busy machine a stop had to be issued several times before it held. Commands that would have quietly restarted it now do nothing and print one line saying the daemon was stopped and what to run to bring it back. Starting the daemon again — directly withspt daemon start, or as part of applying an update — clears the stop.
Fixed
-
A live agent no longer reports as online indefinitely after it has stopped. An agent’s record was tracked by process id alone, so once that id was reused by an unrelated program the agent kept reading as running and never corrected itself. Records are now matched on identity rather than the number alone, and a record that no longer matches repairs itself.
-
spt updateandspt daemon refreshno longer freeze attached views. An attached window could come back from one of these with its display stuck and its keyboard dead until it was detached and reattached. Attached windows now keep their control and keep working across the refresh. -
Stale “update available” notices no longer arrive on machines that are already up to date. A notice waiting to be delivered is now re-checked at the moment of delivery, so one that has since gone out of date is dropped rather than shown.
-
On Windows, attached views no longer corrupt the interior of the screen at a fixed window size. Output that moved the cursor down one line was being treated as a return to the start of the line as well, so everything after it landed in the wrong column. A separate and less common source of stray characters, seen after a resize, is still under investigation and is not addressed here.
Internal
- Recorded the design decisions and hazard notes behind the fixes above, and added regression coverage built from captured real-world sessions. Normal behavior is unchanged.
[0.40.0] - 2026-07-21
A release that makes notifications quieter and more truthful: they no longer pile up across paired machines, they clear themselves once whatever they were about is resolved, and they never cut into an attached session mid-output.
Changed
-
Notifications no longer interrupt an attached session. A notice that arrives while a session is attached now waits for a natural break in the output, or for the next time a window attaches, instead of appearing in the middle of the live view. Previously a notification could surface mid-output and disturb what was on screen.
-
The same notification no longer repeats across a subnet. When machines are paired, a single condition — an available update, for example — now shows as one notification for the group instead of one on every machine. Notifications that concern a single machine stay on that machine rather than spreading to the others.
-
Notifications clear themselves once they no longer apply. When the thing a notice was about is resolved — an update gets applied, a pending step completes — the notice now goes away on its own instead of lingering until dismissed by hand.
-
Leftover “update available” notifications from earlier versions are cleared once, the first time this version runs. Anything still current is shown again shortly after, so nothing real is lost — only the stale copies from before the upgrade go away.
Internal
- Added debug-only diagnostics for investigating display and attach issues. They are off by default and cannot be enabled in a released build, so normal behavior is unchanged.
[0.39.4] - 2026-07-21
A patch release that keeps the live view clean while an attached terminal is resized, and corrects two resize-related claims from earlier notes.
Fixed
- Resizing a controlled or viewed terminal no longer garbles the live view. While a resize is settling, output is held back, and once the new size lands every attached window is sent one clean repaint at that size — the same clean-screen refresh an attach already performs. Previously the live stream kept flowing during the resize and could clobber, merge, or displace visible text.
Corrected
-
The 0.39.1 notes said “Resizing a terminal no longer corrupts what an attached session shows,” and that “Output produced while a resize is in progress is now always interpreted at the screen size it was written for.” That was true of the stored screen a fresh attach repaints, but the output sent to an already-attached terminal during a resize was not held back — so a live attached window could still render mixed-size output and stay garbled. This release holds output while a resize settles and sends every attached window one clean repaint at the new size, closing that gap.
-
The 0.39.0 notes said “Wide characters — CJK text, emoji — no longer misalign an attached screen,” that “Rows no longer shift left, and stray fragments no longer remain at the right margin after attaching.” That fix was and remains correct for its cause — wide characters. But the same visible symptom, shifted rows and fragments at the right margin, had a second cause — resizing the terminal — that was still present, so it could still appear after a resize. This release closes that second cause.
Known
- Keep spt on the same version across machines. If an older spt attaches to a session hosted by a newer spt and that session is resized, the attached view ends cleanly — shown as truncated — instead of corrupting; re-attaching resumes normally. This only happens while the two are on different versions.
Internal
- Output frames for attached sessions carry an additive marker identifying synthesized repaints; older clients on an older daemon are unaffected.
[0.39.3] - 2026-07-21
A documentation release for authors of shell binaries. No behavior changes.
Documentation
-
The frame contract page now specifies how bodies and attribute values are encoded on the wire — the entity escaping applied to both,
<br>for newlines in bodies, the binding decode order (<br>first, ampersand last, and only over an extracted body or attribute rather than the whole line), carriage-return normalization, and the guarantee that a frame is never split across lines.The 0.39.2 notes said that release published each message type “with its attributes, body format, and per-message authentication stamp”. The body and attribute encoding was in fact absent from the page, so a decoder written strictly from it would fail on the first
&,<,>,", or newline in real content. That gap is what this release closes.
[0.39.2] - 2026-07-21
A patch release for authors of shell binaries. Nothing changes for existing setups.
Added
- A shell binary can now find the files sent to it. A manifest’s spawn template
may include a new
{perch_dir}placeholder, filled at launch with that shell instance’s own data directory; joining the relative path from a file notification against it gives the landed file. Previously there was no reliable way to find wherespt shell send --filehad put a file. The placeholder is optional, and existing templates are unaffected.
Documentation
- The frame vocabulary a shell binary parses is now published as “Shells: the frame contract” — every message type spt delivers to a shell, with its attributes, body format, and per-message authentication stamp, plus where sent files land and how to resolve them. Previously this had to be read out of the source.
[0.39.1] - 2026-07-21
A patch release closing out screen fidelity for attached sessions across resizes, and correcting how live agents report their status.
Fixed
-
Resizing a terminal no longer corrupts what an attached session shows. A resize could still leave the screen garbled — words merged or split mid-row, rows shifted left, stray fragments left behind in blank space — and the damage persisted into later attaches. Output produced while a resize is in progress is now always interpreted at the screen size it was written for.
-
Live agents started with the standard bind-then-listen sequence no longer report as “ONLINE - HARNESS ONLY”, and no longer drop to offline after detaching. Previously the second step overwrote what the first had correctly recorded, so an agent that was answering messages could show as offline.
-
Overlapping resize requests to the same attached session are now handled one at a time instead of racing each other.
Known
- Attaching to a session while a resize is still settling may briefly show the screen as it was just before the resize. It catches up as soon as the program produces output.
Internal
- Test-classification enforcement for the broker suite.
[0.39.0] - 2026-07-20
A release about tearing endpoints down honestly, and about attached sessions showing exactly what the program drew.
Changed
-
spt endpoint stopandspt endpoint shutdownnow tear down the whole process tree an endpoint owns, instead of reporting success while part of it keeps running. When something does survive, the command now refuses honestly — naming the process that is still alive and the scoped command to clear it — rather than reporting a teardown that did not happen. -
spt endpoint purge --forcenow confirms before tearing anything down, and refuses outright when it can name a session that is still live. An entry left behind by a process that is already gone is treated as leftover bookkeeping rather than a survivor. -
spt endpoint digest --jsonoutput is now self-contained, and the meaning of each entry kind it reports is documented.
Fixed
-
An endpoint whose session had already exited could still be reported as running, so
spt endpoint runrefused to start it withENDPOINT_ALREADY_LIVEand pointed to a session that was no longer there — leaving the endpoint unreachable without stepping outside the tool. Endpoint liveness is now read from the process table, so a finished session is seen as finished. -
Attached sessions no longer show corrupted output while connecting. Startup diagnostics are no longer written into the screen
spt rcowns — previously a line such as “Reconnecting to local daemon…” could appear spliced together with internal startup text. -
Wide characters — CJK text, emoji — no longer misalign an attached screen. Rows no longer shift left, and stray fragments no longer remain at the right margin after attaching.
Internal
- Test-rig hardening for the endpoint-lifecycle and registry suites.
[0.38.1] - 2026-07-18
A patch release refining how controlled and directly-hosted sessions are labeled and recovered.
Fixed
-
A session being controlled from another window on the same machine now reads “controlled locally” in the picker and in
spt endpoint info, instead of showing a long internal node identifier. -
A session hosted directly by spt now reliably stays online and restores its terminal after a daemon restart, even when it was started over an endpoint that was already listening on the same id. Sessions whose host has died no longer linger in the list as falsely active.
Internal
spt rcnow builds a single attach pump per session instead of two, removing a duplicated startup diagnostic on attach.
[0.38.0] - 2026-07-18
A release focused on attaching to sessions reliably and leaving the terminal clean afterward.
Fixed
-
Attaching to an endpoint whose session is running now works even when its status momentarily looks stale.
spt rcchecks the live session first, so it no longer reports “offline — nothing to attach to” for a session thatspt endpoint run --resumecan attach to. An endpoint that is still starting up now reports as starting rather than offline. -
spt rcon an endpoint hosted by its own harness now says so plainly — “online but harness-hosted; spt does not own its terminal” — instead of assuming its status is stale. -
Qualified attach targets (
spt rc <id>@<node>,spt rc subnet:<id>) now attach after reaching the right node, instead of being refused. -
Taking control from a second window on the same machine now cleanly displaces the first: the first window shows a notice and exits, rather than being left typing blindly into a session it no longer controls. A displaced window can no longer send any input.
-
Attached sessions no longer drop the last screen output of a short-lived command. The final output is always delivered before the exit notice.
-
Closing or losing an attached session no longer leaves the terminal in a broken state — the alternate screen, colors, and cursor are restored on every exit path.
-
Removing an endpoint from the picker no longer leaves stray text fragments on screen.
-
After reattaching or resizing, the screen now repaints exactly — no drifting or stale rows.
Internal
- Test and CI coverage for attach, control, and terminal-render lifecycle; requirement registry updates.
[0.37.1] - 2026-07-17
A follow-up fix for background CPU use tied to the peer directory.
Fixed
- Periodic CPU spikes every ~30 seconds on nodes with long session histories are gone. The peer-directory announcement was re-checking the same project folder once for every past session on the node; it now checks each folder only once per announcement.
[0.37.0] - 2026-07-17
A stability and lifecycle release. It ends the steady background CPU use on every node, stops attached endpoints from stalling, and makes stopping, starting, and controlling endpoints behave predictably.
Changed
spt endpoint runno longer silently reattaches to a running session. When a live session already exists for the endpoint, it now stops withENDPOINT_CREATE_CONFLICTand a non-zero exit instead of quietly joining the existing one. Attach to the running session withspt rc <id>, or pass--resumeto resume it.
Fixed
-
Nodes no longer use steady background CPU while idle. The daemon was re-verifying its own program file about twice a second; it now does so once at startup.
-
Attached endpoints no longer freeze for 15–25 seconds at a time. The daemon could keep re-applying old peer-directory updates; each update is now applied once and completed transfers are discarded immediately. This also ends the slow thread and memory growth that built up on long-lived connections.
-
Closing a remote-control window abruptly no longer leaves an endpoint stuck reporting CONTROLLED. The state is released even if the daemon restarts.
-
Stopped or crashed endpoints no longer resurrect themselves in a wake loop, and stale ONLINE entries left by dead processes now clear on their own.
-
Stopping and restarting an endpoint no longer wedges on a leftover session.
run,list, andshutdownnow agree on whether an endpoint is live; a genuinely dead leftover is cleaned up and the restart proceeds.
Internal
- Improved daemon diagnostics — named threads and stream/seat gauges.
[0.36.0] - 2026-07-16
A stability release for attached sessions. A daemon update no longer disrupts active sessions, and a session stuck by a bad connection now recovers on its own.
Fixed
-
Attached sessions no longer freeze after a daemon update or refresh. Previously, a single bad viewer connection could hang an attached session for up to a minute; a stuck viewer can no longer freeze other sessions.
-
Improved the stability of controlled sessions across a daemon restart. Previously, session controllers could mix up and drop their attached sessions.
-
A session left stuck by a bad connection now recovers on reconnect, instead of staying unresponsive.
Internal
- Improved logging granularity for attached endpoints.
[0.35.0] - 2026-07-16
A subnet resilience and visibility release. A node that briefly cannot reach a peer no longer isolates itself, and node status now shows whether your peers are actually reachable.
Added
- Peer reachability in
spt daemon statusandspt subnet status. Both now report how your node is really doing on its subnets: how many peers are currently reachable, when a peer was last reached successfully, and when the node last accepted a registry update from the subnet. When every peer is unreachable the status reads DEGRADED and names the stage that is failing, instead of staying green while the node is cut off. Dial failures in the daemon log now carry the failing stage and a timestamp.
Fixed
-
A node no longer strands itself from its subnets after a brief failure to reach a peer. Previously a single failed dial could delete the node’s only cached route to a peer — even while a valid address for that peer sat in the subnet roster — leaving the node quietly isolated with its status still green. Routes are now kept and marked unreachable rather than deleted, and address resolution falls back to the roster, so the node recovers on its own with no manual state surgery.
-
Poisoned peer-address entries are repaired at startup. If a cached peer address has come to claim a different peer’s identity, the daemon now detects and repairs it from the subnet roster when it starts — and says so loudly in the log — rather than carrying the bad entry forward.
[0.34.0] - 2026-07-16
A stability fix for live remote sessions during a daemon update.
Fixed
- Updating or refreshing the daemon —
spt update,spt update --restart, orspt daemon refresh— no longer disturbs a remote session you are attached to. A daemon cycle could previously replay already-finished output onto a live remote terminal: old text re-typed itself, control of the session was stolen, or a running session was left frozen. The daemon now treats a finished session’s history as terminal and never re-drives it onto a live terminal, so the remote sessions you are attached to keep running cleanly across an update.
[0.33.0] - 2026-07-16
A performance and identity release. The commands that list endpoints and choose
a run now answer in a fraction of a second instead of many seconds, spt whoami
becomes a focused identity command, and the daemon gains a health report for the
index that makes the fast listings possible.
Changed
-
Breaking:
spt whoamireports only your own identity. It now prints the single endpoint bound to the current session, not the full roster of endpoints on the node — usespt endpoint listfor the roster. Its--jsonoutput is a new, stable object —{id, state?, ready?, alive?, unbound?, description?}— rather than the list shape it returned before. When the current session is not bound to any endpoint, it printsNO_PERCHon stderr (JSON:{"id": null}) and exits non-zero, where it used to succeed. The command runs in bounded time and is safe to call from shell hooks and prompts. -
Listing endpoints is now fast.
spt endpoint list(both the human table and--json), the interactive run picker, andspt api endpoint-inforead a project index the daemon keeps current, instead of inspecting each project’s git state on every call. On a node with many endpoints this takes these commands from many seconds to well under a second. Two notes on the trade-off: a project you just changed may show its previous attribution for a brief moment (the index refreshes on change and reconciles periodically), and on a node whose daemon has not yet built the index, attribution shows as-until the first build lands.
Added
spt daemon statusnow reports the health of the project index that powers the fast listings — when it was last generated, how many projects it covers, the last error if any, and repair and stale-read counters. The--jsonoutput carries the same detail in aproject_indexblock. The index file itself lives at$SPT_HOME/index/project-index.jsonand is derived state: safe to delete, and rebuilt by the daemon. A present index file is not by itself proof of health — the status report is.
Fixed
-
The install instructions now name the real release files —
spt-x86_64-windows.exe,spt-x86_64-linux, andspt-x86_64-linux-musl— with a per-platform download example and thechmod +xstep on Linux. The earlier instructions referenced file names that were never published. -
The
[update.post]adapter hook is now fully documented: it runs once when an adapter is first added, runs in the foreground under a 120-second bound, surfaces its own failures, and is verified before you are notified. This matters when installing a fresh adapter such as the Claude Code plugin.
[0.32.0] - 2026-07-15
A distribution and documentation release. Releases now come from a private
channel through the GitHub CLI, every node serves its own copy of the docs on
localhost, and a single spt update brings the whole node — core and adapters —
current in one command.
Starting the docs server needs a daemon restart. The docs server and
spt daemon refreshlive in the always-on daemon.spt update --restartbrings them up via a full daemon restart (your live sessions restart as the daemon comes back). A barespt updateswaps the core binary in place and leaves the running daemon untouched, so the new docs server starts on the daemon’s next restart.
Added
-
spt install— self-install this binary onto the node. Run it once from a freshly downloaded release binary: it places itself at the canonical install location, adds that location to your PATH, and refuses a binary built for a different platform. Non-interactive and safe to re-run.--dir <path>chooses the install directory;--no-pathskips the PATH change. This is the bootstrap path for a brand-new node. -
Node-local documentation, served by the daemon on
http://localhost:5474.spt docsopens the docs in your browser.spt docs urlprints the resolved URL (honoring any port override). The port can be changed withdocs_portindaemon.jsonor theSPT_DOCS_PORTenvironment variable; the server listens on loopback only. The documentation that used to live at a public web address is now read here, on your own node, always matching your installed version.
-
Every release now ships a documentation bundle (
spt-docs.tar.gz) as a signed release asset.spt updatedownloads it and lands it at$SPT_HOME/docs, so the docs your node serves always match the binary you are running. A docs-download problem never blocks a binary update — it is reported and retried on the next fetch. -
spt update adapters [<name>[,<name>…]]— update your release-shipped adapters. With no names it updates them all; with a comma-separated list it updates just those (names are validated up front, so a typo updates nothing). Each adapter reports its own result, and one failing adapter does not stop the rest. This is a shorter alias forspt adapter update, which still works and now also accepts a comma-separated list. -
spt update --restart— the one-step full cycle: fetch, update adapters, then restart the whole daemon onto the new version. Use it when you want everything — core, adapters, and the always-on daemon — brought current in a single command. Your live sessions restart and come back on their own. -
spt daemon refresh— restart just the daemon’s coordinator in place, with no binary change and without stopping the daemon. Hosted terminals and the network layer keep running. This is the recovery verb for a stuck coordinator that previously needed a fulldaemon stop/start, which killed every hosted session.
Changed
-
Bare
spt updatenow brings the whole node current: it applies a staged core update (if any) and then updates your adapters, in that order. When the core is already current, only the adapters update. Pass-c/--core-onlyto update the core binary alone and skip the adapters step. -
Self-update now fetches releases through the GitHub CLI (
gh) from a private release channel instead of a public web address.ghis now a prerequisite forspt update fetchand for thespt installbootstrap. Ifghis missing or not signed in, the update stops with a clear message — how to installghfor your operating system (winget/brew/ your package manager) and to rungh auth login.ghsupplies its own credentials, so spt never stores a token. -
Updating an adapter that has no release channel — for example a local, in-development adapter — is now skipped rather than treated as a failure. An all-adapters update or a named update no longer fails or returns an error code just because one registered adapter has nothing to pull.
[0.31.0] - 2026-07-10
A messaging and identity release: agent-to-agent messages now deliver exactly once with the right sender on them, endpoints you save come back on their own after a daemon restart, and several ways an agent’s identity or saved context could be lost are closed.
Applying this update needs a daemon restart. Parts of this release live in the always-on daemon, so
spt update fetch --applylands it via a full daemon restart rather than a live swap — your sessions restart as the daemon comes back.
Added
spt endpoint run --save— make an endpoint a startup default. The daemon relaunches every saved endpoint (as a fresh session) each time it starts, so your always-on endpoints come back by themselves after a restart instead of staying offline until someone re-runs them. Saving the same endpoint again replaces its saved entry;spt endpoint runwithout--saveleaves your startup defaults alone.
Fixed
- A message could be delivered twice — once to the agent and once typed into its terminal as stray, never-submitted text. Delivery now takes exactly one path: a session that reads its own messages receives each message there and nowhere else, and when more than one carrier could deliver, exactly one now claims it. Leftover terminal-echo settings from earlier sessions are also cleared when the daemon starts, so they cannot quietly re-open the second path.
spt sendfrom inside an agent’s shell went out as anonymouscli@<machine>instead of the agent itself, so replies to it bounced withNO_PERCH. Sends from an agent-bound shell are now stamped with the agent’s own id (only when that id really holds a bound perch), and replies route back to the sender.- Bringing up a listener punished a refused first attempt: the one-time
startup seed was consumed even when the bind was refused, so the corrected
retry dead-ended with no seed. A refusal now puts the seed back — retrying
with corrected flags just works. Error hints also print a command form that
actually parses, and
spt listen --session-idoffers a fallback way to bind when no seed is present. - A saved context update could silently lose its project-specific part: if it arrived while the agent’s project could not yet be resolved, that part was parsed but never filed, and then deleted with the rest of the update. The unfiled part is now preserved and filed as soon as the project resolves — nothing is dropped.
- A stale or leaked identity value inherited through the environment could let a new session sit down in an agent seat that was not its own. The daemon now scrubs inherited identity variables at startup and refuses seat takeovers from sessions that do not hold the seat.
- Connection logs blamed the wrong thing: routine dial noise from peers that went offline was reported with the same message as a genuinely stuck connection being cut off. The two cases now log distinctly, and connection log lines carry which session and endpoint were involved, so a real stall is recognizable at a glance instead of drowned in noise.
[0.30.6] - 2026-07-10
A reliability patch closing the last update-wedge failure mode: a stuck session consumer can no longer freeze the daemon under load.
Applying this update needs a daemon restart. Unlike the recent seamless in-place updates, this fix lives in the always-on daemon, so
spt update fetch --applylands it via a full daemon restart rather than a live swap — your sessions restart as the daemon comes back. This is a one-time cost to install the fix.
Fixed
- Under load, if a session’s controller consumer stopped reading (for example a
wedged
spt rc --takethat stalled without disconnecting), the daemon could block indefinitely trying to write to it — freezing live terminal sessions with no recovery short of restarting the box. Every write the daemon makes to a session connection is now time-bounded and cancelable: a stuck consumer is cut loose instead of taking the whole daemon down, so your other sessions keep streaming and a freshspt rcresumes cleanly.
[0.30.5] - 2026-07-09
A reliability patch completing the in-place-update fix: applying an update no longer freezes your live terminal sessions.
Fixed
- Applying an update in place with
spt update fetch --applycould still freeze all of your live terminal sessions a few seconds after the swap — they stopped streaming andspt rccould no longer attach, so the box had to be restarted to recover. (The previous release narrowed this but did not fully close it.) Resuming across the update no longer floods the daemon’s internal channel, so your open sessions keep streaming andspt rckeeps working straight through the swap — no freeze, no restart. (Because the fix lives in the incoming version, updating to this release is what makes your next in-place update seamless.)
[0.30.4] - 2026-07-09
A follow-up reliability patch for in-place updates: resumed sessions are no longer seized during the update handoff.
Fixed
- Applying an update in place with
spt update fetch --applycould freeze or seize control of your live terminal sessions as the daemon restarted — a resumed session could be taken over so that your open session andspt rcstopped responding across the swap. Resumed sessions now re-attach as observers instead of taking over the session’s controller, so your open sessions andspt rckeep working across the brain swap. (Follow-up to the previous release’s seamless-swap fix.)
[0.30.3] - 2026-07-09
A reliability patch for in-place updates, plus two message- and endpoint-delivery correctness fixes.
Fixed
-
Applying an update in place with
spt update fetch --applycould freeze every live terminal session for about 30 seconds and then roll the update back instead of completing the swap. The incoming version now drives the running daemon to clear a hard-stopped prior session during the handoff, so the update completes seamlessly with your sessions staying up — no freeze, no rollback. (Because the fix lives in the incoming version’s logic, updating to this release is what makes your next in-place update seamless.) -
A message sent active-only (
spt send --active-only/--deferred, documented as “never wakes an idle target”) could still be delivered to a target the instant it went idle. Active-only messages now stay held for the target’s active window as documented and no longer leak in on the active-to-idle edge. -
spt endpoint list --jsoncould report a locally-hosted endpoint that was actually live as suspended or offline, even while the human-readable listing correctly showed it online — which could cause an adapter to needlessly suspend itself. The JSON listing now reflects the same local liveness truth the interactive view uses. Re-binding an endpoint also no longer discards its pending wake/rest state.
[0.30.2] - 2026-07-09
A follow-on reliability patch for subnets with several peers.
Fixed
- On a subnet with several peers, one unreachable or slow-to-connect peer could hold up connectivity to the others — the node contacted peers one at a time, so a peer slow to answer delayed every peer behind it in the round. Peers are now contacted concurrently and each peer’s outcome is independent: a reachable peer connects and appears right away even while another peer is still failing, and an unreachable peer backs off on its own without affecting the rest. (Builds on the 0.30.1 fix, which stopped a single silent peer from stalling the whole round.)
[0.30.1] - 2026-07-09
A reliability patch for peer connectivity on subnets with multiple peers.
Fixed
- Nodes could stop seeing each other on a subnet when a single peer was reachable
but unresponsive — connected yet not replying (a peer mid-restart, suspended, or
running a version that doesn’t answer the periodic update check). That one peer
would stall the whole peer-sync round, so
spt subnet statuslisted the subnet’s members but showed an empty live-node list and warned that the peer pump had stalled. An unresponsive peer is now dropped and retried on its own instead of holding up the round, so the node keeps converging and peers reappear.
[0.30.0] - 2026-07-08
Adds a statically-linked Linux build for hosts with an older system C library.
Added
- A statically-linked musl Linux artifact (
spt-x86_64-linux-musl) that runs on Linux hosts whose system C library is too old for the default build (pre-glibc 2.39), where that build will not start. On such a hostspt update fetchnow selects and verifies this artifact automatically. The default (glibc) Linux build is unchanged and remains the standard Linux artifact.
[0.29.1] - 2026-07-08
A reliability patch for message delivery into a live session.
Fixed
- A long multi-line message delivered into a live session could arrive with its opening lines cut off when the session had been cleared or checkpointed earlier in its run — the message was typed before the freshly-cleared terminal was ready to receive it. Such a message now arrives intact. (The earlier fix in 0.29.0 covered only the moment a session first starts; this extends it to every clear during a session’s life.)
[0.29.0] - 2026-07-07
A lifecycle-reliability release: previously-online sessions are re-launched automatically after a daemon restart, an attached terminal no longer freezes behind a slow or suspended session, a long multi-line message typed into a live session arrives intact, and an in-place update finishes in a single command.
Added
spt update apply --finishcompletes an update in one command. It swaps in the new binary and restarts the daemon onto it; previously-online sessions are then re-launched automatically.spt update applynow also works while the daemon is stopped — it swaps in place, and the nextspt daemon startruns the new version.- The daemon now keeps a log on disk. Its diagnostics are written to a rotating, size-capped file under the daemon’s home directory, so a failure that happens in the background leaves a trace you can read after the fact.
Changed
spt daemon stopnow protects live sessions. When hosted sessions are running it lists them and refuses to stop unless you pass--force(the sessions come back on the next start), so an accidental stop no longer tears down running agents.spt rcno longer starts a daemon by itself. It attaches only to an already-running daemon; if the daemon is down it says so and exits instead of silently launching one — the cause of the old “I had to stop it several times” behavior. While reconnecting it now shows a live countdown.
Fixed
- Hosted sessions are re-launched after a daemon restart. Previously-online sessions come back automatically once the daemon restarts (including as part of an update) instead of being left offline. The restart still interrupts them — this restores the session, it does not preserve its in-flight work.
- An attached terminal no longer freezes behind a stalled session. If a session’s underlying process hangs or is suspended,
spt rckeeps updating and a stuck connection is dropped automatically so you never lose control; reattaching and taking control (--take) keep working.spt daemon statusreports when such a drop happened. - A long, multi-line message typed into a live agent’s terminal arrives complete. When a message is delivered by typing it into a live session, it is no longer truncated at the front. (Messages drained through the polling channel were never affected.)
spt endpoint digest --jsonno longer repeats rows. Activity replayed across a session checkpoint is collapsed to a single entry.- Waking an agent starts exactly one session. Two wake requests arriving at once no longer launch it twice.
- A dead owner no longer leaves an agent showing as online. When the process that owns a listener exits, the listener stops promptly, so the agent shows offline and can be re-bound.
- Live agents save their context reliably. Fixed a case where an agent’s automatic context save could fail — silently writing to the wrong place, or repeatedly erroring — when its save directory was left to resolve from the daemon’s own working directory; a mis-configured directory now produces a clear one-time warning instead.
[0.28.0] - 2026-07-06
A join-truth release: joining a subnet now tolerates a skewed or stepped system clock so it finds members it used to silently miss, no longer raises the elevated-permission prompt before a member is actually found, and can show the exact ceremony clock it is using — plus a loud warning when no time server can be reached.
Added
spt subnet join --verbosenow shows the ceremony clock. The verbose output prints the joiner’s pairing time-step, its clock offset, and whether that clock is NTP-corrected or running uncorrected on the raw system clock — so a skew-related join problem is visible at a glance.- A loud warning when no time server answers. If every NTP server is unreachable during a join,
sptprintsNTP_TOTP_UNCORRECTED: all NTP servers unreachable — ceremony clock = raw system clock, instead of silently proceeding on a possibly-wrong clock.
Changed
- Joining no longer raises the elevated-permission prompt before a member is found.
spt subnet joinnow asks for OS elevation only once it has actually located a subnet member, rather than up front — so a join that can’t find anyone no longer pops an elevation prompt for nothing. The--codepath is unchanged.
Fixed
- Join now finds members it used to miss when the clock is off. The pairing ceremony clock is corrected against NTP (and re-steps when the system clock jumps), so a joiner whose machine clock is skewed no longer silently fails to meet a subnet member.
- A failed join now reports the joiner’s own clock in the failure detail. The
spt subnet join --verbosefailure block now also carries the joiner’s daemon-side ceremony clock (time-step, offset, and corrected/uncorrected state), so a met-then-refused join shows the clock state on both sides instead of leaving the joiner’s half unexplained.
[0.27.0] - 2026-07-06
A worker-truth release: the background “worker” endpoints an agent spawns now carry stable minted ids, stay out of your endpoint list and the run picker, get cleaned up when they leak, and inherit their parent’s account; and harness adapters are validated more strictly when you add or update them. Breaking for adapter authors: the worker control verbs changed shape — see Changed.
Added
- Leaked or orphaned worker endpoints are now cleaned up automatically. A finished worker whose results have been collected is removed immediately; an orphaned worker (parent gone) or one that leaked without a stop signal — even under a live parent — is reaped after a configurable time-to-live (
worker_reap_ttl_secs, default 24h) instead of lingering as a dead offline row.
Changed
- Breaking (adapter authors): worker endpoints now use core-minted ids and token-free, session-symmetric control verbs. A spawned worker is assigned a stable
<parent>-w<N>id by the core, and the verbs that drive a worker now key on its session id with no separate token. Adapters that spawn or control workers must migrate to the new verb shape; the old form no longer works. spt endpoint listhides worker endpoints by default. Worker endpoints no longer clutter the default listing; pass--workersto include them.- The run picker no longer offers worker or companion (psyche) endpoints. These aren’t independently startable, so the picker lists only endpoints you can actually launch.
Fixed
- A spawned companion (psyche) now runs under its parent’s account. A psyche launched for a live agent inherits the parent agent’s captured account/home root, so it runs in the right environment instead of a default one.
- Adapters that reference an unfillable or misspelled template key are now refused when you add or update them. Adding or updating a harness adapter validates its spawn/role templates at registration time — an unknown or misspelled
{placeholder}, an unterminated{, or a contradictory environment directive (removing and reading the same variable) is rejected with a clear message, instead of failing later when a session is spawned.
[0.26.0] - 2026-07-06
A remote-truth release: acting on an endpoint that lives on another node — waking it, suspending it, attaching to its screen — now works by bare id across your subnets and reports honestly when it can’t; the picker gains back-navigation, a purge shortcut, and remote wake; and adapter handling, session resume, sender identity, and error messages all tell the truth about what happened.
Added
- Wake or suspend an endpoint on another node by its bare id.
spt endpoint wake <id>andspt endpoint suspend <id>(and the other remote verbs) now find the right endpoint across the subnets your node belongs to without an explicit--subnet, and report a clear host error when the target can’t be woken instead of failing quietly. - The picker can wake a suspended remote endpoint. A suspended row that lives on another node now offers Wake-now directly from the pick list.
xpurges an endpoint from the pick list. Highlight a row and pressxto remove that endpoint, with an in-list confirm before it happens.- Backspace steps back one screen in the picker. Backspace now backs out one picker screen, matching Esc.
spt endpoint run --id <id>reuses that endpoint’s own adapter. Running an existing endpoint by id reuses the harness adapter it was created with instead of dropping you into the choose-a-new-adapter picker.
Changed
- Adapters must meet their declared minimum core version to be added or updated. Both adding and updating a harness adapter now enforce the adapter’s minimum-
sptrequirement; one that needs a newersptis refused with a clear message instead of being installed and failing later. - Resuming a session keeps that session’s adapter. Resume-from-history now follows the adapter recorded for each session, so a resumed endpoint runs the adapter it was created with rather than a default.
Fixed
- Attaching to a remote endpoint’s screen now recovers from a dropped connection. If the link to a live remote view is severed, the attach auto-reconnects within a bounded window and, failing that, gives up with a plain-language message instead of hanging; and other attach failures now name what happened and what to try instead of showing an internal transport error.
- An internal fault mid-attach no longer permanently wedges all later attaches. Previously one internal fault during attach churn could make every subsequent attach time out until the daemon was restarted; the affected state now self-heals (worst case, one screen’s scrollback resets and repaints) instead.
- A cold attach now repaints the program’s window title. Attaching to a running session restores the child program’s terminal window/tab title instead of leaving it blank.
- Ending one session no longer stops messages to another endpoint that is still listening. A soft session-end keeps a still-live listener’s delivery address, so its messages keep arriving.
- A message sent from inside an spt-hosted session is attributed to that endpoint. A send from within a hosted session is now stamped as coming from that endpoint rather than the bare command-line identity.
- An endpoint that exists but has never run now reads as suspended. Its status is derived consistently instead of showing an in-between state.
- A stale “controlled” marker left by a gone controller now clears itself. When the process that held an endpoint is gone, the controlled/viewer marking heals on its own.
- The picker shows a project’s readable name everywhere, and tells same-named projects apart. The remaining raw-slug spots now show the friendly name, and two projects that share a name are disambiguated by their folder.
spt daemon stopno longer hangs when the daemon is busy. Shutdown is now bounded and drains in-flight connections, so stop returns promptly instead of parking under load.spt endpoint list(andspt whoami) now flag an endpoint whose input translation has failed. The endpoint’s line showsinput-translation: FAILED (<reason>)and what it means for you — typed input may not reach the session — instead of the fault being invisible.- A remote wake or screen-open no longer spuriously fails with “op already applied — retry with a fresh op_id”. Internal operation ids raised from different sources could collide; they are now kept distinct, and a stale collision retries once on its own.
- Acting on an endpoint that lives on another node now says so in plain language. A remote operation against an endpoint hosted elsewhere reports
this endpoint is not hosted on this nodeand points you atspt endpoint listto find where it lives, instead of an internal-sounding failure.
[0.25.0] - 2026-07-04
A psyche-ephemeral release: a live agent’s Psyche is no longer a resident background process — each event runs one bounded turn — and its conversation now survives the parent agent’s context reset, stays out of the way when it fails, and cleans up cleanly on upgrade.
Changed
- A live agent’s Psyche no longer runs as a resident background process. Instead of one long-lived Psyche process per live agent, each pulse or event now runs a single bounded Psyche turn. Liveness is measured by turns succeeding, not by a process staying resident — nothing lingers between events.
- A Psyche now keeps its own conversation across a parent context reset. The Psyche’s session is owned independently of the parent agent, so its thread continues uninterrupted when the parent’s context is reset — the companion no longer loses its place.
- Nested agent ids resolve without
--subneton multi-subnet nodes. Referring to aparent/nestedagent on a node that belongs to more than one subnet no longer requires an explicit--subnet; resolution finds the agent locally. This also clears the Psyche-poll refusal that a prior version surfaced loudly. - Harness-contract manifest keys added. Adapter manifests can now use
{parent_session_id}(the hosting agent’s session),{subnet}, and{psyche_context_file}— the last replaces the inline{psyche_context}key, passing the Psyche’s mind as a file path rather than inline on the command line, so large contexts no longer risk overrunning the operating system’s argument-length limit.
Fixed
- A failing Psyche no longer takes your endpoint offline. When a Psyche turn fails, the live endpoint stays ready and keeps delivering messages. A gone-session condition triggers a loud reseed with a fresh start; any other failure counts against a small strike budget and is recorded as an endpoint error status — the parent agent is never taken offline, and the older machinery that could disrupt delivery is gone.
- Upgrading now cleans up stranded Psyche processes and leftover binary copies automatically. Upgrading from an older version sweeps away a Psyche process left resident by the previous daemon and removes leftover own-copy binary files at daemon start, instead of leaving them for manual cleanup.
[0.24.0] - 2026-07-03
A picker-polish release: the endpoint picker’s labels, keys, and flows now tell the truth about what each action does and where it acts — and message delivery no longer breaks after a context reset, with a dropped adapter profile fixed along the way.
Added
- The choose-project panel now marks your current directory. A history entry whose folder is your current working directory is tagged
(CURRENT DIR); if your current directory isn’t already in the history, aCURRENT DIR --> <folder>row is offered so you can start there directly. - The interactive endpoint picker now titles its terminal window. The window or tab is set to
SPT Endpoint Pickerwhen the picker opens interactively.
Changed
- Two picker action labels now name their target. “Fork endpoint here –>
” states the directory the fork will run in, and “Set shortcut here –> / ” names the exact shortcut file that will be written, so the label cannot drift from what actually happens. - The translation-binary protocol now requires an explicit commit terminator. Every
{"type":"event"}a translation binary receives must be answered with a trailing{"commit":true}— including an event with nothing to inject, which must still answer with a bare commit. The harness-contract docs also correct the missed-commit consequence: a missed commit no longer permanently kills the binary — it is tolerated and the envelope is re-spooled once.
Fixed
- The picker’s confirm panel now shows the readable project name. The one remaining place that still displayed a raw project slug now shows the friendly name, matching the rest of the picker and
spt endpoint list. - The picker’s
handskeys now work only where they can launch, and the footer only hints them there. Headless-start (h) and shortcut (s) previously fired from rows that could not launch anything, and the footer advertised them where they were dead; both are now live only on a highlighted row that can actually start the endpoint, and the footer hint matches. - “Change harness adapter” now only changes the adapter. Choosing it no longer re-prompts for an id and home directory and then starts a session — it picks a new adapter, applies it to the endpoint, and returns to the confirm panel.
- An endpoint’s adapter profile is no longer dropped on reconnect. An endpoint created with an adapter profile (e.g.
claude-spt:ccs) keeps that profile; re-binding no longer strips it back to the bare adapter. - A missed commit no longer stops idle message delivery. Previously an event that armed nothing (such as a context reset) could make the translation binary miss its commit and permanently stop delivering that session’s idle messages until it was restarted. A single miss is now tolerated — the healthy binary is kept and the next message delivers through it; only repeated misses or a genuine crash fault it, triggering a bounded automatic respawn and recording a fault status on the endpoint instead of failing silently.
- Message delivery and scheduled wake-ups now survive a context reset. After a context reset, an idle agent could stop receiving messages and scheduled wake-ups until its session was restarted; the session boundary now re-stamps its readiness in the correct order across the reset, so messages and wakes right after a reset are delivered instead of dropped.
- A
--force-nativesend that cannot be delivered now says why. The failure message distinguishes its cause — empty message, no daemon running, endpoint active mid-turn, no working translation binary, or not a controllable endpoint — instead of a single identical message for every case.
[0.23.0] - 2026-07-03
A run-truth release: starting, resuming, and shutting down agent sessions now behave honestly — no duplicate sessions, no stale “controlled” markers, no orphaned processes — alongside picker and listing display polish and a fix for adapter updates that could get permanently stuck on a machine running an agent.
Changed
- Projects now display a recognizable name. Across the agent picker (history, the choose-project panel, resume titles) and the
spt endpoint listproject column, a project shows a readable name (e.g.spt-core) instead of a raw slug; two projects with the same name are told apart by their folder. spt endpoint listoutput refreshed. The shared-subnet and total lines are dimmed, the status glyph now sits beside the endpoint name, and the status word is colored.- The agent picker no longer offers “View” for an offline endpoint. An offline endpoint has no live session to view, so only Start is offered.
spt daemon statusnow warns about a stale at-logon task registration. If the auto-start task was registered by an older installer in an unsafe form,spt daemon statusflags it with guidance to re-register via the current installer; the daemon self-protects either way.- Piping
spt daemon runto another command now discards its output by design. A piped launch is treated as a detached one and its console output is dropped; to capture the daemon’s output, redirect it to a file (e.g.spt daemon run 2>daemon.log) instead.
Fixed
- Colored CLI output on Windows consoles no longer garbles. On a raw Windows console,
spt endpoint listand--helpno longer print raw escape sequences; when the console cannot render color, the output is cleanly stripped instead. - Resume-from-history now labels each session with its own project. Past sessions no longer all read as the newest project, and internal or host sessions no longer appear as unresumable rows.
- The Start-now project chooser no longer lists the same project twice.
- A dead or offline endpoint no longer reads as “controlled.” The controlled/viewer marking is cleared once the session is gone — including across a daemon restart — instead of lingering.
spt endpoint runover an already-live endpoint no longer duplicates the session. It attaches to the running session (or, when headless, reports that the endpoint is already live) instead of silently starting a second session with a crossed view.- Resuming a session no longer hangs at “No sessions match.” Resume — including after a daemon restart — now restores the real recorded session, or starts fresh with a clear notice when there is nothing to resume.
spt endpoint shutdownnow fully tears down a wedged or crash-looping Psyche. Its child processes are killed too, instead of being left orphaned for a manual cleanup.- A crash-looping Psyche is now detected and stopped. Instead of silently respawning several times a second, the loop is halted, backed off, and surfaced as an error.
- Adapter updates no longer get permanently stuck on a machine running an agent. Leftover files from a prior update no longer make every later
spt adapter updatefail and roll back; update errors now name the file and operation involved. - One agent can no longer corrupt another agent’s session identity. An agent’s endpoint identity and presence are no longer overwritten by another agent’s Psyche, and a dead endpoint can no longer be silently re-bound to a different agent’s session.
[0.22.0] - 2026-07-03
A picker-and-presence truth release: the endpoint picker and listing show real project, type, and control state — including for endpoints on other machines — and a few rough edges around piping and cross-node display are fixed.
Added
spt api endpoint-info. A new command that emits, as JSON, which node an endpoint is attached to — for harnesses that need to resolve the node from which a controller is attached.
Changed
spt endpoint listnow shows a project column. Each row reads id / project / type / status, so you can see at a glance which project an endpoint belongs to.- The endpoint picker got two UX fixes. Starting an endpoint now opens a choose-project panel, and the resume view keeps the top endpoint details panel visible.
Fixed
- Endpoints on other machines now show truthful details. A remote machine’s rows previously displayed faked adapter, history, and control information; the gossiped rows now carry the real adapter, project history, and whether the endpoint is being controlled.
- A controlled endpoint now reads as CONTROLLED accurately — and stops reading CONTROLLED once control ends. An endpoint being driven shows as controlled both in its own machine’s picker and from other nodes; when the controller detaches or exits, the stale controlled/viewer marking is now cleared instead of lingering.
spt sendfrom inside an agent’s hosted session no longer mis-stamps the sender. The sender could be stamped ascli@<node>, causing replies to bounce; sender identity now falls back to process ancestry when the session’s environment variables are absent.- Piping
sptoutput to a command that closes early no longer errors. Sending output to something like| headthat closes the pipe now exits cleanly (0) instead of failing with a broken-pipe error.
[0.21.0] - 2026-07-02
A visibility + update-honesty release: the endpoint list is reorganized around machines, and spt update apply / spt daemon status are clearer about which version is actually running.
Added
spt endpoint list --show-all. Suspended (resting) endpoints are now hidden by default to cut clutter;--show-allreveals them. Each machine’s total discloses how many were hidden (nothing silently vanishes), and a corrupt record always shows regardless.spt daemon statusnow reports the running daemon’s version. It shows the version the running daemon was built from beside the installed version and flags a mismatch — so you can tell when an update is on disk but the daemon still needs a restart to fully load it. The--jsonoutput gains matchingbroker_image/broker_stalefields.
Changed
spt endpoint list(andspt whoami) is now grouped by machine, not by subnet. Your own node comes first, then each remote machine alphabetically — every endpoint appears once per machine (no more duplicate rows for a machine reachable through several subnets), with a per-machine total and the subnets it shares with you. The oldENDPOINTS:summary line (which counted a machine once per subnet) is removed.spt update applynow tells you when a daemon restart is needed. On a successful update it notes that daemon-coordinated features keep running the previous version until you restart the daemon, and points you atspt daemon statusto confirm which version is live.spt endpoint list --jsongains a per-endpointendpoint_typefield. Additive — the existing JSON shape is otherwise unchanged.
Fixed
spt update applywhen already up to date no longer errors. Re-running apply on a version that is already installed used to fail with an “access denied” error; it now recognizes the up-to-date state and exits cleanly with a clear message.- Concurrent first-time store initialization no longer fails. Two spt processes initializing the same fresh data store at once (for example the daemon and a command racing on first use) could fail with a “could not lock config file” error; initialization is now race-tolerant.
[0.20.0] - 2026-07-02
A cross-node delivery release: messages to a remote, idle spt-hosted endpoint now arrive immediately, alongside adapter-update, sender-labeling, and auth-recovery fixes.
Added
- New
{node}manifest substitution key. Adapter manifests can reference the advertised node label as a single-token{node}in command templates.
Changed
spt sendwith no explicit sender now stampscli@<node>. A delivered message never shows a blank sender — a message sent without a from-identity is attributed to the originating node instead of arriving empty.- Clearer delivery diagnostics. Spool and idle-drain failures now emit loud, one-shot log lines, and operator notes on message-delivery timing are documented.
Fixed
- Messages to an idle remote endpoint now deliver immediately. A message sent across the network to an spt-hosted endpoint that was idle used to wait for the receiving adapter’s next poll; the daemon now injects it on arrival, and any messages spooled while the endpoint was active drain the moment it goes idle. Cross-node delivery no longer stalls.
spt adapter updateon a profile endpoint now actually updates and reports honestly. Updating a specific profile (--adapter <name>:<profile>) previously could report success without swapping anything; it now performs the swap and only reports success once the adapter has really been replaced.- A perch stranded on a dead session now recovers itself. When the session a perch was pinned to is gone, the next activity re-pins it automatically instead of staying wedged; a pin to a still-live different session is still refused.
[0.19.1] - 2026-07-01
A follow-up field-hardening release. Three bugs surfaced by running spt across real remote nodes — and by a machine losing power mid-write — are fixed.
Fixed
spt rc <endpoint>no longer refuses to attach to a node you reach through more than one subnet. When the same machine was advertised into several subnets,rcwrongly reported that the endpointexists in several subnetsand asked you to disambiguate — but a subnet is not a way to tell nodes apart, so there was nothing to pick.rcnow recognizes the entries as one node and attaches.- A machine that lost power no longer shows up as online forever. If a perch’s on-disk record was destroyed by a hard reset (a power loss can leave the file present but zero-filled), the endpoint used to keep advertising as online. Perch records are now written durably, and a record that is present but unreadable is treated as not-alive — shown as suspended, distinct from one that is simply gone.
- A corrupt local perch now reads as offline everywhere, not just under the Subnet tab. Such a perch was invisible in
spt endpoint listand in thespt endpoint runpicker’s Local and Project views while still showing online under Subnet; all views now agree and show it offline.
[0.19.0] - 2026-07-01
A field-hardening release. Twelve bugs surfaced by running spt across real remote nodes are fixed: cross-node attach and messaging now work and tell the truth, remote presence and counts read correctly, attaching to a running terminal session repaints cleanly instead of corrupting scrollback, spt rc works on Windows 10 / raw PowerShell, and adapter update and digest handle relocated installs.
Added
spt rccan now attach to an endpoint running on another node. Previouslyspt rc <endpoint>only resolved endpoints with a live session on the local machine — a cross-nodeActiveendpoint (visible inspt endpoint list) failed withno live session for endpoint.rcnow resolves the owning node from the registry and attaches over the network, so you can drive a remote endpoint the same way you drive a local one.
Changed
spt endpoint listnow labels the local machine by name. TheLOCAL (this node)header is nowThis node: <node-id>, so a listing captured from one machine is unambiguous about which node produced it.spt endpoint listuses the same status codes and colored markers as thespt endpoint runpicker. The non-interactive listing previously printed raw, text-only status; it now renders the picker’s colored square glyphs and status vocabulary, so both surfaces read identically.spt endpoint rungroups endpoints by machine instead of by subnet. A machine that shares two subnets with you used to appear twice (once per subnet) with duplicate endpoints; it now shows as a single group with the shared subnets listed beneath the machine name.- A lone detached endpoint no longer reads as
Dormant.Dormantis the multi-instance routing state; a single detached instance now displays as online instead of borrowing that label. - The top-right endpoint ID badge is off. The one-shot corner badge scrolled off screen and left artifacts as the hosted TUI animated or resized; it is disabled pending a proper sticky overlay.
Fixed
- Attaching to a running terminal session no longer corrupts the scrollback. Cold-attaching to a full-screen TUI (e.g. Claude Code) used to replay the raw output ring into a fresh terminal — flipping the alternate screen on and off mid-stream and spilling TUI frames into history. The broker now keeps an authoritative screen model and synthesizes a clean repaint of the current screen on a cold attach, so you get the live frame, not a corrupt transcript. (A resume from a known point still re-fetches raw output as before.)
spt rcto an already-running endpoint no longer prints garbled escape codes on Windows 10 / raw PowerShell. The garbling was the same raw-ring replay problem as above (not a client terminal-mode issue —spt endpoint run --attachrendered fine in the same terminal); the clean-repaint-on-attach fix resolves it. VT output is also enabled defensively on legacy Windows consoles.spt rcto a live local endpoint no longer times out withbrain IPC read deadline elapsedafter a self-update. A single panic while the broker’s effect journal was locked could poison it and brick every subsequent attach; the journal now recovers from a poisoned lock, and the loopback attach path fails fast with a real error instead of an opaque 10-second deadline.spt sendacross nodes no longer reportsSENT(WAN)when nothing was delivered. A cross-node send was a fire-and-forget local buffer write — a refused or no-perch delivery was silently dropped while the sender printed success. The receiver now writes the delivery outcome back and the sender waits for it, so a real failure prints an honest line; the dial also tries the last-known direct address first (mirroring the gossip path) instead of forcing a cold discovery on every send.- The remote endpoint count no longer drifts as endpoints are added and purged. A remote viewer’s
--nodescount counted non-routable ghost rows in the denominator and never evicted purged endpoints, producing wrong ratios like0/2or1/3. The count now uses a routable-only denominator, and rows left offline past a grace window are evicted from the gossiped snapshot instead of leaking forever. - A locally-controlled endpoint no longer shows as “ready to control” on other machines. Remote viewers only learned about a remote controller; a locally-driven endpoint gossiped as uncontrolled. The broker now advertises whether anyone (local or remote) is driving an endpoint, so remote viewers see it as controlled.
spt adapter addno longer swallows install errors. Errors from the install-as-first-update step were discarded; the failing step’s output is now surfaced, and the composite post-install step runs at install time.spt adapter updateno longer fails to re-register after fetching. The update derived the install directory from the[update]repo name rather than the adapter’s registered source directory — so after a repo rename it wrote to a fresh empty directory and then failed re-register withos error 2. Update now targets the registered source directory and tolerates a changed update repo.spt endpoint digestnow works for endpoints whose transcript lives under a relocated profile. A profile that relocates the harness transcript tree (via a runtime env likeCLAUDE_CONFIG_DIR) producedNO_DIGESTbecause the on-demand extractor ran without that environment. The digest path now carries the profile’s transcript-location environment to the extractor, which locates and reads the transcript itself.
[0.18.0] - 2026-06-30
A small release that smooths spt update: clearer messaging when the latest version is already downloaded, and a one-step spt update fetch --apply.
Added
spt update fetch --apply. Fetch and install in a single step — it applies the staged update even when the latest was already downloaded (so a no-op fetch still installs). The one-shot “get me to the latest”, replacing the brittlespt update fetch && spt update applychain (which skipped the install whenever fetch found nothing new to download). A genuine fetch problem (bad signature, no build for your platform, a real downgrade) still stops without installing.
Fixed
spt update fetchno longer reports an already-downloaded update as an error. When the latest version was already fetched and only needed installing,spt update fetchprinted a raw internal rejection (UPDATE_FETCH_REJECTED:Rollback { … }) and exited non-zero, which read as a failure. It now says “Update (counter N) is already downloaded. Runspt update applyto install it.” (or “Already up to date”) and exits cleanly; genuine refusals print a plain, readable reason instead of a debug dump.
[0.17.0] - 2026-06-28
A minor release that hardens joining a subnet across the wider internet and makes endpoint presence tell the truth across machines: a join now survives a half-broken IPv6 connection, the join flow asks for the code only after it finds a member and tells you what is happening (and what went wrong), dead endpoints no longer appear online on other machines, and the endpoint picker shows the same detail for remote endpoints as for local ones.
Added
spt subnet joinnow shows progress while it searches. Instead of a single silent “Searching…”, the command prints the elapsed time and the deadline every few seconds, so a slow join reads as “still working”, not “hung”.spt subnet join --verbose. On a failed join,--verboseprints a diagnostic dump — which IP families were usable, the time window it searched, how many attempts it made against the deadline, and the last concrete error — so you can tell a dead subnet from a wrong code from a network problem.- Force an IP family off —
SPT_DISABLE_IPV6/SPT_DISABLE_IPV4. Set either environment variable to make the daemon skip that IP family at startup regardless of what it probes (a deterministic escape hatch, mirroringSPT_NTP_SERVER). Setting both is an error. - The endpoint picker shows remote endpoints in full detail. Endpoints on other machines in the subnet now render with the same state as local ones — bound vs. unbound, who is controlling an endpoint (the controlling node is named in the detail pane), and harness-only endpoints — instead of being flattened to a plain online/offline dot.
Changed
- Joining a subnet now survives a half-broken IPv6 connection. If a machine can resolve IPv6 addresses but cannot actually reach them,
sptused to silently spend its whole join window on the dead path and fail with no error. It now checks each IP family once at startup and uses only the ones that actually work (both, IPv4-only, or IPv6-only), so a join over a broken-IPv6 network succeeds. spt subnet joinasks for the pairing code after it finds a member, not before. The join is now two-phase: it first finds a member of the subnet, then prompts for the code and pairs immediately. Because the code is used the moment you enter it, a slow search no longer causes a freshly-read code to be rejected as “wrong”, and re-entering a code after a typo retries the pairing only — it no longer restarts the whole search. (The non-interactive--codeform is unchanged.)
Fixed
- Other machines no longer show your closed endpoints as online. An endpoint whose session had ended was advertised to other nodes in a way they painted as online (green), even though its own machine correctly showed it closed. A closed-but-machine-up endpoint is now advertised as suspended, so every machine agrees: it reads as suspended (a distinct gray, wakeable), never falsely online.
- A failed subnet join now tells you why. A join that could not find a member used to end with no message at all. The failure is now reported before any code prompt, with the last concrete error (and the full diagnostic dump under
--verbose).
[0.16.0] - 2026-06-25
A minor release adding a one-lever adapter-update arc (a delegated post-step), a global --json for status queries, an incremental digest cursor, a persistent spt rc identity marker, and manifest substitution primitives — plus the removal of spt send --reply-to.
Added
- Composite adapter update —
[update.post]. A manifest can declare a delegated post-step thatspt adapter updateruns after the primary update avenue resolves, so one command both pulls the adapter’s.spt(e.g. from a GitHub release) and runs an in-harness sync. The post-step runs unconditionally (even on an up-to-date no-op), reads a published JSON line on stdin describing the update, and its stdout decides the post-update notice. A post-step failure warns and falls back — it never rolls back the committed pull. - Global
--jsonfor status queries.endpoint list/whoami,daemon status,subnet status/show-code,endpoint description/role,adapter list/version,notif list,grant list,access list,shell list, andhow-tonow accept a global--jsonflag emitting stable, explicit fields for scripted consumption. (Action commands ignore it.) spt endpoint digestincremental cursor.--jsonoutput gains--last <N>(the last N turns;--last 1is the latest turn), a stable per-entryseqthat survives live re-projection and window slides,--after <seq>(only what is newer, with a full-refresh signal if the cursor fell out of the window), apartialflag on the in-progress turn, and a per-entryts— so a consumer can process turn-ends incrementally instead of re-reading the whole window.spt rcidentity marker. An attached controller now shows a persistent top status row — right-alignedSUBNET : ENDPOINT_ID @ NODEin cyan — so you always know which endpoint you are driving. It re-asserts across alt-screen, resize, and scroll-region resets.- Manifest substitution primitives. Two adapter-static substitution keys —
{adapter_dir}(the adapter’s install dir, which survives updates) and{adapter_name}— are available wherever command/string substitution runs, and[strings]values are now substituted atget-stringread time. This lets an adapter resolve a path to its own packed binary without spt-core ever executing it. [message-idle-translation-binary]takes acommand. The idle-delivery translation binary can be declared with acommand(program token plus args, with adapter-static substitution) instead of the barepath, so it can be invoked as a subcommand of a consolidated adapter binary. The spawn and stdin/stdout protocol are unchanged.- Empty-scope creation flow. Running
spt endpoint run(or barespt) on a node with no endpoints at all now opens directly on the adapter-creation screen instead of an empty picker.
Changed
[message-idle-translation-binary].pathis deprecated in favor ofcommand. It still parses (and warns at registration); exactly one ofpath/commandmay be set.
Removed
spt send --reply-tois removed. The send target is now a required positional argument; reply correlation rides the structuralfromon the message envelope. (The flag was a target-fallback nicety with no wire effect.)
[0.15.0] - 2026-06-24
A minor release adding per-message delivery controls to spt send, an opaque metadata payload, a resume-context pull command, and a Windows console-flash fix.
Added
spt senddelivery-window controls.--idle-onlydelivers a message only while the target is idle (the idle/wake window), holding until then;--active-onlydelivers only through the target’s own poll, without ever waking an idle target. (--active-onlyreplaces the old--deferred, which still works as a hidden back-compat alias.)spt sendchannel controls.--prefer-nativedelivers through the target’s translation binary when one is running and falls back to the normal channel otherwise;--force-nativedelivers only through the translation binary, with no fallback or spooling (reported undelivered if none is running).spt send --ephemeraldrops a message that can’t be delivered to a translation-binary target within its window, or that expires (TTL), instead of spooling it. (A harness-relay target with no live listener still spools — that evaporation case lands in a later release.)spt send --json-payload <JSON>attaches an opaque JSON metadata blob alongside the message body, carried verbatim for the receiving adapter to parse (it does not replace the body).spt api psyche-download <id>pulls an agent’s resume context for an adapter to restore at session start, appending any not-yet-synthesized commune/signoff updates.
Fixed
- Inbound messages are no longer silently lost if the delivery worker faults mid-handoff — they re-spool and surface on the next poll (closes the transient gap left after the v0.14.3 raw-inject removal).
- The spt-hosted translation binary no longer flashes a console window on Windows.
[0.14.3] - 2026-06-23
A patch release hardening idle message delivery to spt-hosted endpoints.
Fixed
- Idle messages to an spt-hosted endpoint are no longer silently dropped when delivery can’t complete. If no working translation helper is available to submit an incoming message to an idle spt-hosted endpoint, the daemon now queues the message for poll-based delivery and honestly reports it as queued — instead of typing it into the endpoint’s terminal without ever submitting it (which looked delivered but was not). Delivery via a working helper is unchanged.
[0.14.2] - 2026-06-23
A patch release fixing idle message delivery to spt-hosted endpoints.
Fixed
- Messages delivered to an idle spt-hosted endpoint now submit instead of stalling half-typed. The daemon now resolves an adapter’s idle-delivery translation binary against the adapter’s install directory, so it launches correctly; previously the helper failed to start and an incoming message was typed into the endpoint’s terminal but never sent.
[0.14.1] - 2026-06-23
A patch release: spt adapter add no longer clobbers an existing install and reports its outcome more clearly, and the interactive spt endpoint run picker now lets you choose a new endpoint’s home subnet on multi-subnet nodes.
Added
- The interactive
spt endpoint runpicker offers a home subnet. On a node that belongs to more than one subnet, choosing Create new now prompts for which subnet the new endpoint should home to, with your most-recently-used subnet first. (The non-interactive--subnetpath from 0.14.0 is unchanged.)
Changed
spt adapter addis non-destructive. Re-adding an already-registered adapter is now refused, with guidance to usespt adapter updateorspt adapter removeinstead. A fresh install stages the new files and swaps them in only once it is complete, so a failed or repeated add can no longer clobber a working install or leave it half-written (previously this could surface as a cryptic “os error 2”).spt adapter addreports its outcome more clearly. Its messages now distinguish an adapter that is installed and ready from one whose install is still pending.
[0.14.0] - 2026-06-23
A release focused on how endpoints are created: each endpoint now picks its subnet once, when you create it, on nodes that belong to more than one subnet; and you can attach to an endpoint while it is still starting up, before it is ready to receive messages.
Added
- An endpoint chooses its subnet when you create it.
spt endpoint runhomes a new endpoint to a single subnet for its lifetime. On a node that belongs to just one subnet this happens automatically. On a node in two or more subnets,endpoint runnow settles the subnet up front: interactively it proposes your most-recently-used subnet and asks you to confirm; non-interactively it requires--subnet <name>and, if you omit it, refuses immediately with the list of available subnets instead of hanging. Previously a multi-subnet node could stall silently during endpoint bringup. - You can attach to an endpoint before it finishes starting. Between the moment an endpoint is spawned and the moment it binds, it now accepts a connection:
spt rc <id>(andspt endpoint run --attach) drops you into the live pre-bind session, so you can watch startup or clear a bringup prompt before the endpoint is ready. Such an endpoint is not message-addressable yet — it appears as a hollowUNBOUNDrow in the endpoint picker,spt endpoint list, andspt whoami, distinct from an offline endpoint.
[0.13.2] - 2026-06-22
A release focused on adapter packaging and updates: one adapter package can cover several platforms, adapters update live without restarting your agents, installs can pull from private GitHub repositories, plus a few adapter-tooling conveniences.
Added
- One adapter package can cover multiple platforms. A
.sptadapter can now bundle binaries for several operating systems and CPU architectures alongside one shared manifest; installing extracts the shared files plus the binary for your platform. Existing single-platform packages keep working. - Adapters update without restarting your agents. When an adapter updates, the daemon stops just that adapter’s background binary, swaps it in place, reloads its manifest, and restarts it — running agents continue across the update.
- Adapter installs and updates can use private GitHub repositories.
spt adapter add --releasecan fetch from a private repo through the GitHub CLI, with no access token to manage. New--gh/--httpsflags choose the transport (default: automatic). - Adapters can show a notice after they update. An adapter may declare a short Markdown message shown once, only when an update is actually applied.
spt adapter version <name>prints an installed adapter’s version.spt adapter digest-proofandspt adapter translate-proofcan test an unpackaged adapter via new--dir/--manifestoptions — proof a development or bare-file adapter before it’s installed.
Fixed
spt --helpand the CLI reference no longer leak internal tracking codes. Generated help and reference text are swept clean of internal identifiers.
[0.13.1] - 2026-06-22
A patch release: an author-time proof tool for idle-delivery translation binaries, plus a correction to the translation-binary contract docs.
Added
spt adapter translate-proof <adapter> --event '<EVENT…>'— validate an adapter’s[message-idle-translation-binary]without a live session. It spawns and feeds the declared binary exactly as the daemon does at idle delivery and prints the keystroke commands it emits ({key}/{text}/{delay_ms}/{commit}), failing a binary that emits nothing or never sends a terminating{commit}. The author-time mirror ofspt adapter digest-proof.
Fixed
- The
[message-idle-translation-binary]contract now documents{commit}. The published contract had omitted the mandatory{"commit":true}sequence terminator (and its degenerate example would have faulted at the 5-second commit deadline); it now describes{commit}, the inject floor, and the commit-deadline behavior.
[0.13.0] - 2026-06-21
A minor release: idle message delivery for spt-hosted endpoints now runs through an adapter translation binary, spt rc gains real paste, key, and mouse support on Windows and stays attached under heavy output, session resume actually resumes a prior session, and the daemon no longer flashes a console window.
Added
- Idle message delivery for spt-hosted endpoints now uses a translation binary. An adapter can declare a
[message-idle-translation-binary]; spt-core brings it up alongside the endpoint, where it polls the relay for incoming messages and optionally emits keypresses, delays, and text injection that spt-core applies to the endpoint’s terminal. Idle delivery now flows through the relay poll for every endpoint, instead of spt-hosted endpoints falling back to direct PTY injection. - Windows paste in
spt rc. Ctrl+V and right-click now paste the local clipboard into the attached agent as a single bracketed paste. - Windows special keys in
spt rc. Arrow keys, Home/End, Delete, function keys and other special keys are translated to terminal sequences and reach the agent — previously only plain characters got through. spt rcforwards mouse scroll to the agent’s terminal.
Fixed
- Pasting or typing into an spt-hosted endpoint no longer freezes the daemon. A large paste or input burst could wedge the daemon’s input path so every new attach died; input now runs on a dedicated per-session writer and never blocks the daemon (this also covers the effect-journal stall on interactive input).
- Session resume actually resumes now. Agent endpoints track session history and offer explicit session resume via
spt endpoint run(--resume <session>or Resume from history); the adapter manifest can now declare a[session.resume]command so the relaunch reattaches the prior session instead of always starting a new one. The run picker now shows each row’s working directory and local time. spt rc --viewviewers survive a high-output terminal. A viewer no longer dies when the output backlog rolls over or it is briefly evicted — it snaps forward or skips to live instead of failing — and a slow controller can no longer starve a concurrent viewer.spt rcno longer races a just-started agent. Attach now waits for the endpoint to come online, instead of failing when you attach immediately afterspt endpoint run.- The daemon no longer flashes or respawns a console window on Windows.
- Windows Backspace and Ctrl+Backspace now do the right thing in
spt rc. Backspace deletes a character and Ctrl+Backspace deletes the previous word (Windows-native), instead of both deleting only a character.
Changed
- The
spt endpoint runpicker is clearer. It opens directly on an existing pick, auto-attaches, shows the controlling node name, and produces clean bring-up output. - Human-prose command output renders Markdown. Prose output (how-to topics and similar) now shows styled headers and emphasis in a terminal and clean plain text when piped — matching the v0.12.1
--helpfix.
[0.12.1] - 2026-06-18
A patch release fixing the live-agent lifecycle in a real terminal: attaching to a running agent, keeping it alive when you close the terminal, and the daemon staying responsive now all work as intended. Also polishes spt endpoint list, the run picker, and spt --help.
Fixed
- Attaching to a running agent now shows its output.
spt rc <id>against an agent started withspt endpoint runnow delivers the agent’s terminal output immediately, instead of connecting to a blank screen. - Closing the terminal that started an agent no longer kills it. When
spt endpoint runlaunches the background daemon for you, closing that terminal tab or window now leaves the agent running and re-attachable withspt rc <id>. - A crashed agent with a disconnected viewer no longer freezes the daemon. A dead agent process combined with an abruptly-closed
spt rccould previously wedge the daemon so new agents wouldn’t start; the daemon now stays responsive and marks the dead agent offline.
Changed
spt endpoint listandspt whoamialways include this machine’s local agents — your own just-started agent always shows up. The--localflag has been removed: local agents are now always merged into the listing.- The
spt endpoint runpicker offers the right action. An already-running agent now offers Attach instead of a meaningless “Start now”. spt --helprenders cleanly. Help text no longer shows raw**and backtick characters — emphasis and command names display as styled text in a terminal, and as plain text when piped or redirected.
[0.12.0] - 2026-06-18
A minor release fixing the live-agent lifecycle: running an agent, attaching to it, and stopping or restarting the daemon now behave correctly, and an agent’s reported status reflects whether it is actually reachable.
Added
spt endpoint purge <id>— removes an offline endpoint’s record and leftover files in one step (offline endpoints only).- Agents started with
spt readynow appear in thespt endpoint runresume-from-history picker when offline. Previously only live agents were offered there, so a message-listener agent couldn’t be relaunched from history; now it can.
Fixed
- An agent’s status now reflects whether it is actually reachable. A daemon-hosted agent whose session has gone away is now marked offline on the next check instead of staying “online” indefinitely. Agents reached over a relay are unaffected.
- Attaching to an agent no longer hangs on a dead or silent session.
spt rcnow fails fast with a message instead of showing an endless blank screen, and stopping an endpoint marks it offline. spt daemon stopnow fully stops the daemon. It finishes releasing its sockets before reporting success, and it cleans up the agent and Psyche processes it launched instead of leaving them running.- Stopping or signing off a single agent now also shuts down its Psyche. Previously, stopping one agent (without stopping the whole daemon) left its Psyche process running until the next
spt daemon stop; the Psyche is now reaped as soon as the agent is un-hosted. - Restarting the daemon no longer revives stale “online” agents that are not actually running, and no longer leaves a duplicate Psyche behind.
[0.11.0] - 2026-06-17
A minor release: messages now reach daemon-hosted agents, endpoint environment variables are populated, and several errors are clearer.
Fixed
spt sendnow delivers to an agent whose terminal is hosted by the daemon. When the target has nospt api listenrelay (the daemon holds its terminal directly),spt sendpreviously queued the message silently. It now injects the message into the agent’s session and reports “Sent” only once delivery is confirmed — otherwise it queues it (“Queued”) as before, and never reports a false “Sent”.spt endpoint runnow fills in[env]values. Placeholders such as{id}in an adapter’s[env]entries (for exampleSPT_ENDPOINT_ID) were never substituted, so an agent launched without explicit flags came up with an empty endpoint id and never registered. The values are now substituted and set on the launched process.- A daemon-hosted terminal now reports a clear message when the daemon is stopped, instead of failing with a raw “failed to fill whole buffer” that looked like a crash.
- Clearer error when an adapter’s manifest has not been extracted yet. Using such an adapter now reports an actionable message (and logs it as skipped) instead of a raw “os error 2” and a silent drop.
- A removed endpoint no longer lingers with a stale status — it is now shown as offline.
[0.10.0] - 2026-06-17
A minor release: richer, consistent agent status in the picker and spt endpoint list.
Added
- Four-state endpoint status in the picker. Beyond offline and online, a live agent running only inside its harness (with no hosted terminal) now shows as “online — harness only”, and an agent whose session is currently being driven by someone shows as “online + controlled” — so you can tell at a glance how an agent is reachable.
Changed
- Subnet entries now show a readable node label instead of a raw key prefix, rendered the same way in both the picker and
spt endpoint list. spt endpoint listcolumns are now aligned instead of ragged, and the subnet listing notes that it is the subnet view, so a local agent you just started isn’t mistaken for missing.
Fixed
- The picker now loads each agent’s project history — it previously always showed empty.
- A self-owned agent listed under both Local and Subnet no longer shows conflicting status. The live local status is now authoritative for both listings.
[0.9.1] - 2026-06-17
A patch hardening harness-adapter resolution and making a stale-daemon error actionable.
Fixed
- Going live or ready still resolves the adapter when the harness executable was renamed in place. After an in-place update that leaves the running program renamed (e.g.
claude.exe.old.<timestamp>), the daemon now matches it to its adapter by the name stem before the first dot, so bringup keeps working instead of failing to find the adapter. - A daemon left running from before 0.9.0 now reports an actionable error. Seeding a session against an out-of-date daemon previously failed with a cryptic “failed to fill whole buffer”. It now explains the cause and tells you to run
spt daemon stop(the daemon restarts automatically on the nextspt apicommand).
[0.9.0] - 2026-06-17
A minor release: harness-hosted agents go live (or ready) without naming an adapter — the daemon resolves it from the running session.
Added
[adapter] host_binariesmanifest field — declares which harness executables an adapter hosts; the daemon matches a live session to its adapter by the running binary.spt adapter use <adapter>[:profile]— sets the default adapter profile per harness binary. Durable (survives adapter updates); without it, the most-recently-registered matching adapter is used.
Changed
- Going live or ready under a harness no longer requires
--adapter.spt api seedrecords just the session (pid + id);spt api listenresolves the owning adapter automatically from the session’s process — restoring the one-step legacy bringup.--adapterremains an optional override for adapter development.
[0.8.4] - 2026-06-17
A patch fixing a Windows launch failure for harness/shell adapters whose start command is a script.
Fixed
- Windows: harness and shell sessions launched via a script command now start. A start command that resolves to a Windows batch file (
.cmd/.bat) or an extensionless CLI shim (e.g. the nodeccslauncher) previously failed with “not a valid Win32 application” (os error 193) — the daemon tried to execute the non-PE file directly. spt-core now resolves the program throughPATHEXT(preferring real executables) and runs script targets through their interpreter (cmd.exe/ PowerShell). No change on macOS/Linux.
[0.8.3] - 2026-06-16
A reliability patch: a dead or unresponsive subnet peer can no longer stall background sync.
Fixed
- A dead or unresponsive peer can no longer stall background sync. The daemon now bounds every network operation it makes on a live agent’s behalf, so a roster peer that has gone offline (or stopped responding mid-handshake) fails fast as an ordinary, recoverable error instead of hanging the daemon’s peer-sync loop. Previously such a peer could freeze background synchronization for tens of minutes and force repeated internal restarts; now the peer is simply skipped and retried on the next cycle, and a healthy node’s sync keeps flowing. Normal peers are unaffected (no added latency).
[0.8.2] - 2026-06-16
A reliability patch for command-template argument handling and dead-on-launch Psyche reporting.
Fixed
- Command-template substitution now fills each argument as a single element: a multi-word or quoted value (e.g. a Psyche prompt) is passed through intact instead of being split or injected across multiple arguments.
- A daemon-hosted Psyche that launches but exits immediately is now correctly reported as a failed host (the harness-reachable psyche-host-error signal added in v0.8.1), instead of leaving a phantom “online” entry backed by a dead process.
[0.8.1] - 2026-06-16
A visibility fix: a daemon that can’t host a live agent’s Psyche now reports it, instead of leaving the agent looking online with no cause.
Fixed
- Harness-reachable psyche-host failure signal. When the daemon cannot host a live agent’s Psyche (for example, the adapter’s psyche binary is missing from its install directory), the failure is now recorded on the agent’s perch state and surfaced by
spt endpoint list/spt whoamias apsyche-host: FAILED (<reason>; <n> attempt(s); <ts>)line. Previously this failure was silent — visible only on the daemon’s internal stderr — leaving an agent reportingonlinewith no Psyche and no visible cause. Liveness (status) is unaffected and remains authoritative; the newpsyche_host_errorfield is additive and backward-compatible.
[0.8.0] - 2026-06-16
Remote shells you can hand off and watch across the subnet, two new in-CLI how-to guides, plus adapter-distribution and install fixes.
Added
- Drive and watch a hosted shell across the subnet. Building on the remote-terminal host from 0.7.0, a hosted session now has a single live driver and any number of read-only watchers, with explicit per-capability consent before a sensitive action runs. You can tunnel into a same-node session, and a gateway node can own the shell on behalf of a peer it fronts. (Cross-node tunnelling is not yet available.)
- Two new
spt how-toguides:subnetandlive. Pairing machines (create vs join, the 6-digit code, reaching remote agents) and running as a live agent (the persistentspt api listenrelay, the Psyche seam) now have task-oriented in-binary topics instead of dead-ending. - Update an adapter from a GitHub release, optionally signature-checked. An adapter’s
[update]feed can now be a GitHub release (avenue = "gh_release");spt adapter updatepulls the newer.spt. Declare asigning_keyand verification is fail-closed — an unsigned or wrong-signature artifact is refused, not installed. spt apiresolves an adapter’s manifest and install directory from--adapter. Pass--adapter <name[:profile]>without--manifestand spt looks both up from the registry;--manifestbecomes an override for an unregistered or local manifest.
Fixed
- A bundled adapter binary resolves from its install directory before
PATH. An adapter that ships its own helper binaries — a[digest]extractor, the Psyche-spawn command — now finds them in the adapter’s install directory first, so a bare program name in a manifest works without you placing it onPATH. - The Windows at-logon task starts the daemon in the background. It now launches detached (
spt daemon start) instead of holding a foreground console window.
[0.7.3] - 2026-06-15
Install an adapter straight from a GitHub release.
Added
spt adapter add --release <user/repo>installs an adapter from a GitHub release. Point it at a repo — optionally with--tag <tag>and--asset <name>— and spt downloads the published.sptarchive, extracts it, and registers the adapter. This lets you ship an adapter that lives inside a larger repository, where cloning the whole tree with--githubdoesn’t fit. It trusts HTTPS and GitHub for the download, and doesn’t change how an already-installed adapter updates.
Changed
- Clearer adapter-installation guidance: a spelled-out post-install activation step and the distribution-repo layout for
--github.
[0.7.2] - 2026-06-15
A digest-proof fix: spt adapter digest-proof now works with the documented {session_id} example templates.
Fixed
spt adapter digest-proofnow fills the same substitution keys the live extractor does. A proof run previously supplied an empty key map, so any extractor template using{session_id}(the shape in the published examples) failed instead of producing a sample. It now populates{id}and{session_id}to match runtime, with an optional--sessionto pin a specific value.
[0.7.1] - 2026-06-15
A consistency-and-clarity patch: messages now arrive in one envelope across every channel, and spt update apply confirms success in plain language.
Changed
- One message envelope across every channel. Messages drained with
spt api pollandspt api worker-pollnow arrive in the same<EVENT type="msg" from="…">…</EVENT>envelope as the livespt api listenstream — one format to parse everywhere, and several queued messages are now self-delimiting. (Building an adapter? Parse the<EVENT>envelope on the poll channel; the older internal frame is gone.)
Fixed
spt update applynow confirms the update applied, in plain language. A successful apply printsUpdated spt-core to vX.Y.Z.with a link to the changelog, instead of the earlier provisional “trial” wording that left a finished update looking unresolved. (The changelog link now points at the canonicalgithub.comaddress.)
[0.7.0] - 2026-06-14
Remote terminals land. You can now bring an agent up under spt’s own terminal host and attach to it from your own machine or across the subnet — drive it, or just watch — with a real one-at-a-time controller and any number of read-only viewers.
Added
spt endpoint runbrings an agent up under spt and attaches you to it. spt hosts the session’s terminal itself;spt endpoint run --adapter <a> --id <name>starts it and drops you in. Detach (seespt rc) and it keeps running headless until you come back — from this machine or another node.spt rc <id>— attach to a running session’s terminal. Scrollback replays, live output streams, your keystrokes drive it. Detach with ctrl-b thend(the session keeps running);ctrl-b ctrl-bsends a literal ctrl-b. Works the same whether the session is on this machine or across the subnet.- Controller / viewer model. One person drives at a time (the controller); any number can
spt rc <id> --viewto watch read-only (no input, never resizes the session). The controller’s window size drives the terminal. spt rc <id> --take— take control. If someone else is driving,--takekicks them (they get a loud “you were taken over by …” notice and are detached) and you become the controller. A plainspt rc <id>on a session someone else controls now refuses with guidance (it tells you to--viewor--take) instead of silently stealing control.spt endpoint runis now an interactive picker. Run it bare (no--adapter/--id) and pick Create new (choose a harness adapter + profile, name the endpoint) or Pick existing (browse by project / local node / subnet with live status, type-to-filter, and a description pane), then attach / start / view / resume-from-history. The flagged form is unchanged for scripts. Barespt(no subcommand) opens the same picker on an interactive terminal — a pipe, redirect, or CI run still prints help instead. A controlled endpoint in the picker shows View and Kick and take control (not a plain attach), pinned withcontrolled by <node> (+N viewing). Presssto bake the current selection into a project-rootspt-<id>launcher shortcut (an adapter can brand it, e.g.cc-<id>, via the new[adapter] shortcut_basenamemanifest field).spt subnet joinshows a QR code + setup code on success. After joining, scan the QR (or read theotpauth://code) to re-provision an authenticator app for the subnet.- Privilege-gated commands self-elevate, cross-platform. When a command needs elevation, spt re-launches itself the right way for your system — a Windows UAC prompt, a Linux desktop
pkexec/terminalsudo, or inlinesudoin a terminal — and prints the exact command to run by hand when it can’t. spt spt— ???
Changed
spt whoaminow shows the full picture. It is an alias forspt endpoint list— your own endpoint pinned first (with its description, if set), then the subnet roster — instead of just printing a bare id.
[0.6.0] - 2026-06-13
The session digest grows up — its own adapter seam, it follows an agent across /clear, and it shows the context spt itself feeds the agent.
Changed
- The session digest gets its own adapter seam, follows an agent across
/clear, and shows the context spt feeds it. An adapter now declares a[digest]extractor that maps its native log to the digest’s{role, text, tool, ts}contract — its own manifest section, separate from[history](which stays full-fidelity for the echo-commune). The digest spans a/clearor/compact: it enumerates an endpoint’s recent sessions and shows a── /clear ──divider instead of going blank at every reset. It also interleaves spt’s own injected context (session-start Psyche download, echo mirror, incoming messages) with the agent’s activity, in time order. Newspt adapter digest-proof <adapter> --sample <log>runs your extractor against a real log and prints exactly what parsed, what rendered, and every dropped line with the reason — no more silent-empty digest.- Breaking (adapter authors): this supersedes the v0.5.0 guidance to emit the digest contract through your
[history]normalizer. Declare a[digest]extractor (or push viaspt api digest-entry) instead; one[history]normalizer can no longer serve both the opaque echo-commune and the contract-typed digest.
- Breaking (adapter authors): this supersedes the v0.5.0 guidance to emit the digest contract through your
[0.5.0] - 2026-06-13
Adapter customization and richer session surfaces — make an adapter your own without forking it, give an agent a durable role, mark who a message came from, and get an at-a-glance “what is this agent doing” view for any session.
Added
- Adapter profiles — customize an adapter without forking it.
spt adapter create-profile <adapter> <name>makes a named variant of an installed adapter (its own environment, prompts, and capabilities); launch it by addressingadapter:name. A profile you create locally survives updating or re-adding the underlying adapter, andspt adapter listshows each profile as its own spawnable option.spt adapter delete-profileremoves one. (A profile may only tighten what the adapter allows — an attempt to loosen a capability is refused at registration.) - Adapter config values.
spt adapter set-string <adapter> <key> <value>andspt adapter get-string <adapter> <key>read and write an adapter’s named settings — per-profile when you address a profile. - Keyword hints. An adapter can teach its own commands in context: when a keyword it declares appears in one of your messages, a one-line tip surfaces — at most once per session, so it never nags.
spt endpoint role— a durable agent role. Set a free-form statement of what an endpoint is for; it is shown to the agent first, at the start of every new session.spt endpoint roleis the only thing that writes it — nothing automated ever overwrites your wording.spt api digest-entrylets a harness with no readable session log feed its activity directly, so even those sessions show a live digest.
Changed
- The live session digest now works for any session, not just terminal-hosted ones.
spt endpoint digest <id>builds its at-a-glance view from a session’s normalized logs instead of scraping the raw terminal stream — so a session spt-core doesn’t host in its own terminal (for example a Claude Code session) now shows a digest too.--followstill streams changes as they happen.- Breaking (adapter authors): the
[pty_digest]manifest section is removed. The digest now rides your[history]source — emit your history records as{"role": …, "text": …, "tool": …}JSON (or push them withspt api digest-entry) and the digest builds itself. No digest-specific manifest section is needed.
- Breaking (adapter authors): the
- Messages now carry who sent them — a person or an agent. A message you send is delivered marked as user-sent, and the daemon re-stamps anything that falsely claims to be from a user. A human-backed Gateway endpoint is accepted as a first-class endpoint — addressable, able to own shells, and able to subscribe to digests — including from another machine on your subnet.
[0.4.2] - 2026-06-11
Fixed
- (Linux) An update now takes effect immediately — no manual restart needed. On Linux, applying an update replaced the program on disk but the background service kept running the previous version until you restarted it by hand, so a fix could sit installed-but-inactive without you realizing the update hadn’t truly taken hold. The service now relaunches its worker onto the freshly applied version on its own — the seamless behavior Windows already had. As an added safeguard on every platform, an update that somehow comes up running the wrong version is now detected and rolled back automatically instead of being recorded as applied.
[0.4.1] - 2026-06-11
Fixed
- An unreachable peer can no longer stall your node’s background work. If another machine on your subnet dropped off mid-exchange — a network drop, a sleep, a hard crash — the daemon’s outbound loops (peer sync, notifications, update checks) could hang waiting on it, in the worst case for hours, until something restarted the service. Now a stalled exchange gives up on its own in under a minute and the background loops resume, so one dead peer never freezes the rest of your node.
[0.4.0] - 2026-06-10
Fixed
spt update applynow actually runs the new version — no manual restart needed. Previously, applying an update replaced the program files on disk but the already-running background service kept executing the old code until you manually restarted it. A fix could sit installed-but-inactive, and you’d keep seeing the old behavior — sometimes for a long time without realizing the update hadn’t truly taken effect. The background service now relaunches itself onto the freshly installed version automatically, so an update goes live the moment you apply it. The swap is seamless: terminal sessions and network connections the daemon is hosting stay alive across it — nothing you’re running gets dropped.- A failed update now rolls back on its own. If a newly applied version can’t start cleanly, the service automatically returns to the last version that was working instead of leaving you with a daemon that won’t come up. Your machine keeps running on a known-good build while you sort out the bad release.
Changed
- (Windows) The background service no longer flashes brief console windows when it starts or restarts its internal worker process.
[0.3.2] - 2026-06-09
Fixed
spt update fetchcan no longer end up installing another platform’s binary. In a mixed Windows/Linux fleet, fetching an update on one machine and letting another machine pull it peer-to-peer could hand that machine a build for the wrong operating system — leavingsptunable to start.spt update fetchnow downloads the signed, multi-platform update set, so every machine installs (and re-shares to its peers) the build for its own platform. As an extra safeguard,spt update applyrefuses any staged update whose target platform can’t be confirmed to match this machine. If you have an update staged from 0.3.1, re-runspt update fetchon 0.3.2 to replace it with the platform-safe set.
Changed
spt update applyprints a friendly confirmation — for exampleUpdated spt-core to v0.3.2.followed by a link to the changelog — instead of a terse internal status line.
[0.3.1] - 2026-06-08
Added
spt update fetch— pull and stage the latest signed release straight from the project’s GitHub releases, thenspt update applyto install it. This bootstraps the first machine in a fleet (or any machine with no peer to update from), which previously could only receive an update from another machine that already had it. The download is verified against the same signed-release keys as peer-to-peer updates. Add--tag vX.Y.Zto fetch a specific version.
Fixed
- Messages sent from Windows no longer arrive garbled. A message piped into
spt sendorspt ringfrom a Windows shell (whose text carries a carriage return) could corrupt how the message displayed on the receiving machine. The message codec now neutralizes carriage returns, andsend/ringtrim their input likenotifyalready did. - A node is no longer stranded offline after a reboot. If the daemon started before the machine’s network was ready (common immediately after boot), it used to come up with no connection and stay that way until you manually restarted it —
spt daemonwould just report the peer pump as “STALLED”. Now the daemon keeps retrying the network in the background and brings itself online once the network is up, with no restart needed. While it’s waiting,spt daemonreports “no connection” honestly instead of a misleading stalled-pump message. (On Linux the installed service now also waits for the network at boot.)
[0.3.0] - 2026-06-08
Added
spt subnet revoke <node>…— remove one or more machines from a subnet across the whole fleet, not just locally. It tells every member to drop the node within moments, then rotates the subnet’s shared secret so the removed machine is locked out and must re-pair to come back. By default the rotation is batched at the end of a one-hour window — several revokes in that window share a single rotation, and any member that was briefly offline heals automatically across it. Pass--force-rotate-seedto rotate the secret immediately (the compromised-machine path; a member that’s offline at that moment will have to re-pair rather than auto-heal). Name each target by hostname, key prefix, or full key. Requires running elevated. This is the fleet-wide counterpart tospt subnet prune, which only cleans a dead node off the local machine.spt daemon start— bring the daemon up in the background, idempotently. Whensptis installed as a service (the Linux per-user service, or the Windows logon task),startandstopnow drive that service instead of a stray hand-started daemon — so the two never fight each other for the connection.spt daemon starton an already-running daemon just says so and does nothing.
Changed
- Subnets are now a full mesh. (Breaking — see the upgrade note.) Every machine in a subnet now connects directly to, and shows, every other member in
spt subnet status --nodes— previously you mainly saw the machines you had paired with directly. Membership in the subnet is now what grants trust, replacing the separate per-peer trust list that earlier versions kept.- Upgrading from 0.2.0: there is no automatic migration of the old trust list, so after updating, re-pair your machines into their subnets (
spt subnet join, or create + invite from a seed holder) to rebuild membership. Until a machine is re-paired, it won’t be reachable in its subnets.
- Upgrading from 0.2.0: there is no automatic migration of the old trust list, so after updating, re-pair your machines into their subnets (
spt daemon stopis service-aware. If a managed service owns the daemon,stopasks the service manager to stop it cleanly (so it doesn’t immediately restart), instead of signalling the process directly. A hand-started daemon still stops the same way as before.spt daemon runis now strictly foreground on every platform — it stays attached to your terminal until you stop it (the form the installed service uses). For a background daemon, usespt daemon start. On Windows, runningdaemon runfrom an elevated shell now refuses with a hint rather than silently disappearing into the background.spt daemon statusshows what manages the daemon — whether a service owns it (and is active) or it was started by hand.
Performance
spt subnet status --nodesis much faster when several nodes are offline. It now checks all the quiet nodes at once, so the view comes back in about the time of a single check instead of stacking the wait up node by node. (This matters more now that a subnet is a full mesh and you see every member.)
Fixed
- A peer’s name no longer disappears when it goes offline. Once you’ve seen another node’s hostname in
spt subnet status --nodes, it now stays shown even after that node goes offline — previously the name reverted to a bare key after the node went quiet for a while. The name is only forgotten when you explicitlyspt subnet prunethat node.
[0.2.0] - 2026-06-08
Added
spt endpointcommand group. A single home for everything you do to an endpoint:spt endpoint fork,suspend,wake,shutdown,rename,stop, anddigestall live here now. (See Changed — this is where these moved from.)spt endpoint list— one combined view of every endpoint you can see, grouped by subnet, with your own endpoint pinned at the top.spt endpoint list --localshows just this machine’s endpoints.spt endpoint list --subnet <name>filters to one subnet.spt endpoint list --detailadds each endpoint’s description blurb.
spt endpoint description [set]— read or write an endpoint’s description blurb (bare command shows it,setwrites it).spt endpoint access— per-endpoint access control (allow/revoke/open/list), scoped to the individual endpoint.spt daemoncommand group:spt daemon status(or barespt daemon) — a node status view: whether the daemon is running, its background-sync health, your subnets, and your local endpoints.spt daemon stop— cleanly stops the running daemon.spt daemon run— runs the daemon in the foreground (previously a hidden command).
- Pause and resume a subnet without stopping the daemon:
spt subnet detach <name>— stop advertising and connecting for that subnet (peers see you go offline for it) while everything else keeps running.spt subnet attach <name>— start serving it again.- Add
--saveto either to make that choice the default the next time the daemon starts. spt subnet statusnow shows a per-subnet state for each subnet (serving / detached / no connection).
- Leave a subnet:
spt subnet leave <name>removes the subnet and its trust completely from this node. - Clean up dead nodes:
spt subnet prune <node>removes a stale node’s trust so this machine stops trying to reach it. You can name the node by its hostname label, a key prefix, or the full key; it refuses if the name is ambiguous or refers to yourself. - Node names in
spt subnet status --nodes. Each node now shows its hostname label and can be addressed as@<hostname>in commands. Nodes that aren’t running any endpoints still show their hostname instead of a bare key. - Automatic re-pair cleanup. If you reinstall or regenerate a node’s identity and pair it again from the same machine under the same name, its old, now-dead identity is removed automatically during pairing — no manual prune needed.
- Firewall setup on Windows. The installer (when run elevated) now adds the inbound network rule
sptneeds so other nodes can reach you. If it wasn’t added,spt subnet statusand the “coming online” banner now tell you the rule is missing and print the exact command to add it. - Starts on boot. The installer now registers
sptto start automatically — at login on Windows, and as a per-user service on Linux — so your node is reachable after a reboot without running a command first.
Changed
- BREAKING — commands have been reorganized; old spellings no longer work (no aliases). If a command isn’t found, check its new home below. The agent-messaging commands you use most are unchanged:
spt send,spt ring,spt ready,spt whoami, andspt how-toall stay where they are, as does top-levelspt notif.- These endpoint commands moved under
spt endpoint:fork,suspend,wake,shutdown,rename,stop,digest. For example,spt fork …is nowspt endpoint fork …. - The old
resourcesview is gone; its listing is nowspt endpoint list --detailand its per-endpoint blurb is nowspt endpoint description. spt notify …moved tospt subnet notify [message] [--target <subnet>]. With no--target, it sends to your home subnet; if you have no home subnet and don’t pass--target, it refuses rather than guessing.- Stopping/checking the daemon moved under
spt daemon(spt daemon stop,spt daemon status).
- These endpoint commands moved under
spt subnet statustells the truth about a stopped daemon. A node with no subnets now reads “this node is standalone” and no longer implies messaging works while the daemon is down. If the background sync has stalled, the status view says so instead of looking healthy.- Cleaner node listing. In
spt subnet status --nodes, a normally-named node now shows just its hostname (e.g.KITSUBITO) instead ofKITSUBITO (43a51d9a…); the extra key prefix only appears when two nodes share the same hostname and need telling apart. spt subnethints tidied. The “hint:” lines now appear only on the barespt subnetoverview, not inspt subnet status(so the status view is clean to read).- Pairing works on machines with a wrong clock. Pairing now checks network time and tolerates a node whose system clock is off by more than a minute (which previously made pairing fail silently). If network time can’t be reached, it falls back to the local clock as before; it never changes your system clock.
- Faster first sync after joining or restarting.
sptnow remembers peers’ last known addresses, so after a join — or after the daemon restarts — other nodes reappear inspt subnet status --nodesin seconds instead of taking up to a minute. - Endpoints going online/offline show up almost immediately. When an endpoint starts or stops, peers now see the change in
spt subnet status --nodeswithin seconds instead of waiting for the next sync cycle.
Fixed
spt subnet status --nodesno longer hangs on a dead peer. Checking a node that has gone away used to stall the command for ~30 seconds; it’s now bounded to a couple of seconds, and the command prints “Checking remote nodes…” so the brief wait is expected.- Detached/unreachable peers now read as offline. A peer you’ve detached from a subnet (or that has stopped serving it) is correctly shown offline in
spt subnet status --nodes, instead of appearing online indefinitely just because its machine is up. - Messages from other agents now arrive properly formatted. Incoming messages on the listener stream now include the full envelope with the sender’s name, instead of showing as a raw, unwrapped line.
sudo spt …now works on Linux user installs. Whensptis installed to your user directory, elevation guidance that said to “run as administrator/root” used to dead-end withsudo: spt: command not found. The installer now also makessptreachable undersudo, and on an interactive terminalsptre-runs itself withsudoautomatically; otherwise it prints a command that actually works.- Elevated
spton Linux runs under your account, not root. The first time you run an elevatedspt, it asks once which account should own the daemon and its data, remembers that choice, and every latersudo sptruns the daemon and stores state under that account — never as root. spt daemon stopon Windows now finds the daemon it started. A daemon launched through Windows’ elevation prompt could end up using the wrong home directory, sospt daemon stopreported “daemon not running” while a daemon kept running. It now keeps the right home directory across that elevation step.- Removed a confusing internal status line.
sptno longer prints the internal “DEELEVATED: running as uid …” notice during normal use. - Stale node rows clear out on their own. Nodes that haven’t been heard from in a while are now removed from the listing automatically, so old/dead entries stop cluttering
spt subnet status.
[0.1.1] - 2026-06-07
Maintenance fixes following the first public release. (This changelog was introduced in 0.2.0; 0.1.1 and earlier are summarized here for completeness.)
[0.1.0] - 2026-06-06
First public release of spt.