# Changelog

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](https://keepachangelog.com) 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.

## [Unreleased]

### Added

<!-- [doc->REQ-WEB-SERVING-REGISTRY] -->
- `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.
<!-- [doc->REQ-WEB-ACCESS-SURFACE] -->
- `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.
<!-- [doc->REQ-WEB-CROSS-NODE-PROXY] -->
- 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.

### Changed

<!-- [doc->REQ-WEB-URL-NODE-PREFIX] -->
- 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.

### Fixed

<!-- [doc->REQ-IO-MSG-EDGES] -->
- 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.

## [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. (releases#222)

### 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. (releases#222)
- 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 commands** — `create`,
  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 --> <current dir>" states the directory the fork will run in, and "Set shortcut here --> <current dir>/<file>" 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`.
