Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.70.0] - 2026-09-13

Added

  • Quoting an absolute or home-relative filepath while controlling an agent through remote spt rc can 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 cmd operation now lists the available command operations and points to the adapter manifest for other input methods.

  • Ctrl+B, then d, now detaches a read-only spt rc --view session 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 --bootstrap on 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 relink now reports binding while it waits for its handshake, instead of a successful launch reading as offline.

  • 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, and spt serve list --json manage 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_path optionally gives it a short URL alias. Removing an adapter stops serving its output without deleting the files.
  • spt adapter add names 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.
  • WEB joins 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; HEAD and Range requests are honored by the owner. An owner that refuses answers 403 naming WEB; 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 with spt fetch when it wants them. --ttl sets 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, and spt send --reply-to <id> marks what a message answers. Messages from another machine resolve through the machine that holds them.
  • spt serve lan --bootstrap hands 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, and spt install gains --expect-sha256 and --release-json so what was downloaded can be compared before it is run. The documentation port is untouched and stays on loopback.
  • [adapter].docs_dir publishes an adapter’s own documentation at the docs segment 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 ?json views. / redirects to /<node>/; canonical docs URLs live at /<node>/docs/, while existing bare docs paths remain compatibility aliases.

Removed

  • The XFER access 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 names XFER is 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 listen now 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 bind no 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: the command, start and stop_grace_ms keys; 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); the stop-requested, status-advisory and startup.capture files in its runtime directory; how a cooperative stop, an update hold and a startup fault behave; and what spt adapter service list and spt adapter service status report. 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 new spt 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 as pubkey <prefix> so it cannot be mistaken for an endpoint name.

Internal

  • The digest’s echo record kind is renamed from echo_mirror to echo_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 busy accepts --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. --mid at idle, and --mid with 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. --limit caps one answer and says that it capped, deferring the rest to the next call rather than dropping it. The feed is authenticated like spt 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 cmd woke 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 the RESUME_NO_HARNESS_SESSION notice, 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-output so the categories that read it can fire.
  • The picture can be narrowed and capped: --spec-manifest reads 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|idle can carry the turn’s text: --payload-stdin or --payload-file <path>. Passing both is refused by name with STATE_PAYLOAD_AMBIGUOUS. Sending no payload behaves exactly as before, so an already-installed adapter keeps working untouched.
  • Adapter manifests gain an [io] section. compliance = true hands the parsing of an agent’s own turn text to spt; shortform = false keeps that parsing off while staying compliant. Both are off by default, so an adapter that declares neither sees no change.
  • With [io] compliance declared, 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 hint is now a thin alias for the hint part of now-signal and 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-signal instead. 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-authenticator can now actually enroll on Windows. It previously refused on every machine with SEAL_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_kind for Windows Hello is now hello-kcm-rs256, and the previous hello-rs256 token 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 describe reads 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 reads 2026-08-25 14:07 CEST in 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 verify no longer fails over a trailing newline. seal mint seals the trimmed text, so piping the same text back in with echo used 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_hash and ceremony_kind. The verdict line already carries the token, and a mismatch already prints both hashes; those fields belong to describe, 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 update now 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 under spt 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 on 3 can 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 ring used to judge such a rule against whatever --from label 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 list now shows where an endpoint’s commune must be written — as a commune drop dir: line under your own pin, and as a drop_dir field 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-authenticator enrolls 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 verify now 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 describe shows the ceremony used and whether a signature is present.
  • E on 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 offered E.

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 allow and spt 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 mint seals 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 verify answers BOUND or NOT-BOUND against the actual content on any node of the seal’s binding subnet, and spt api seal describe shows 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> --seal runs 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 --subnet wins, 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 monic commands 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 allow form 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 under running_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-room answered 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 status now 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 list and spt endpoint access rules forms 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 named list or rules is 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 --help now 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 create now 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 allow help 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 --subnet now takes more than one subnet — a comma list, or the flag repeated.

Changed

  • An invite code minted without --subnet is 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 allow with 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 rc no 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 closed posture no longer refuses discovery. Setting a posture to closed — 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 blanket closed to 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 reads DISCOVER 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 reads closed (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 list no 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 explaining unknown are 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 gc reports 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 --force restarts a shell instance that is already running. Plain relink brings a stopped instance back and refuses one that is already up. --force covers 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 node is now the primary spelling for managing a machine, and spt daemon is 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 — so node names 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 the node spelling.

Fixed

  • spt subnet status counted reachable peers from one population and total peers from another. The fraction could read impossibly — 7/1 was 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 names spt node stop followed by spt 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_INTENT no longer appears; ATTACH_INTENT_CHOSEN reports the same decision at the three paths that actually run, distinguished by a site= field. Two consequences for anyone reading logs: a RESUME_ATTACH_INTENT line 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 run is 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 (--off clears it).
    • spt go <id> — brings it up and attaches.

    Bringing a brand-new endpoint up is therefore two commandscreate, then start or go — 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 run answers with the verb to use instead, and the flags that retired with it — --save, and --id and --subnet on 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 send and spt knock redeem now require exactly one of --send-only or --send-receive. There is no default: a bare invocation refuses and names both. The flag describes the asking side — --send-receive pre-authorizes the reverse on that endpoint, --send-only deliberately declines it.
    • spt knock approve and spt knock new-code no 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.
    • --mutual and --one-way are parse errors wherever they used to be accepted. The refusal names the flag that replaced it and, at approve and new-code, the verb to use instead. There are no deprecation aliases.
    • spt knock list --json renames its mutual key to send_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:knock guidance still teaches the retired shapes (bare knock, --mutual on knock and approve, bare redeem, 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-room brings 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 next spt rc engine-room asked 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 rc does refuse an engine-room attach, it now names spt rc engine-room — which asks for a member or admin code — rather than a bring-up verb the engine room deliberately refuses.
  • spt knock no 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 add treated the id as free and overwrote it, while spt endpoint monic update refused 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 add takes the id of the monic to write, not a peer endpoint id, and refuses when that id is already taken — replacing one is update, so a typo or a re-run no longer silently overwrites the monic already stored there.
  • spt endpoint monic update refuses when nothing is stored under that id; writing the first one is add. A monic is stated whole.
  • add and update both accept --batch to write or replace several at once from a single stdin payload.
  • spt endpoint monic clone copies by monic id, or --all for 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 --help describes what monic and trust-warning actually 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 list renders a Your 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 moves Total:.
  • 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_INGESTED warning naming the commune it lost, instead of resuming stale silently. Harness roles that are LLM turns can declare invocation_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 --timeout is now denominated in MINUTES (an agent answers on agent time), and the default is 30. A bare number is minutes; an explicit s or m suffix sets the unit, so 90s and 2m both work. Scripts that passed bare seconds now wait longer — pass s explicitly for sub-minute waits.
  • The line under this node’s name in spt endpoint list says Joined subnets: — these are the subnets this node is a member of. Remote nodes keep Shared 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 --closed now 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. Every subnet status view 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 that spt 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 list when 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 of spt knock send.
  • Answering a knock has to state the direction. spt knock approve and spt knock new-code require exactly one of --mutual or --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@machine makes 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 --json reports 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 stop and spt update apply --finish now 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 ring waits 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 list prints an approval command that the CLI accepts. Surface names are now matched without regard to case, so a request naming msg is 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 knock asks 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-code mints one that grants the named surfaces when redeemed, and spt knock redeem presents one that was handed over.
  • spt endpoint monic records 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-warning shows 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, deny and remove now operate for the whole node, with --for to scope one to a single endpoint.

Changed

  • An endpoint’s access rules accept a subject rather than only a node, and gain deny beside allow. A rule can be removed by restating it with the flags that created it (remove), alongside the existing revoke.
  • 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-room now opens an interactive code prompt (Esc cancels); --code remains 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 access answers 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 list and spt endpoint access rules are retired; the roster views above replace them (allow/revoke/open are unchanged).
  • spt subnet revoke requires 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 create shows 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 access rules 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 rc says 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 create mints two keys — a member key and an admin key — and asks whether the subnet starts open or closed (--open / --closed skip 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-refresh re-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 rules prints 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 --from supplied on the command line cannot satisfy such a rule; it remains what it always was, reply-routing information.

Fixed

  • spt ring no 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 ring held 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 status now 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 local spt shell relink, or by a spt shell cmd that 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 suspend reported nothing to do on a visibly online endpoint until an explicit spt endpoint wake repaired the record.
  • spt send --active-only no 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 list and spt 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 stop and spt endpoint shutdown on 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 list now reports such instances offline.
  • Fixed spt shell relink refusing 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 cmd silently 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 activity frame 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 single spt api drive-poll can 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 --json and spt api endpoint-info now report whether each endpoint is busy or idle, 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 --last and --after behaving 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. --follow still works only on the local machine — poll with --after instead. 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 stop now 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 with spt 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 update and spt daemon refresh no 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 where spt shell send --file had 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 stop and spt endpoint shutdown now 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 --force now 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 --json output 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 run refused to start it with ENDPOINT_ALREADY_LIVE and 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 rc owns — 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 rc now 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 rc checks the live session first, so it no longer reports “offline — nothing to attach to” for a session that spt endpoint run --resume can attach to. An endpoint that is still starting up now reports as starting rather than offline.

  • spt rc on 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 run no longer silently reattaches to a running session. When a live session already exists for the endpoint, it now stops with ENDPOINT_CREATE_CONFLICT and a non-zero exit instead of quietly joining the existing one. Attach to the running session with spt rc <id>, or pass --resume to 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, and shutdown now 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 status and spt 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, or spt 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 whoami reports only your own identity. It now prints the single endpoint bound to the current session, not the full roster of endpoints on the node — use spt endpoint list for the roster. Its --json output 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 prints NO_PERCH on 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, and spt api endpoint-info read 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 status now 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 --json output carries the same detail in a project_index block. The index file itself lives at $SPT_HOME/index/project-index.json and 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, and spt-x86_64-linux-musl — with a per-platform download example and the chmod +x step 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 refresh live in the always-on daemon. spt update --restart brings them up via a full daemon restart (your live sessions restart as the daemon comes back). A bare spt update swaps 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-path skips 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 docs opens the docs in your browser.
    • spt docs url prints the resolved URL (honoring any port override). The port can be changed with docs_port in daemon.json or the SPT_DOCS_PORT environment 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 update downloads 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 for spt 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 full daemon stop/start, which killed every hosted session.

Changed

  • Bare spt update now 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-only to 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. gh is now a prerequisite for spt update fetch and for the spt install bootstrap. If gh is missing or not signed in, the update stops with a clear message — how to install gh for your operating system (winget / brew / your package manager) and to run gh auth login. gh supplies 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 --apply lands 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 run without --save leaves 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 send from inside an agent’s shell went out as anonymous cli@<machine> instead of the agent itself, so replies to it bounced with NO_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-id offers 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 --apply lands 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 --take that 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 fresh spt rc resumes 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 --apply could still freeze all of your live terminal sessions a few seconds after the swap — they stopped streaming and spt rc could 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 and spt rc keeps 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 --apply could 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 and spt rc stopped responding across the swap. Resumed sessions now re-attach as observers instead of taking over the session’s controller, so your open sessions and spt rc keep 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 --apply could 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 --json could 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 status listed 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 host spt update fetch now 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 --finish completes 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 apply now also works while the daemon is stopped — it swaps in place, and the next spt daemon start runs 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 stop now 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 rc no 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 rc keeps updating and a stuck connection is dropped automatically so you never lose control; reattaching and taking control (--take) keep working. spt daemon status reports 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 --json no 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 --verbose now 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, spt prints NTP_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 join now 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 --code path 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 --verbose failure 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 list hides worker endpoints by default. Worker endpoints no longer clutter the default listing; pass --workers to 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> and spt 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.
  • x purges an endpoint from the pick list. Highlight a row and press x to 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-spt requirement; one that needs a newer spt is 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 stop no 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 (and spt whoami) now flag an endpoint whose input translation has failed. The endpoint’s line shows input-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 node and points you at spt endpoint list to 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 --subnet on multi-subnet nodes. Referring to a parent/nested agent 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, a CURRENT 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 Picker when 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 h and s keys 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-native send 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 list project 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 list output 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 status now 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 status flags it with guidance to re-register via the current installer; the daemon self-protects either way.
  • Piping spt daemon run to 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 list and --help no 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 run over 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 shutdown now 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 update fail 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 list now 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 send from inside an agent’s hosted session no longer mis-stamps the sender. The sender could be stamped as cli@<node>, causing replies to bounce; sender identity now falls back to process ancestry when the session’s environment variables are absent.
  • Piping spt output to a command that closes early no longer errors. Sending output to something like | head that 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-all reveals them. Each machine’s total discloses how many were hidden (nothing silently vanishes), and a corrupt record always shows regardless.
  • spt daemon status now 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 --json output gains matching broker_image / broker_stale fields.

Changed

  • spt endpoint list (and spt 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 old ENDPOINTS: summary line (which counted a machine once per subnet) is removed.
  • spt update apply now 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 at spt daemon status to confirm which version is live.
  • spt endpoint list --json gains a per-endpoint endpoint_type field. Additive — the existing JSON shape is otherwise unchanged.

Fixed

  • spt update apply when 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 send with no explicit sender now stamps cli@<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 update on 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, rc wrongly reported that the endpoint exists in several subnets and asked you to disambiguate — but a subnet is not a way to tell nodes apart, so there was nothing to pick. rc now 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 list and in the spt endpoint run picker’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 rc can now attach to an endpoint running on another node. Previously spt rc <endpoint> only resolved endpoints with a live session on the local machine — a cross-node Active endpoint (visible in spt endpoint list) failed with no live session for endpoint. rc now 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 list now labels the local machine by name. The LOCAL (this node) header is now This node: <node-id>, so a listing captured from one machine is unambiguous about which node produced it.
  • spt endpoint list uses the same status codes and colored markers as the spt endpoint run picker. 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 run groups 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. Dormant is 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 rc to 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 --attach rendered fine in the same terminal); the clean-repaint-on-attach fix resolves it. VT output is also enabled defensively on legacy Windows consoles.
  • spt rc to a live local endpoint no longer times out with brain IPC read deadline elapsed after 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 send across nodes no longer reports SENT(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 --nodes count counted non-routable ghost rows in the denominator and never evicted purged endpoints, producing wrong ratios like 0/2 or 1/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 add no 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 update no 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 with os error 2. Update now targets the registered source directory and tolerates a changed update repo.
  • spt endpoint digest now works for endpoints whose transcript lives under a relocated profile. A profile that relocates the harness transcript tree (via a runtime env like CLAUDE_CONFIG_DIR) produced NO_DIGEST because 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 brittle spt update fetch && spt update apply chain (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 fetch no longer reports an already-downloaded update as an error. When the latest version was already fetched and only needed installing, spt update fetch printed 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. Run spt update apply to 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 join now 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, --verbose prints 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, mirroring SPT_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, spt used 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 join asks 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 --code form 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 that spt adapter update runs 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 --json for 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, and how-to now accept a global --json flag emitting stable, explicit fields for scripted consumption. (Action commands ignore it.)
  • spt endpoint digest incremental cursor. --json output gains --last <N> (the last N turns; --last 1 is the latest turn), a stable per-entry seq that 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), a partial flag on the in-progress turn, and a per-entry ts — so a consumer can process turn-ends incrementally instead of re-reading the whole window.
  • spt rc identity marker. An attached controller now shows a persistent top status row — right-aligned SUBNET : ENDPOINT_ID @ NODE in 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 at get-string read time. This lets an adapter resolve a path to its own packed binary without spt-core ever executing it.
  • [message-idle-translation-binary] takes a command. The idle-delivery translation binary can be declared with a command (program token plus args, with adapter-static substitution) instead of the bare path, 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 bare spt) 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].path is deprecated in favor of command. It still parses (and warns at registration); exactly one of path/command may be set.

Removed

  • spt send --reply-to is removed. The send target is now a required positional argument; reply correlation rides the structural from on 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 send delivery-window controls. --idle-only delivers a message only while the target is idle (the idle/wake window), holding until then; --active-only delivers only through the target’s own poll, without ever waking an idle target. (--active-only replaces the old --deferred, which still works as a hidden back-compat alias.)
  • spt send channel controls. --prefer-native delivers through the target’s translation binary when one is running and falls back to the normal channel otherwise; --force-native delivers only through the translation binary, with no fallback or spooling (reported undelivered if none is running).
  • spt send --ephemeral drops 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 run picker 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 --subnet path from 0.14.0 is unchanged.)

Changed

  • spt adapter add is non-destructive. Re-adding an already-registered adapter is now refused, with guidance to use spt adapter update or spt adapter remove instead. 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 add reports 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 run homes 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 run now 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> (and spt 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 hollow UNBOUND row in the endpoint picker, spt endpoint list, and spt 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 .spt adapter 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 --release can fetch from a private repo through the GitHub CLI, with no access token to manage. New --gh / --https flags 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-proof and spt adapter translate-proof can test an unpackaged adapter via new --dir / --manifest options — proof a development or bare-file adapter before it’s installed.

Fixed

  • spt --help and 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 of spt 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 rc forwards 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 --view viewers 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 rc no longer races a just-started agent. Attach now waits for the endpoint to come online, instead of failing when you attach immediately after spt 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 run picker 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 --help fix.

[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 with spt endpoint run now 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 run launches the background daemon for you, closing that terminal tab or window now leaves the agent running and re-attachable with spt rc <id>.
  • A crashed agent with a disconnected viewer no longer freezes the daemon. A dead agent process combined with an abruptly-closed spt rc could previously wedge the daemon so new agents wouldn’t start; the daemon now stays responsive and marks the dead agent offline.

Changed

  • spt endpoint list and spt whoami always include this machine’s local agents — your own just-started agent always shows up. The --local flag has been removed: local agents are now always merged into the listing.
  • The spt endpoint run picker offers the right action. An already-running agent now offers Attach instead of a meaningless “Start now”.
  • spt --help renders 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 ready now appear in the spt endpoint run resume-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 rc now fails fast with a message instead of showing an endless blank screen, and stopping an endpoint marks it offline.
  • spt daemon stop now 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 send now delivers to an agent whose terminal is hosted by the daemon. When the target has no spt api listen relay (the daemon holds its terminal directly), spt send previously 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 run now fills in [env] values. Placeholders such as {id} in an adapter’s [env] entries (for example SPT_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 list columns 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 next spt api command).

[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_binaries manifest 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 seed records just the session (pid + id); spt api listen resolves the owning adapter automatically from the session’s process — restoring the one-step legacy bringup. --adapter remains 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 node ccs launcher) 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 through PATHEXT (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 whoami as a psyche-host: FAILED (<reason>; <n> attempt(s); <ts>) line. Previously this failure was silent — visible only on the daemon’s internal stderr — leaving an agent reporting online with no Psyche and no visible cause. Liveness (status) is unaffected and remains authoritative; the new psyche_host_error field 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-to guides: subnet and live. Pairing machines (create vs join, the 6-digit code, reaching remote agents) and running as a live agent (the persistent spt api listen relay, 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 update pulls the newer .spt. Declare a signing_key and verification is fail-closed — an unsigned or wrong-signature artifact is refused, not installed.
  • spt api resolves an adapter’s manifest and install directory from --adapter. Pass --adapter <name[:profile]> without --manifest and spt looks both up from the registry; --manifest becomes 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 on PATH.
  • 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 .spt archive, extracts it, and registers the adapter. This lets you ship an adapter that lives inside a larger repository, where cloning the whole tree with --github doesn’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-proof now 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 --session to 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 poll and spt api worker-poll now arrive in the same <EVENT type="msg" from="…">…</EVENT> envelope as the live spt api listen stream — 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 apply now confirms the update applied, in plain language. A successful apply prints Updated 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 canonical github.com address.)

[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 run brings 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 (see spt 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 then d (the session keeps running); ctrl-b ctrl-b sends 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> --view to 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, --take kicks them (they get a loud “you were taken over by …” notice and are detached) and you become the controller. A plain spt rc <id> on a session someone else controls now refuses with guidance (it tells you to --view or --take) instead of silently stealing control.
  • spt endpoint run is 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. Bare spt (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 with controlled by <node> (+N viewing). Press s to bake the current selection into a project-root spt-<id> launcher shortcut (an adapter can brand it, e.g. cc-<id>, via the new [adapter] shortcut_basename manifest field).
  • spt subnet join shows a QR code + setup code on success. After joining, scan the QR (or read the otpauth:// 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/terminal sudo, or inline sudo in a terminal — and prints the exact command to run by hand when it can’t.
  • spt spt — ???

Changed

  • spt whoami now shows the full picture. It is an alias for spt 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 /clear or /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. New spt 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 via spt api digest-entry) instead; one [history] normalizer can no longer serve both the opaque echo-commune and the contract-typed digest.

[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 addressing adapter:name. A profile you create locally survives updating or re-adding the underlying adapter, and spt adapter list shows each profile as its own spawnable option. spt adapter delete-profile removes 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> and spt 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 role is the only thing that writes it — nothing automated ever overwrites your wording.
  • spt api digest-entry lets 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. --follow still 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 with spt api digest-entry) and the digest builds itself. No digest-specific manifest section is needed.
  • 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 apply now 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 fetch can 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 — leaving spt unable to start. spt update fetch now 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 apply refuses 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-run spt update fetch on 0.3.2 to replace it with the platform-safe set.

Changed

  • spt update apply prints a friendly confirmation — for example Updated 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, then spt update apply to 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.Z to fetch a specific version.

Fixed

  • Messages sent from Windows no longer arrive garbled. A message piped into spt send or spt ring from 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, and send/ring trim their input like notify already 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 daemon would 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 daemon reports “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-seed to 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 to spt subnet prune, which only cleans a dead node off the local machine.
  • spt daemon start — bring the daemon up in the background, idempotently. When spt is installed as a service (the Linux per-user service, or the Windows logon task), start and stop now drive that service instead of a stray hand-started daemon — so the two never fight each other for the connection. spt daemon start on 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.
  • spt daemon stop is service-aware. If a managed service owns the daemon, stop asks 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 run is 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, use spt daemon start. On Windows, running daemon run from an elevated shell now refuses with a hint rather than silently disappearing into the background.
  • spt daemon status shows what manages the daemon — whether a service owns it (and is active) or it was started by hand.

Performance

  • spt subnet status --nodes is 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 explicitly spt subnet prune that node.

[0.2.0] - 2026-06-08

Added

  • spt endpoint command group. A single home for everything you do to an endpoint: spt endpoint fork, suspend, wake, shutdown, rename, stop, and digest all 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 --local shows just this machine’s endpoints.
    • spt endpoint list --subnet <name> filters to one subnet.
    • spt endpoint list --detail adds each endpoint’s description blurb.
  • spt endpoint description [set] — read or write an endpoint’s description blurb (bare command shows it, set writes it).
  • spt endpoint access — per-endpoint access control (allow / revoke / open / list), scoped to the individual endpoint.
  • spt daemon command group:
    • spt daemon status (or bare spt 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 --save to either to make that choice the default the next time the daemon starts.
    • spt subnet status now 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 spt needs so other nodes can reach you. If it wasn’t added, spt subnet status and 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 spt to 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, and spt how-to all stay where they are, as does top-level spt notif.
    • These endpoint commands moved under spt endpoint: fork, suspend, wake, shutdown, rename, stop, digest. For example, spt fork … is now spt endpoint fork ….
    • The old resources view is gone; its listing is now spt endpoint list --detail and its per-endpoint blurb is now spt endpoint description.
    • spt notify … moved to spt 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).
  • spt subnet status tells 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 of KITSUBITO (43a51d9a…); the extra key prefix only appears when two nodes share the same hostname and need telling apart.
  • spt subnet hints tidied. The “hint:” lines now appear only on the bare spt subnet overview, not in spt 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. spt now remembers peers’ last known addresses, so after a join — or after the daemon restarts — other nodes reappear in spt subnet status --nodes in 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 --nodes within seconds instead of waiting for the next sync cycle.

Fixed

  • spt subnet status --nodes no 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. When spt is installed to your user directory, elevation guidance that said to “run as administrator/root” used to dead-end with sudo: spt: command not found. The installer now also makes spt reachable under sudo, and on an interactive terminal spt re-runs itself with sudo automatically; otherwise it prints a command that actually works.
  • Elevated spt on Linux runs under your account, not root. The first time you run an elevated spt, it asks once which account should own the daemon and its data, remembers that choice, and every later sudo spt runs the daemon and stores state under that account — never as root.
  • spt daemon stop on Windows now finds the daemon it started. A daemon launched through Windows’ elevation prompt could end up using the wrong home directory, so spt daemon stop reported “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. spt no 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.