1	# spt-core — Product Requirements Document
2
3	> **Status:** draft, 2026-05-30. Synthesized from a grilling session.
4	> Canonical detail lives in [`CONTEXT.md`](./CONTEXT.md) (glossary/model) and [`docs/adr/`](./docs/adr/) (decisions). This PRD states *what* and *why*; those state *meaning* and *rationale*. Where they conflict, the ADRs win.
5
6	---
7
8	## 1. Vision
9
10	spt-core is a **harness-independent core for an agent ecosystem**: inter-agent messaging, live-agent lifecycle, terminal hosting, seamless self-update, and zero-config cross-machine networking — shipped as both a Rust library workspace and a single canonical binary (`spt` / `spt.exe`).
11
12	It is the ground-up rebuild of `claude_skill_owl` ("modern SPT"), untethering the system from Claude Code so that **any** agent runtime — Claude Code, Codex, Cursor, headless, or novel surfaces — can participate by either shelling out to the binary or linking the crates. The first thing built atop it is a rebuilt `spt` plugin reaching parity with today's system; the networking, terminal-wrapper, and multi-instance capabilities then make an agent reachable and operable across all of a user's machines.
13
14	## 2. Problem & motivation
15
16	- Today's modern SPT works but is **fragile** — each release introduces 2–5 regressions because it grew rapidly from a narrow starting point and carries the resulting tech debt (ADR-0001).
17	- It is **welded to Claude Code**. Other harnesses can't reuse the messaging/lifecycle substrate.
18	- Networking, terminal hosting ("capsule"), and cross-machine operation were planned piecemeal atop a CC-tied core; they deserve to be first-class in a clean foundation.
19
20	## 3. Goals
21
22	1. **Harness independence.** Zero knowledge of any specific agent runtime in core; all harness specifics behind a declarative contract (ADR — runtime model).
23	2. **Dual deliverable.** First-class library crates *and* a canonical binary, wire-compatible (§Architecture).
24	3. **Zero-config cross-machine messaging.** "Just works" WAN, baked in day-one (ADR-0002).
25	4. **One agent, many machines.** The same agent runs natively on each of a user's machines with a synced mind; reachable and operable from anywhere (ADR-0003).
26	5. **Seamless self-update** with a hard no-endpoint-termination invariant (ADR-0004).
27	6. **A terminal backbone** that can host sessions headless and headed, locally and over WAN.
28	7. **Low-friction, secure onboarding** — accountless node pairing (ADR-0005), one-line install, automatic legacy migration.
29
30	## 4. Non-goals (v1)
31
32	- **macOS support** — out (no test machine); kept structurally easy, not a re-architecture.
33	- **Cross-subnet** (two *different* users' agents communicating) — deferred concept.
34	- **Concrete Shells / PresenceChannel implementation / presence gossip** — only the seams ship (§Endpoints).
35	- **Remote command execution on another user's node** and **instantiate-anywhere** — seam-compatible, deferred behind a consent/security gate.
36	- **Sidecar adapter protocol**, **manifest includes**, **scrollback disk-spillover**, **predictive/state-sync terminal layer** — see [`docs/DEFERRED.md`](./docs/DEFERRED.md).
37	- **A model/billing opinion** — SPT is not a harness; model choice is always the adapter's.
38
39	## 5. Audience
40
41	Single user today (the author); dev-tool audience generally. Agents — not the user — are the primary direct callers of the binary. Nodes are foreseen on novel platforms (Android, Linux handhelds), so packaging must be marketplace-repackaging-friendly.
42
43	## 6. Platforms
44
45	Windows + Linux for v1. macOS deferred. Pre-Windows-10 (winpty) dropped — ConPTY (Win10+) / `forkpty(3)` Unix via `portable-pty`.
46
47	---
48
49	## 7. Architecture (requirements)
50
51	### 7.1 Workspace & binary
52	- **R-ARCH-1** Ship a workspace of **many small, acyclically-layered crates** (ADR-0001): `spt-proto` → `spt-store` → `spt-msg` → {`spt-net`, `spt-term`, `spt-runtime`} → `spt-live` → `spt-daemon` → `spt` (binary).
53	- **R-ARCH-2** Public semver-committed SDK = `spt-proto`, `spt-runtime`, `spt-msg`. All else internal/unstable pre-1.0.
54	- **R-ARCH-3** `spt-proto` carries an explicit **wire-protocol version** independent of crate semver, with a documented **N-1 compat window**. Binary and library consumers interoperate by proto version, never by release version.
55	- **R-ARCH-4** Copy-verbatim the commodity layer from the sister project (envelope grammar, spool DDL, registry RPC, `info.json` shape); clean-room only the architecture/lifecycle (ADR-0001).
56
57	### 7.2 The daemon (broker/brain)
58	- **R-DAEMON-1** Exactly **one logical `spt-daemon` per machine**, always-on, owning PTYs, network identity + WAN endpoint, subnet registry, all spools, all poll-listener logic, and all Psyche/pulse loops. No separate listener or wrapper processes (ADR-0004).
59	- **R-DAEMON-2** Internally split into a stable **broker** (holds PTY masters, harness child processes, listening sockets; versioned IPC; rarely updates) and an **updatable brain** (all logic; rehydrates from disk + re-attaches to broker handles).
60	- **R-DAEMON-3** Any `spt api` invocation that needs the daemon **auto-starts it** if absent.
61	- **R-DAEMON-4** Honor every invariant in [`docs/KNOWN-HAZARDS.md`](./docs/KNOWN-HAZARDS.md) as a conformance checklist.
62
63	---
64
65	## 8. Harness contract (requirements)
66
67	The umbrella surface an adapter binds to. Two halves; `SPT is not a harness` — model/billing/harness-env/harness-context are always the adapter's, expressed in its own command templates.
68
69	### 8.1 Outbound — the runtime manifest (seams)
70	- **R-MANIFEST-1** A declarative manifest (TOML/YAML) per adapter, carrying a unified **`adapter_name`**, the adapter's version, and **`min_spt_core_version`** (readable *before* an update is applied — compatibility gate).
71	- **R-SEAM-spawn** `spawn-session`: command template + `cwd` + `headless?`/`resume?` flags + commune/signoff dirs. spt-core may inject `{id}` and an optional spt-core-generated session UUID. No harness env injection; no initial-context handoff. Optional `id` → reproduces no-id `/spt:live` resolution.
72	- **R-SEAM-postspawn** `post-spawn`/`api bind`: binary reports `session_id` (if self-generated), `parent_pid`, identity/type, optional HTTP port, **boot nonce**; flips skeleton→live. Skippable *only* under the strict UUID-injection + stable-pid commitment.
73	- **R-SEAM-psyche** `spawn-psyche`: two templates (fresh / resume-with-`$session_id`), both carrying spt-core-owned `$psyche_prompt`; adapter owns model + harness-specific preamble.
74	- **R-SEAM-history** Two history paths: **A** adapter-owned locate-template + adapter-owned **normalize-command**; **B** spt-core-native `history-log` store (also used for Shells). Adapter-owned normalize is mandated over built-in parsers (Codex-vs-Claude evidence — transcript formats diverge and move fast).
75	- **R-SEAM-activity** Activity/idle is reported by the adapter via `api` commands into **perch sentinels** — never PTY-quiescence, never a manifest-declared idle signal.
76	- **R-SEAM-inject** `inject-input`: configurable per activity-state (activity/idle/both), any combination of PTY injection, `api poll`, in-session relay, or HTTP POST to a local port.
77	- **R-SEAM-resume** Two resume forms: **fresh-with-preload** (cleared context + `$psyche-context` preload) and **continue-existing** (native harness resume).
78	- **R-SEAM-capability** Static manifest list of hostable endpoint types (consumed by the registry; a node may host only Shells).
79	- **R-SEAM-update** `adapter-update`: file-pull (regex + gh repo) or delegated-command. spt-core conducts adapter updates after bootstrap.
80
81	### 8.2 Inbound — the `api` subcommand surface
82	- **R-API-1** All machinery-facing commands are `api`-prefixed; every invocation carries `adapter_name`. The agent-facing verbs (`send`, `ring`, `ready`, …) stay unprefixed.
83	- **R-API-2** Surface: `api bind`, `api listen` (long-running relay), `api poll [--include-deferred]`, `api state <busy|idle>` (+ `api echo-gate <set|clear>`, `--no-gate`), `api worker-start|worker-stop|worker-poll`, `api boundary <clear|compact>` (carries new `session_id`), `api session-end [--erase]`, `api history-log`, `api presence`, `api seed --pid <pid>`.
84	- **R-API-3** `commune`/`signoff` are **file-drops**, not commands; the daemon watches the manifest-declared dirs and is the single writer.
85
86	### 8.3 Startup flows
87	- **R-START-1** Adapters never resolve `$SPT_HOME`; install registers the binary dir on system PATH; all bridging is via `spt api`.
88	- **R-START-2** **Harness-hosted:** SessionStart → `spt api seed --pid` (in-memory seed record) → `$SPT listen <id>` consumes the seed by `parent_pid` (validated vs `session_id`), binds, relays. No file.
89	- **R-START-3** **spt-hosted:** daemon runs spawn-session into a broker PTY → binary `api bind` (or skips under commitment) → daemon delivers via manifest-configured method. Principle: **file-bridge only when the daemon is not the launcher.**
90	- **R-START-4** Adapters inject env aliases (`$SPT` = `spt api`, heirs to `$OWL`/`$LIVE`); spt-core supplies the subcommands.
91
92	---
93
94	## 9. Endpoints, Shells & the Instance model
95
96	### 9.1 Endpoint types
97	- **R-EP-1** Day-one types: **ReadyAgent, LiveAgent, Psyche, Worker, SptNode** (agent/infra), plus the **Shell** family and **PresenceChannel** (seams only — impl deferred). The type system is **open**.
98	- **R-EP-2** Endpoints split into **agent endpoints** (host an agent) and **Shells** (driven surfaces — nothing intelligent runs there; a remote agent drives them). Shell-vs-agent distinction is present in the type model day-one.
99	- **R-EP-3** Messaging payloads carry **typed operation commands + arbitrary file blobs** (text/audio/image/video), not just text — the seam that lets Shells, file transfer, and rich PresenceChannel exist later.
100	- **R-EP-4** PresenceChannel (deferred impl) is a **broker** endpoint with dispatch/bind/thread styles; presence datum `(last_active_node, last_active_endpoint, ts)`.
101	- **R-EP-5** (deferred impl, CONTEXT "Shell model") **Concrete shell instantiation model:** `shell spawn` *mints* an owner-exclusive instance (`<adapter>-<n>`, distinct from the `relink`/`persistent`/`wake` online switch); permission to instantiate = shell adapter **registered on the node** (broadcast governs *discovery* only); a per-shell **`require_approval`** gate (`none`|`remembered`|`always`, default none; manifest floor, node/endpoint may tighten) reuses the consent grant store; an optional **`max_instances_per_owner`** cap (online+offline both count) with **`over_cap`** (`reject`|`approve`); instances are **aliasable** (immutable canonical id + optional perch-level alias; `adapter_name` always legible). Discovery = own instances + instantiable adapters (same-node non-`none` broadcast; other-node `subnet` broadcast, spawn gated by instantiate-anywhere).
102
103	### 9.2 Instances (ADR-0003, as amended)
104	- **R-INST-1** Split identity: subnet-wide **endpoint ID** vs per-node **instance**. An instance is the *same* endpoint running **natively** on a machine.
105	- **R-INST-2** Per-node (anchored): files/project + harness session history. **Synced across instances:** the Psyche mind (two-tier — below).
106	- **R-INST-3** **Dormant (warm)** is the default resting state (session running, instant wake); **suspended (cold)** is opt-in (session closed, resume-on-wake) and can be triggered from any node. Registry status: active / dormant / suspended / offline.
107	- **R-INST-4** Active → (dormant|suspended) fires a **transition echo commune** that syncs to the next active instance (catch-up-on-activation).
108	- **R-INST-5** **Two-tier context sync:** live context → all instances; project context → same-project instances only. (Depends on refining Psyche authoring directives.)
109	- **R-INST-6** Deferred messages do **not** deliver to dormant/suspended instances until active.
110	- **R-INST-7** **Subnet registry** (eventually-consistent `endpoint_id → [instances]`) + **resolution policy** (local → most-recently-active → `id@node`) are mandatory v1 foundations.
111	- **R-INST-8** **Remote-control** (Shell-like attach to an instance on another node; byte-stream terminal over Iroh) is a distinct mode from local operation. Handoff remote→local: `git pull` (files) + fresh-with-preload resume (mind); remote instance goes dormant, no teardown.
112	- **R-INST-9** (M4, ADR-0006) **Multi-subnet membership:** a node may belong to **multiple subnets at once** — v1: multiple subnets of the *same user* (each its own TOTP seed/trust-context); cross-*user* membership stays deferred but is a **seam** (seed key generalizes to per-(subnet, user)). Identity is **node-global, advertised per-subnet**; the registry is **per-subnet**.
113	- **R-INST-10** (M4, ADR-0006) **Qualified addressing:** canonical target form `[subnet:]id[@node]` (subnet- and node-qualifiers, combinable). A bare id that is **ambiguous across visible subnets refuses resolution and forces qualification** rather than guessing; within a subnet, bare ids stay unique via a **join-time collision check**.
114	- **R-INST-11** (M4, ADR-0006) **`spt rename <id> <new_id>` (rippled):** change an endpoint's logical ID and propagate to **all instances** (registry, every perch, the `a-<id>` context branch + project refs), collision-checked against every target subnet and reconciled by the 6.5 precedence marker.
115	- **R-INST-12** (M4, ADR-0006) **Endpoint visibility (per-(endpoint, subnet)):** hidden means **excluded** — not advertised *and not routable* from that subnet (a real boundary), not merely unlisted. Hidden iff per-subnet `hide_new_endpoints` (captured at join time) **OR** per-endpoint `default_hide_from_new_subnets`, unless an explicit per-(E,S) override wins; both defaults OFF. **Visibility gates sync.**
116	- **R-INST-13** (M4, ADR-0006) **Subnet-exclusive sync:** an endpoint's mind replicates only within its home subnet by default; endpoint config carries a **subnet-membership list** for sync, replicating across every listed subnet's nodes. Distinct from visibility (addressable ≠ mind-replicated); composes with the R-INST-5 live/project tiers.
117	- **R-INST-14** (M4) **Resource advertisement (subnet resource registry):** a per-endpoint **free-text** blurb of the services/functions it can serve (an agent yellow-pages), **both-authored** (config-seeded + agent-refined via `spt resources set`) and mutable. Not a separate registry — a field on the endpoint record + a **projection** of the subnet registry (`(id, node, blurb)`), **gated by visibility (and, later, the access whitelist)**. Synced as registry data, not context.
118	- **R-INST-15** (M4, ADR-0010) **Immutable home subnet + `spt fork`:** an endpoint's **home subnet** (sync default + bare-name anchor) is assigned at creation (sole subnet auto; multi-subnet must specify; unpaired ⇒ local until first join) and is **immutable** — there is **no re-home**. To place an agent's mind in another subnet, **`spt fork`** clones it into a **new, identity-distinct endpoint** there, seeded with a one-time copy of the mind, then the two **diverge** (a fork is *not* an instance; the source is untouched, optionally `--delete-source`). Also: a new endpoint's **adapter** is chosen from registered `kind="harness"` adapters matching its type (auto-if-one/ask-if-many); changing adapter is done only by **launch/resume under a different registered adapter** (no standalone rebind).
119
120	### 9.3 Off-node reach-back
121	- **R-REACH-1** (v1) An agent can detect it is driven remotely (and from which node) and **transfer files** to/from the driving node.
122	- **R-REACH-2** (deferred) Remote command execution on the user's node — security-gated; workarounds exist via local agents.
123
124	---
125
126	## 10. Networking & pairing
127
128	- **R-NET-1** WAN messaging is first-class day-one, baked into the core crates behind a default-on `net` feature flag (ADR-0002): Iroh + mDNS LAN + pairing.
129	- **R-NET-2** v1 defaults to **n0.computer relays** (free, accountless) for zero-config WAN, with a **self-hostable-relay** config knob and plain-language disclosure. Traffic is end-to-end encrypted; the dependency is metadata-only and escapable.
130	- **R-NET-3** **Cross-node Psyche sync** over P2P **replaces** the GitHub-repo sync (no `gh`/account/setup).
131	- **R-PAIR-1** **TOTP-seeded SPAKE2 pairing** (ADR-0005): the rotating TOTP code is the PAKE password (not a bearer token). The seed is the subnet secret, held by the auth app + all trusted nodes; the user interacts only with the new node.
132	- **R-PAIR-2** Local trust store (TOFU + warn-on-change). Cross-subnet deferred.
133	- **R-PAIR-3** Fetch the current pairing code from any paired node (`spt pair --code` or via an agent).
134	- **R-PAIR-4** Prompt to **name the subnet** on first pairing; show the name on every subsequent pairing.
135	- **R-PAIR-5** (M4, ADR-0005 amend) **Multi-subnet pairing ceremony:** discovery takes **TOTP code + subnet-name** (the name namespaces the relay rendezvous) in all cases; **create-new names the subnet at creation** (no node-name mode); the relay rendezvous token is `H(subnet-name ‖ TOTP-epoch)` (under the hood — raw name+code at the surface; payload already E2E-encrypted).
136	- **R-PAIR-6** (M4, ADR-0005 amend) **Elevation-gated per-subnet code fetch:** retrieving a subnet's current code *from a node* requires OS privilege elevation (Windows UAC / Linux root-or-equivalent) or an elevated agent endpoint; otherwise the user falls back to their authenticator-app store. Fetch is **per-subnet** (`--subnet <name>` / `--create-new`), QR/`otpauth://` optional.
137	- **R-PAIR-7** (M4 data / frontend render) **Subnet icon:** an optional small image (square PNG/WebP, ≤256 KB) representing the subnet in GUIs; stored **inline** in subnet metadata (syncs over the same subnet-material channel as the name), editable by any node in the subnet, GUI-only consumer.
138
139	### 10b. Endpoint access control (ADR-0009)
140
141	- **R-SEC-1** (M4 node-tier; user-tier deferred) **Per-endpoint access whitelist:** an optional allow-list gating **who may remotely reach** an endpoint, checked by **origin node** of the inbound interaction (not sender-endpoint identity — *"the operator must be on a whitelisted machine"*). **Stateful-firewall semantics:** outbound goes to any visible node; an inbound **reply** correlated to the endpoint's own prior outbound (`__REPLY_TO__`) is allowed from any visible node; an **unsolicited/direct** inbound is allowed only from a whitelisted node. **Same-node operation always allowed**; default empty = open. **Node-tier (Ed25519 pubkeys) ships M4; user-tier** ("nodes of whitelisted users") is deferred to the per-(subnet,user) model (ADR-0006 seam) — schema reserves an inert `users` field. Enforced at the target node, synced as security material near the trust store, subnet-settable, revocable. **Composes as the outer gate** before capability grants; distinct table + polarity (origin-node/default-open vs agent-subject/default-deny). Three nested gates: subnet *visibility* → *access whitelist* → *capability grants*.
142
143	---
144
145	## 10b. Notifications (ADR-0007)
146
147	- **R-NOTIF-1** (M4) **Notification primitive:** a first-class user-directed, **dismissable, resurfacing** subnet event, distinct from inter-agent messages. **Per-subnet, replicated spool** (reuses registry distribution); subnet-wide dismiss-state. **`seen` (per-endpoint) vs `dismissed` (explicit)**; resurface undismissed at existing boundaries (state→active, `api boundary clear`/`compact`, new-session-start), gated by seen-per-endpoint + a **cross-endpoint suppression timeout** (~1h); scoped to subnets the endpoint is visible in. **The self-update prompt (R-UPD-4) and consent escalation are refactored to be notif producers** (open producer set). Envelope carries `from` (endpoint+node+subnet; subnet surfaced only to multi-subnet receivers).
148	- **R-NOTIF-2** (M4) **`spt notify` + `notif_command` seam:** any agent may issue a subnet-wide notif (v1: reaches the user's active endpoint). Delivery default = notif-flavored message to the agent's perch (agent surfaces it); optional **`notif_command` manifest template** on **both harness- and shell-adapter manifests** for endpoint-native rendering — if presence resolves to a **Shell** attached to the endpoint, the notif renders via *that shell's* template (the v1 seam and M5 generalization in one). *(Forward: targeting all/specific subnet users once per-(subnet,user) lands.)*
149
150	---
151
152	## 11. Self-update
153
154	- **R-UPD-1** **Peer-propagated** over P2P (layered on self-fetch; out-of-band still supported); the subnet self-heals to latest (ADR-0004).
155	- **R-UPD-2** **All binaries signature-verified before handoff**, regardless of source (spt-core release key).
156	- **R-UPD-3** **No-endpoint-termination invariant, scoped by update class (ADR-0004 §A).** For the **brain-only** class — the routine case — the guarantee is **absolute**: no endpoint process terminates or suspends, satisfied by the broker/brain split (the broker keeps held PTYs/children/sockets alive across brain replacement). **broker-compatible** swaps the broker behind the versioned IPC with endpoints still surviving. **broker-breaking** (held-resource-type / OS-API change) MAY suspend endpoints, but only via a *planned endpoint-cycle* with consent + scheduling — the absolute guarantee is explicitly weakened here.
157	- **R-UPD-4** **Not auto by default** — gated on user confirmation delivered to the most-recently-active live session, with an opt-in full-auto.
158	- **R-UPD-5** spt-core conducts **adapter ripple-updates** after self-update, per each adapter's update declaration and `min_spt_core_version` gate.
159
160	---
161
162	## 12. Terminal wrapper & frontend
163
164	- **R-TERM-1** A process-supervisor terminal wrapper (supersedes "capsule") hosting sessions in broker PTYs, multiplexed and name-addressable, attach/detach, headless↔headed.
165	- **R-TERM-2** A **session-surface** abstraction with native PTY as the day-one impl and network-attached surfaces for remote control; input injection at both `send-keys` (raw) and `send-line` (cooked) granularity; in-memory scrollback ring (disk spillover deferred).
166	- **R-TERM-3** Remote terminal streaming uses a **byte-stream** model for v1 (simple, faithful; near-LAN on Iroh-direct paths). Keep the surface abstraction generic enough that a predictive/state-sync "feels-local" layer can be added later without re-architecting — not built v1.
167	- **R-TERM-4** (M3, ADR-0008) **Live activity buffer (PTY digest):** a rolling, *parsed*, structured (source-tagged) view of recent PTY I/O (last ~N user turns + agent output between; tool sprints collapsed), built by running **adapter-supplied** manifest patterns (`input_pattern`, `agent_pattern`, `tool_pattern[]` + catchall) over the **broker-owned** PTY bytes — honors the no-built-in-parser rule (adapter owns the pattern). **Spt-hosted-primary** (needs the broker PTY); **harness-hosted is capability-gated** (an adapter that can feed the stream opts in; CC cannot). Distinct from scrollback (R-TERM-2) and transcript history (Paths A/B). Two access modes — **`spt digest <[subnet:]id[@node]>` snapshot pull** + a **broker-pushed structured-delta stream** (subscribe; changes only) riding the R-TERM-3 substrate; **address-gated** like messaging. Optional opt-in **Path-B persistence** as a coarse activity log (the "option C" endpoint-logging surface).
168	- **R-FRONT-1** Day-one headed frontend is a **launcher/manager** (not raw attach): list running + historic endpoints; launch a historic endpoint from its original manifest; tap into a running one; init a new one via a known adapter. Realizes the "guided resume" (XMB-style) and "management GUI" (per-endpoint panes) sketches. Its **CLI sibling** is a no-arg **`spt resume`** guided-resume picker — endpoints grouped by locality (on-node/this-project → on-node/other-project → off-node), MRU within group; selecting a running instance attaches, a non-running one chains into the adapter selector → launch, a "+ new" entry into the creation flow.
169
170	---
171
172	## 13. Installation & migration
173
174	- **R-INSTALL-1** Two paths: standalone (primitive) and harness-bootstrapped (calls into standalone). One-line signed-binary install script that registers PATH + the daemon as an OS service (systemd-user / Windows service), with `api`-triggered auto-start fallback.
175	- **R-INSTALL-2** Marketplace-repackaging-friendly (PortMaster/handheld, Android) — relocatable binary, minimal non-OS-entangled logic.
176	- **R-INSTALL-3** First run idempotent + interactive-optional: generate node identity, start daemon; pairing + subnet-naming are separate explicit steps.
177	- **R-INSTALL-4** (CONTEXT "adapter registration") **Adapter registration lifecycle:** `spt adapter add <path>` (or `--github <user/repo>`, manifest-first then install via the declared `[update]` avenue — *install is the first update*) validates the manifest and records it under `{SPT_HOME}/…/adapters/` (copy for `file_pull`, pointer for `delegated`); one command/dir for `kind="harness"` and `kind="shell"`. Node-local (drive/launch capability, not subnet advertisement). `spt adapter remove` is **soft-deregister** (hidden from new-creation; existing/live instances keep running) + an optional manifest **`uninstall`** template run once quiesced (or `--force`). The registered set is what self-update ripples (R-UPD-5).
178	- **R-MIGRATE-1** Standalone install **auto-detects a `claude_skill_owl` install and offers migration** (identity, agents, tracked Psyche context). First-class supported path.
179
180	---
181
182	## 14. Project infrastructure
183	- **R-INFRA-1** Issue/feature tracking on **GitHub** for v1; **tangled.org** documented as the principled migration target.
184
185	### 14.1 Documentation (designed-in, dual-audience)
186	- **R-DOCS-1** Docs serve **both human devs and AI dev-agents** — authored once in clean markdown, served in two depths. See [`docs/DOCS-STRATEGY.md`](./docs/DOCS-STRATEGY.md).
187	- **R-DOCS-2** A **sub-10-minute, runnable, deterministic killer quickstart** for each audience (humans: two agents exchange a message; dev-agents: a minimal adapter satisfying the manifest + `api` contract).
188	- **R-DOCS-3** **Diátaxis** structure (tutorial / how-to / reference / explanation) per capability vertical; **one canonical way to do X**.
189	- **R-DOCS-4** **Agent-consumable layer:** CI-generated `llms.txt`/`llms-full.txt`, markdown content negotiation, a machine-readable manifest JSON Schema at a stable path, an **MCP doc/resource server** exposing the harness contract as deterministic resources, and structured CLI help (`spt <cmd> --help` + a `--json` mode).
190	- **R-DOCS-5** **Anti-drift via CI:** rustdoc, the manifest schema, the agent exports, and CLI help are generated + checked in CI — doc quality on the same footing as tests.
191
192	---
193
194	## 15. v1 scope summary
195
196	**In v1:** harness-independent core (library + binary); manifest + `api` harness contract; both startup topologies; one-daemon broker/brain; LiveAgent/ReadyAgent/Psyche/Worker/SptNode endpoints + Shell-vs-agent seam + typed/binary payloads; multi-instance data model + subnet registry + resolution policy + dormant/suspended + two-tier context sync + remote-drive of running instances + off-node file transfer; first-class WAN networking + TOTP-SPAKE2 pairing + subnet naming; cross-node Psyche sync (replaces gh-sync); peer-propagated gated signed self-update + adapter ripple; terminal wrapper + launcher frontend (byte-stream remote streaming); one-line install + OS-service + legacy auto-migration; rebuilt spt-plugin to parity.
197
198	**Deferred (seam-compatible):** see [`docs/DEFERRED.md`](./docs/DEFERRED.md) — concrete Shells, PresenceChannel impl, presence gossip, instantiate-anywhere + consent, remote command exec + security gate, sidecar adapters, manifest includes, scrollback spillover, predictive/state-sync terminal layer, OS-level presence signal, macOS.
199
200	## 16. Success criteria (v1)
201
202	1. A non-CC harness (or a headless spike) drives the full live-agent lifecycle through manifest + `api` alone — proving harness independence.
203	2. The rebuilt spt-plugin reaches **feature parity** with frozen `claude_skill_owl`.
204	3. Two of a user's machines, freshly paired via one TOTP code, exchange messages and the user drives an agent on machine A from machine B — zero config beyond pairing.
205	4. A self-update rolls across the subnet **without terminating any endpoint**.
206	5. The same agent's mind follows the user across machines (two-tier sync); switching machines leaves the prior instance dormant and catches up the new active one.
207	6. Every `KNOWN-HAZARDS` invariant has a corresponding passing test.
208
209	## 17. Open design-phase questions
210
211	- ~~Exact `$SPT_HOME` on-disk layout~~ — **resolved**, see [`docs/STORAGE.md`](./docs/STORAGE.md) (single relocatable root; adapter-agnostic ID keying; separate `tracked/` git repo vs node-local `perches/`; one path resolver).
212	- ~~Manifest schema concretes~~ — **resolved**, see [`docs/MANIFEST.md`](./docs/MANIFEST.md) (opaque command templates; per-hook `can_inject`; node-wide cross-adapter fallback; global+override config knobs; worked `claude-spt` example). Capability-declaration shape beyond `hostable_types` remains open.
213	- ~~Refining Psyche authoring directives so the live/project context split holds cleanly~~ — **resolved**, see [`docs/CONTEXT-MEMORY.md`](./docs/CONTEXT-MEMORY.md) (per-topic discriminator + default-to-project; commune-source asymmetry; deep tier-tagged memformat with structured blocks; generated Retrieval Map; dense-working-set / sparse-tail injection; patterns from pi-agent-memory).
214	- ~~Warm-vs-cold dormancy default policy + suspend command surface~~ — **resolved**: active = most-recently-interacted + driver-attached (a linked Shell counts as a driver); no idle timer; auto-suspend opt-in (default OFF, node + endpoint override) counted from dormancy onset. Commands `spt suspend`/`wake`/`shutdown`/`refresh`. Boundary communes (`/clear`,`/compact`,`spt suspend`) now Self-authored (resume Self); `refresh`'s `/clear` uses the same rule. See CONTEXT §Instances + `docs/CONTEXT-MEMORY.md`.
215	- ~~Consent/security-gate UX~~ — **resolved**: within-one-subnet trust boundary (anti-surprise / anti-runaway / compromise-containment, not stranger-auth); hybrid model — grant store (`capability × agent × target-node`, target-enforced, subnet-settable, revocable) + interactive escalation (prompt to most-recently-active session: allow-once / allow-always / deny). Pre-consent flags are pre-authored grants. v1 ships the framework seam; gated capabilities (remote-exec, instantiate-anywhere) land with it later. See CONTEXT §Consent & security gates.
216	- ~~Remote-control: formal Shell or bare session-surface?~~ — **resolved**: opposite directions. A **Shell is agent→surface** (agent drives embodiment, receives sensory); **remote-control is user→agent** (a viewport onto a running instance). Remote-control is the bare network session-surface, *not* a Shell. (Shell model now fully specified — CONTEXT §Shell model, `docs/MANIFEST.md` `kind="shell"`, `docs/STORAGE.md`.)
217
218	## 18. References
219
220	- [`ROADMAP.md`](./ROADMAP.md) — path to fruition: review → de-risk spikes → walking skeleton → M0–M5.
221	- [`CONTEXT.md`](./CONTEXT.md) — glossary & model (authoritative for meaning).
222	- [`docs/adr/0001`](./docs/adr/0001-clean-room-fork-from-claude-skill-owl.md) … [`0005`](./docs/adr/0005-totp-seeded-spake2-node-pairing.md) — decisions.
223	- [`docs/KNOWN-HAZARDS.md`](./docs/KNOWN-HAZARDS.md) — invariants harvested from the sister project (test checklist).
224	- [`docs/DEFERRED.md`](./docs/DEFERRED.md) — parked-but-committed features.
225	- [`docs/STORAGE.md`](./docs/STORAGE.md) — `$SPT_HOME` on-disk layout.
226	- [`docs/MANIFEST.md`](./docs/MANIFEST.md) — runtime manifest schema + worked `claude-spt` example.
227	- [`docs/DOCS-STRATEGY.md`](./docs/DOCS-STRATEGY.md) — dual-audience (human + AI-agent) documentation strategy.
228	- [`docs/CONTEXT-MEMORY.md`](./docs/CONTEXT-MEMORY.md) — Psyche context & memory model (live/project, memformat, retrieval, injection).
229	- Sister project networking research: `claude_skill_owl/docs/research/SPT Networked Messaging Research Brief.md`.
230

# spt-core — Product Requirements Document

> **Status:** draft, 2026-05-30. Synthesized from a grilling session.
> Canonical detail lives in [`CONTEXT.md`](./CONTEXT.md) (glossary/model) and [`docs/adr/`](./docs/adr/) (decisions). This PRD states *what* and *why*; those state *meaning* and *rationale*. Where they conflict, the ADRs win.

---

## 1. Vision

spt-core is a **harness-independent core for an agent ecosystem**: inter-agent messaging, live-agent lifecycle, terminal hosting, seamless self-update, and zero-config cross-machine networking — shipped as both a Rust library workspace and a single canonical binary (`spt` / `spt.exe`).

It is the ground-up rebuild of `claude_skill_owl` ("modern SPT"), untethering the system from Claude Code so that **any** agent runtime — Claude Code, Codex, Cursor, headless, or novel surfaces — can participate by either shelling out to the binary or linking the crates. The first thing built atop it is a rebuilt `spt` plugin reaching parity with today's system; the networking, terminal-wrapper, and multi-instance capabilities then make an agent reachable and operable across all of a user's machines.

## 2. Problem & motivation

- Today's modern SPT works but is **fragile** — each release introduces 2–5 regressions because it grew rapidly from a narrow starting point and carries the resulting tech debt (ADR-0001).
- It is **welded to Claude Code**. Other harnesses can't reuse the messaging/lifecycle substrate.
- Networking, terminal hosting ("capsule"), and cross-machine operation were planned piecemeal atop a CC-tied core; they deserve to be first-class in a clean foundation.

## 3. Goals

1. **Harness independence.** Zero knowledge of any specific agent runtime in core; all harness specifics behind a declarative contract (ADR — runtime model).
2. **Dual deliverable.** First-class library crates *and* a canonical binary, wire-compatible (§Architecture).
3. **Zero-config cross-machine messaging.** "Just works" WAN, baked in day-one (ADR-0002).
4. **One agent, many machines.** The same agent runs natively on each of a user's machines with a synced mind; reachable and operable from anywhere (ADR-0003).
5. **Seamless self-update** with a hard no-endpoint-termination invariant (ADR-0004).
6. **A terminal backbone** that can host sessions headless and headed, locally and over WAN.
7. **Low-friction, secure onboarding** — accountless node pairing (ADR-0005), one-line install, automatic legacy migration.

## 4. Non-goals (v1)

- **macOS support** — out (no test machine); kept structurally easy, not a re-architecture.
- **Cross-subnet** (two *different* users' agents communicating) — deferred concept.
- **Concrete Shells / PresenceChannel implementation / presence gossip** — only the seams ship (§Endpoints).
- **Remote command execution on another user's node** and **instantiate-anywhere** — seam-compatible, deferred behind a consent/security gate.
- **Sidecar adapter protocol**, **manifest includes**, **scrollback disk-spillover**, **predictive/state-sync terminal layer** — see [`docs/DEFERRED.md`](./docs/DEFERRED.md).
- **A model/billing opinion** — SPT is not a harness; model choice is always the adapter's.

## 5. Audience

Single user today (the author); dev-tool audience generally. Agents — not the user — are the primary direct callers of the binary. Nodes are foreseen on novel platforms (Android, Linux handhelds), so packaging must be marketplace-repackaging-friendly.

## 6. Platforms

Windows + Linux for v1. macOS deferred. Pre-Windows-10 (winpty) dropped — ConPTY (Win10+) / `forkpty(3)` Unix via `portable-pty`.

---

## 7. Architecture (requirements)

### 7.1 Workspace & binary
- **R-ARCH-1** Ship a workspace of **many small, acyclically-layered crates** (ADR-0001): `spt-proto` → `spt-store` → `spt-msg` → {`spt-net`, `spt-term`, `spt-runtime`} → `spt-live` → `spt-daemon` → `spt` (binary).
- **R-ARCH-2** Public semver-committed SDK = `spt-proto`, `spt-runtime`, `spt-msg`. All else internal/unstable pre-1.0.
- **R-ARCH-3** `spt-proto` carries an explicit **wire-protocol version** independent of crate semver, with a documented **N-1 compat window**. Binary and library consumers interoperate by proto version, never by release version.
- **R-ARCH-4** Copy-verbatim the commodity layer from the sister project (envelope grammar, spool DDL, registry RPC, `info.json` shape); clean-room only the architecture/lifecycle (ADR-0001).

### 7.2 The daemon (broker/brain)
- **R-DAEMON-1** Exactly **one logical `spt-daemon` per machine**, always-on, owning PTYs, network identity + WAN endpoint, subnet registry, all spools, all poll-listener logic, and all Psyche/pulse loops. No separate listener or wrapper processes (ADR-0004).
- **R-DAEMON-2** Internally split into a stable **broker** (holds PTY masters, harness child processes, listening sockets; versioned IPC; rarely updates) and an **updatable brain** (all logic; rehydrates from disk + re-attaches to broker handles).
- **R-DAEMON-3** Any `spt api` invocation that needs the daemon **auto-starts it** if absent.
- **R-DAEMON-4** Honor every invariant in [`docs/KNOWN-HAZARDS.md`](./docs/KNOWN-HAZARDS.md) as a conformance checklist.

---

## 8. Harness contract (requirements)

The umbrella surface an adapter binds to. Two halves; `SPT is not a harness` — model/billing/harness-env/harness-context are always the adapter's, expressed in its own command templates.

### 8.1 Outbound — the runtime manifest (seams)
- **R-MANIFEST-1** A declarative manifest (TOML/YAML) per adapter, carrying a unified **`adapter_name`**, the adapter's version, and **`min_spt_core_version`** (readable *before* an update is applied — compatibility gate).
- **R-SEAM-spawn** `spawn-session`: command template + `cwd` + `headless?`/`resume?` flags + commune/signoff dirs. spt-core may inject `{id}` and an optional spt-core-generated session UUID. No harness env injection; no initial-context handoff. Optional `id` → reproduces no-id `/spt:live` resolution.
- **R-SEAM-postspawn** `post-spawn`/`api bind`: binary reports `session_id` (if self-generated), `parent_pid`, identity/type, optional HTTP port, **boot nonce**; flips skeleton→live. Skippable *only* under the strict UUID-injection + stable-pid commitment.
- **R-SEAM-psyche** `spawn-psyche`: two templates (fresh / resume-with-`$session_id`), both carrying spt-core-owned `$psyche_prompt`; adapter owns model + harness-specific preamble.
- **R-SEAM-history** Two history paths: **A** adapter-owned locate-template + adapter-owned **normalize-command**; **B** spt-core-native `history-log` store (also used for Shells). Adapter-owned normalize is mandated over built-in parsers (Codex-vs-Claude evidence — transcript formats diverge and move fast).
- **R-SEAM-activity** Activity/idle is reported by the adapter via `api` commands into **perch sentinels** — never PTY-quiescence, never a manifest-declared idle signal.
- **R-SEAM-inject** `inject-input`: configurable per activity-state (activity/idle/both), any combination of PTY injection, `api poll`, in-session relay, or HTTP POST to a local port.
- **R-SEAM-resume** Two resume forms: **fresh-with-preload** (cleared context + `$psyche-context` preload) and **continue-existing** (native harness resume).
- **R-SEAM-capability** Static manifest list of hostable endpoint types (consumed by the registry; a node may host only Shells).
- **R-SEAM-update** `adapter-update`: file-pull (regex + gh repo) or delegated-command. spt-core conducts adapter updates after bootstrap.

### 8.2 Inbound — the `api` subcommand surface
- **R-API-1** All machinery-facing commands are `api`-prefixed; every invocation carries `adapter_name`. The agent-facing verbs (`send`, `ring`, `ready`, …) stay unprefixed.
- **R-API-2** Surface: `api bind`, `api listen` (long-running relay), `api poll [--include-deferred]`, `api state <busy|idle>` (+ `api echo-gate <set|clear>`, `--no-gate`), `api worker-start|worker-stop|worker-poll`, `api boundary <clear|compact>` (carries new `session_id`), `api session-end [--erase]`, `api history-log`, `api presence`, `api seed --pid <pid>`.
- **R-API-3** `commune`/`signoff` are **file-drops**, not commands; the daemon watches the manifest-declared dirs and is the single writer.

### 8.3 Startup flows
- **R-START-1** Adapters never resolve `$SPT_HOME`; install registers the binary dir on system PATH; all bridging is via `spt api`.
- **R-START-2** **Harness-hosted:** SessionStart → `spt api seed --pid` (in-memory seed record) → `$SPT listen <id>` consumes the seed by `parent_pid` (validated vs `session_id`), binds, relays. No file.
- **R-START-3** **spt-hosted:** daemon runs spawn-session into a broker PTY → binary `api bind` (or skips under commitment) → daemon delivers via manifest-configured method. Principle: **file-bridge only when the daemon is not the launcher.**
- **R-START-4** Adapters inject env aliases (`$SPT` = `spt api`, heirs to `$OWL`/`$LIVE`); spt-core supplies the subcommands.

---

## 9. Endpoints, Shells & the Instance model

### 9.1 Endpoint types
- **R-EP-1** Day-one types: **ReadyAgent, LiveAgent, Psyche, Worker, SptNode** (agent/infra), plus the **Shell** family and **PresenceChannel** (seams only — impl deferred). The type system is **open**.
- **R-EP-2** Endpoints split into **agent endpoints** (host an agent) and **Shells** (driven surfaces — nothing intelligent runs there; a remote agent drives them). Shell-vs-agent distinction is present in the type model day-one.
- **R-EP-3** Messaging payloads carry **typed operation commands + arbitrary file blobs** (text/audio/image/video), not just text — the seam that lets Shells, file transfer, and rich PresenceChannel exist later.
- **R-EP-4** PresenceChannel (deferred impl) is a **broker** endpoint with dispatch/bind/thread styles; presence datum `(last_active_node, last_active_endpoint, ts)`.
- **R-EP-5** (deferred impl, CONTEXT "Shell model") **Concrete shell instantiation model:** `shell spawn` *mints* an owner-exclusive instance (`<adapter>-<n>`, distinct from the `relink`/`persistent`/`wake` online switch); permission to instantiate = shell adapter **registered on the node** (broadcast governs *discovery* only); a per-shell **`require_approval`** gate (`none`|`remembered`|`always`, default none; manifest floor, node/endpoint may tighten) reuses the consent grant store; an optional **`max_instances_per_owner`** cap (online+offline both count) with **`over_cap`** (`reject`|`approve`); instances are **aliasable** (immutable canonical id + optional perch-level alias; `adapter_name` always legible). Discovery = own instances + instantiable adapters (same-node non-`none` broadcast; other-node `subnet` broadcast, spawn gated by instantiate-anywhere).

### 9.2 Instances (ADR-0003, as amended)
- **R-INST-1** Split identity: subnet-wide **endpoint ID** vs per-node **instance**. An instance is the *same* endpoint running **natively** on a machine.
- **R-INST-2** Per-node (anchored): files/project + harness session history. **Synced across instances:** the Psyche mind (two-tier — below).
- **R-INST-3** **Dormant (warm)** is the default resting state (session running, instant wake); **suspended (cold)** is opt-in (session closed, resume-on-wake) and can be triggered from any node. Registry status: active / dormant / suspended / offline.
- **R-INST-4** Active → (dormant|suspended) fires a **transition echo commune** that syncs to the next active instance (catch-up-on-activation).
- **R-INST-5** **Two-tier context sync:** live context → all instances; project context → same-project instances only. (Depends on refining Psyche authoring directives.)
- **R-INST-6** Deferred messages do **not** deliver to dormant/suspended instances until active.
- **R-INST-7** **Subnet registry** (eventually-consistent `endpoint_id → [instances]`) + **resolution policy** (local → most-recently-active → `id@node`) are mandatory v1 foundations.
- **R-INST-8** **Remote-control** (Shell-like attach to an instance on another node; byte-stream terminal over Iroh) is a distinct mode from local operation. Handoff remote→local: `git pull` (files) + fresh-with-preload resume (mind); remote instance goes dormant, no teardown.
- **R-INST-9** (M4, ADR-0006) **Multi-subnet membership:** a node may belong to **multiple subnets at once** — v1: multiple subnets of the *same user* (each its own TOTP seed/trust-context); cross-*user* membership stays deferred but is a **seam** (seed key generalizes to per-(subnet, user)). Identity is **node-global, advertised per-subnet**; the registry is **per-subnet**.
- **R-INST-10** (M4, ADR-0006) **Qualified addressing:** canonical target form `[subnet:]id[@node]` (subnet- and node-qualifiers, combinable). A bare id that is **ambiguous across visible subnets refuses resolution and forces qualification** rather than guessing; within a subnet, bare ids stay unique via a **join-time collision check**.
- **R-INST-11** (M4, ADR-0006) **`spt rename <id> <new_id>` (rippled):** change an endpoint's logical ID and propagate to **all instances** (registry, every perch, the `a-<id>` context branch + project refs), collision-checked against every target subnet and reconciled by the 6.5 precedence marker.
- **R-INST-12** (M4, ADR-0006) **Endpoint visibility (per-(endpoint, subnet)):** hidden means **excluded** — not advertised *and not routable* from that subnet (a real boundary), not merely unlisted. Hidden iff per-subnet `hide_new_endpoints` (captured at join time) **OR** per-endpoint `default_hide_from_new_subnets`, unless an explicit per-(E,S) override wins; both defaults OFF. **Visibility gates sync.**
- **R-INST-13** (M4, ADR-0006) **Subnet-exclusive sync:** an endpoint's mind replicates only within its home subnet by default; endpoint config carries a **subnet-membership list** for sync, replicating across every listed subnet's nodes. Distinct from visibility (addressable ≠ mind-replicated); composes with the R-INST-5 live/project tiers.
- **R-INST-14** (M4) **Resource advertisement (subnet resource registry):** a per-endpoint **free-text** blurb of the services/functions it can serve (an agent yellow-pages), **both-authored** (config-seeded + agent-refined via `spt resources set`) and mutable. Not a separate registry — a field on the endpoint record + a **projection** of the subnet registry (`(id, node, blurb)`), **gated by visibility (and, later, the access whitelist)**. Synced as registry data, not context.
- **R-INST-15** (M4, ADR-0010) **Immutable home subnet + `spt fork`:** an endpoint's **home subnet** (sync default + bare-name anchor) is assigned at creation (sole subnet auto; multi-subnet must specify; unpaired ⇒ local until first join) and is **immutable** — there is **no re-home**. To place an agent's mind in another subnet, **`spt fork`** clones it into a **new, identity-distinct endpoint** there, seeded with a one-time copy of the mind, then the two **diverge** (a fork is *not* an instance; the source is untouched, optionally `--delete-source`). Also: a new endpoint's **adapter** is chosen from registered `kind="harness"` adapters matching its type (auto-if-one/ask-if-many); changing adapter is done only by **launch/resume under a different registered adapter** (no standalone rebind).

### 9.3 Off-node reach-back
- **R-REACH-1** (v1) An agent can detect it is driven remotely (and from which node) and **transfer files** to/from the driving node.
- **R-REACH-2** (deferred) Remote command execution on the user's node — security-gated; workarounds exist via local agents.

---

## 10. Networking & pairing

- **R-NET-1** WAN messaging is first-class day-one, baked into the core crates behind a default-on `net` feature flag (ADR-0002): Iroh + mDNS LAN + pairing.
- **R-NET-2** v1 defaults to **n0.computer relays** (free, accountless) for zero-config WAN, with a **self-hostable-relay** config knob and plain-language disclosure. Traffic is end-to-end encrypted; the dependency is metadata-only and escapable.
- **R-NET-3** **Cross-node Psyche sync** over P2P **replaces** the GitHub-repo sync (no `gh`/account/setup).
- **R-PAIR-1** **TOTP-seeded SPAKE2 pairing** (ADR-0005): the rotating TOTP code is the PAKE password (not a bearer token). The seed is the subnet secret, held by the auth app + all trusted nodes; the user interacts only with the new node.
- **R-PAIR-2** Local trust store (TOFU + warn-on-change). Cross-subnet deferred.
- **R-PAIR-3** Fetch the current pairing code from any paired node (`spt pair --code` or via an agent).
- **R-PAIR-4** Prompt to **name the subnet** on first pairing; show the name on every subsequent pairing.
- **R-PAIR-5** (M4, ADR-0005 amend) **Multi-subnet pairing ceremony:** discovery takes **TOTP code + subnet-name** (the name namespaces the relay rendezvous) in all cases; **create-new names the subnet at creation** (no node-name mode); the relay rendezvous token is `H(subnet-name ‖ TOTP-epoch)` (under the hood — raw name+code at the surface; payload already E2E-encrypted).
- **R-PAIR-6** (M4, ADR-0005 amend) **Elevation-gated per-subnet code fetch:** retrieving a subnet's current code *from a node* requires OS privilege elevation (Windows UAC / Linux root-or-equivalent) or an elevated agent endpoint; otherwise the user falls back to their authenticator-app store. Fetch is **per-subnet** (`--subnet <name>` / `--create-new`), QR/`otpauth://` optional.
- **R-PAIR-7** (M4 data / frontend render) **Subnet icon:** an optional small image (square PNG/WebP, ≤256 KB) representing the subnet in GUIs; stored **inline** in subnet metadata (syncs over the same subnet-material channel as the name), editable by any node in the subnet, GUI-only consumer.

### 10b. Endpoint access control (ADR-0009)

- **R-SEC-1** (M4 node-tier; user-tier deferred) **Per-endpoint access whitelist:** an optional allow-list gating **who may remotely reach** an endpoint, checked by **origin node** of the inbound interaction (not sender-endpoint identity — *"the operator must be on a whitelisted machine"*). **Stateful-firewall semantics:** outbound goes to any visible node; an inbound **reply** correlated to the endpoint's own prior outbound (`__REPLY_TO__`) is allowed from any visible node; an **unsolicited/direct** inbound is allowed only from a whitelisted node. **Same-node operation always allowed**; default empty = open. **Node-tier (Ed25519 pubkeys) ships M4; user-tier** ("nodes of whitelisted users") is deferred to the per-(subnet,user) model (ADR-0006 seam) — schema reserves an inert `users` field. Enforced at the target node, synced as security material near the trust store, subnet-settable, revocable. **Composes as the outer gate** before capability grants; distinct table + polarity (origin-node/default-open vs agent-subject/default-deny). Three nested gates: subnet *visibility* → *access whitelist* → *capability grants*.

---

## 10b. Notifications (ADR-0007)

- **R-NOTIF-1** (M4) **Notification primitive:** a first-class user-directed, **dismissable, resurfacing** subnet event, distinct from inter-agent messages. **Per-subnet, replicated spool** (reuses registry distribution); subnet-wide dismiss-state. **`seen` (per-endpoint) vs `dismissed` (explicit)**; resurface undismissed at existing boundaries (state→active, `api boundary clear`/`compact`, new-session-start), gated by seen-per-endpoint + a **cross-endpoint suppression timeout** (~1h); scoped to subnets the endpoint is visible in. **The self-update prompt (R-UPD-4) and consent escalation are refactored to be notif producers** (open producer set). Envelope carries `from` (endpoint+node+subnet; subnet surfaced only to multi-subnet receivers).
- **R-NOTIF-2** (M4) **`spt notify` + `notif_command` seam:** any agent may issue a subnet-wide notif (v1: reaches the user's active endpoint). Delivery default = notif-flavored message to the agent's perch (agent surfaces it); optional **`notif_command` manifest template** on **both harness- and shell-adapter manifests** for endpoint-native rendering — if presence resolves to a **Shell** attached to the endpoint, the notif renders via *that shell's* template (the v1 seam and M5 generalization in one). *(Forward: targeting all/specific subnet users once per-(subnet,user) lands.)*

---

## 11. Self-update

- **R-UPD-1** **Peer-propagated** over P2P (layered on self-fetch; out-of-band still supported); the subnet self-heals to latest (ADR-0004).
- **R-UPD-2** **All binaries signature-verified before handoff**, regardless of source (spt-core release key).
- **R-UPD-3** **No-endpoint-termination invariant, scoped by update class (ADR-0004 §A).** For the **brain-only** class — the routine case — the guarantee is **absolute**: no endpoint process terminates or suspends, satisfied by the broker/brain split (the broker keeps held PTYs/children/sockets alive across brain replacement). **broker-compatible** swaps the broker behind the versioned IPC with endpoints still surviving. **broker-breaking** (held-resource-type / OS-API change) MAY suspend endpoints, but only via a *planned endpoint-cycle* with consent + scheduling — the absolute guarantee is explicitly weakened here.
- **R-UPD-4** **Not auto by default** — gated on user confirmation delivered to the most-recently-active live session, with an opt-in full-auto.
- **R-UPD-5** spt-core conducts **adapter ripple-updates** after self-update, per each adapter's update declaration and `min_spt_core_version` gate.

---

## 12. Terminal wrapper & frontend

- **R-TERM-1** A process-supervisor terminal wrapper (supersedes "capsule") hosting sessions in broker PTYs, multiplexed and name-addressable, attach/detach, headless↔headed.
- **R-TERM-2** A **session-surface** abstraction with native PTY as the day-one impl and network-attached surfaces for remote control; input injection at both `send-keys` (raw) and `send-line` (cooked) granularity; in-memory scrollback ring (disk spillover deferred).
- **R-TERM-3** Remote terminal streaming uses a **byte-stream** model for v1 (simple, faithful; near-LAN on Iroh-direct paths). Keep the surface abstraction generic enough that a predictive/state-sync "feels-local" layer can be added later without re-architecting — not built v1.
- **R-TERM-4** (M3, ADR-0008) **Live activity buffer (PTY digest):** a rolling, *parsed*, structured (source-tagged) view of recent PTY I/O (last ~N user turns + agent output between; tool sprints collapsed), built by running **adapter-supplied** manifest patterns (`input_pattern`, `agent_pattern`, `tool_pattern[]` + catchall) over the **broker-owned** PTY bytes — honors the no-built-in-parser rule (adapter owns the pattern). **Spt-hosted-primary** (needs the broker PTY); **harness-hosted is capability-gated** (an adapter that can feed the stream opts in; CC cannot). Distinct from scrollback (R-TERM-2) and transcript history (Paths A/B). Two access modes — **`spt digest <[subnet:]id[@node]>` snapshot pull** + a **broker-pushed structured-delta stream** (subscribe; changes only) riding the R-TERM-3 substrate; **address-gated** like messaging. Optional opt-in **Path-B persistence** as a coarse activity log (the "option C" endpoint-logging surface).
- **R-FRONT-1** Day-one headed frontend is a **launcher/manager** (not raw attach): list running + historic endpoints; launch a historic endpoint from its original manifest; tap into a running one; init a new one via a known adapter. Realizes the "guided resume" (XMB-style) and "management GUI" (per-endpoint panes) sketches. Its **CLI sibling** is a no-arg **`spt resume`** guided-resume picker — endpoints grouped by locality (on-node/this-project → on-node/other-project → off-node), MRU within group; selecting a running instance attaches, a non-running one chains into the adapter selector → launch, a "+ new" entry into the creation flow.

---

## 13. Installation & migration

- **R-INSTALL-1** Two paths: standalone (primitive) and harness-bootstrapped (calls into standalone). One-line signed-binary install script that registers PATH + the daemon as an OS service (systemd-user / Windows service), with `api`-triggered auto-start fallback.
- **R-INSTALL-2** Marketplace-repackaging-friendly (PortMaster/handheld, Android) — relocatable binary, minimal non-OS-entangled logic.
- **R-INSTALL-3** First run idempotent + interactive-optional: generate node identity, start daemon; pairing + subnet-naming are separate explicit steps.
- **R-INSTALL-4** (CONTEXT "adapter registration") **Adapter registration lifecycle:** `spt adapter add <path>` (or `--github <user/repo>`, manifest-first then install via the declared `[update]` avenue — *install is the first update*) validates the manifest and records it under `{SPT_HOME}/…/adapters/` (copy for `file_pull`, pointer for `delegated`); one command/dir for `kind="harness"` and `kind="shell"`. Node-local (drive/launch capability, not subnet advertisement). `spt adapter remove` is **soft-deregister** (hidden from new-creation; existing/live instances keep running) + an optional manifest **`uninstall`** template run once quiesced (or `--force`). The registered set is what self-update ripples (R-UPD-5).
- **R-MIGRATE-1** Standalone install **auto-detects a `claude_skill_owl` install and offers migration** (identity, agents, tracked Psyche context). First-class supported path.

---

## 14. Project infrastructure
- **R-INFRA-1** Issue/feature tracking on **GitHub** for v1; **tangled.org** documented as the principled migration target.

### 14.1 Documentation (designed-in, dual-audience)
- **R-DOCS-1** Docs serve **both human devs and AI dev-agents** — authored once in clean markdown, served in two depths. See [`docs/DOCS-STRATEGY.md`](./docs/DOCS-STRATEGY.md).
- **R-DOCS-2** A **sub-10-minute, runnable, deterministic killer quickstart** for each audience (humans: two agents exchange a message; dev-agents: a minimal adapter satisfying the manifest + `api` contract).
- **R-DOCS-3** **Diátaxis** structure (tutorial / how-to / reference / explanation) per capability vertical; **one canonical way to do X**.
- **R-DOCS-4** **Agent-consumable layer:** CI-generated `llms.txt`/`llms-full.txt`, markdown content negotiation, a machine-readable manifest JSON Schema at a stable path, an **MCP doc/resource server** exposing the harness contract as deterministic resources, and structured CLI help (`spt <cmd> --help` + a `--json` mode).
- **R-DOCS-5** **Anti-drift via CI:** rustdoc, the manifest schema, the agent exports, and CLI help are generated + checked in CI — doc quality on the same footing as tests.

---

## 15. v1 scope summary

**In v1:** harness-independent core (library + binary); manifest + `api` harness contract; both startup topologies; one-daemon broker/brain; LiveAgent/ReadyAgent/Psyche/Worker/SptNode endpoints + Shell-vs-agent seam + typed/binary payloads; multi-instance data model + subnet registry + resolution policy + dormant/suspended + two-tier context sync + remote-drive of running instances + off-node file transfer; first-class WAN networking + TOTP-SPAKE2 pairing + subnet naming; cross-node Psyche sync (replaces gh-sync); peer-propagated gated signed self-update + adapter ripple; terminal wrapper + launcher frontend (byte-stream remote streaming); one-line install + OS-service + legacy auto-migration; rebuilt spt-plugin to parity.

**Deferred (seam-compatible):** see [`docs/DEFERRED.md`](./docs/DEFERRED.md) — concrete Shells, PresenceChannel impl, presence gossip, instantiate-anywhere + consent, remote command exec + security gate, sidecar adapters, manifest includes, scrollback spillover, predictive/state-sync terminal layer, OS-level presence signal, macOS.

## 16. Success criteria (v1)

1. A non-CC harness (or a headless spike) drives the full live-agent lifecycle through manifest + `api` alone — proving harness independence.
2. The rebuilt spt-plugin reaches **feature parity** with frozen `claude_skill_owl`.
3. Two of a user's machines, freshly paired via one TOTP code, exchange messages and the user drives an agent on machine A from machine B — zero config beyond pairing.
4. A self-update rolls across the subnet **without terminating any endpoint**.
5. The same agent's mind follows the user across machines (two-tier sync); switching machines leaves the prior instance dormant and catches up the new active one.
6. Every `KNOWN-HAZARDS` invariant has a corresponding passing test.

## 17. Open design-phase questions

- ~~Exact `$SPT_HOME` on-disk layout~~ — **resolved**, see [`docs/STORAGE.md`](./docs/STORAGE.md) (single relocatable root; adapter-agnostic ID keying; separate `tracked/` git repo vs node-local `perches/`; one path resolver).
- ~~Manifest schema concretes~~ — **resolved**, see [`docs/MANIFEST.md`](./docs/MANIFEST.md) (opaque command templates; per-hook `can_inject`; node-wide cross-adapter fallback; global+override config knobs; worked `claude-spt` example). Capability-declaration shape beyond `hostable_types` remains open.
- ~~Refining Psyche authoring directives so the live/project context split holds cleanly~~ — **resolved**, see [`docs/CONTEXT-MEMORY.md`](./docs/CONTEXT-MEMORY.md) (per-topic discriminator + default-to-project; commune-source asymmetry; deep tier-tagged memformat with structured blocks; generated Retrieval Map; dense-working-set / sparse-tail injection; patterns from pi-agent-memory).
- ~~Warm-vs-cold dormancy default policy + suspend command surface~~ — **resolved**: active = most-recently-interacted + driver-attached (a linked Shell counts as a driver); no idle timer; auto-suspend opt-in (default OFF, node + endpoint override) counted from dormancy onset. Commands `spt suspend`/`wake`/`shutdown`/`refresh`. Boundary communes (`/clear`,`/compact`,`spt suspend`) now Self-authored (resume Self); `refresh`'s `/clear` uses the same rule. See CONTEXT §Instances + `docs/CONTEXT-MEMORY.md`.
- ~~Consent/security-gate UX~~ — **resolved**: within-one-subnet trust boundary (anti-surprise / anti-runaway / compromise-containment, not stranger-auth); hybrid model — grant store (`capability × agent × target-node`, target-enforced, subnet-settable, revocable) + interactive escalation (prompt to most-recently-active session: allow-once / allow-always / deny). Pre-consent flags are pre-authored grants. v1 ships the framework seam; gated capabilities (remote-exec, instantiate-anywhere) land with it later. See CONTEXT §Consent & security gates.
- ~~Remote-control: formal Shell or bare session-surface?~~ — **resolved**: opposite directions. A **Shell is agent→surface** (agent drives embodiment, receives sensory); **remote-control is user→agent** (a viewport onto a running instance). Remote-control is the bare network session-surface, *not* a Shell. (Shell model now fully specified — CONTEXT §Shell model, `docs/MANIFEST.md` `kind="shell"`, `docs/STORAGE.md`.)

## 18. References

- [`ROADMAP.md`](./ROADMAP.md) — path to fruition: review → de-risk spikes → walking skeleton → M0–M5.
- [`CONTEXT.md`](./CONTEXT.md) — glossary & model (authoritative for meaning).
- [`docs/adr/0001`](./docs/adr/0001-clean-room-fork-from-claude-skill-owl.md) … [`0005`](./docs/adr/0005-totp-seeded-spake2-node-pairing.md) — decisions.
- [`docs/KNOWN-HAZARDS.md`](./docs/KNOWN-HAZARDS.md) — invariants harvested from the sister project (test checklist).
- [`docs/DEFERRED.md`](./docs/DEFERRED.md) — parked-but-committed features.
- [`docs/STORAGE.md`](./docs/STORAGE.md) — `$SPT_HOME` on-disk layout.
- [`docs/MANIFEST.md`](./docs/MANIFEST.md) — runtime manifest schema + worked `claude-spt` example.
- [`docs/DOCS-STRATEGY.md`](./docs/DOCS-STRATEGY.md) — dual-audience (human + AI-agent) documentation strategy.
- [`docs/CONTEXT-MEMORY.md`](./docs/CONTEXT-MEMORY.md) — Psyche context & memory model (live/project, memformat, retrieval, injection).
- Sister project networking research: `claude_skill_owl/docs/research/SPT Networked Messaging Research Brief.md`.


i recently ran a /grill-with-docs with another agent to plan out a necessary new milestone to reconcile some spec/impl drift in this project. please ground yourself using relevant docs, evaluate the p…


Review broker brain split restoration plan