B1. f/u: 1. ok 2. please explain in greater detail what this means for project deps and end-user UX. no code-level examples/explanations.


<owl_reachability_notice>
You have an active owl perch. When spawning subagents or doing work, use run_in_background: true so you stay reachable for incoming messages.
</owl_reachability_notice>


CAVEMAN MODE ACTIVE (full). Drop articles/filler/pleasantries/hedging. Fragments OK. Code/commits/security: write normal.


**What the n0.computer relay dependency means**

**Mechanism recap (why relays exist at all).** Two machines on different networks usually can't talk directly — home routers (NAT) hide them behind one shared public address. Iroh solves this in two steps: a *relay* server acts as a meeting point so the two machines can find each other and attempt a direct connection ("hole punching"). ~70% of the time the punch succeeds and the relay drops out of the path entirely — machines talk directly, relay sees nothing further. The other ~30% (strict corporate NAT, mobile CGNAT), the relay stays in the path and forwards encrypted traffic it can't read.

**What it means for project dependencies:**

- spt-core's WAN mode, out of the box, points at relay servers operated by **n0.computer** (Iroh's authors). Free, public, no account.
- This is a **soft runtime dependency on a third party.** Not a code dependency — an operational one. If n0's relays are down or go away someday, the ~30% relay-fallback case and the *initial* meeting step degrade. Already-direct connections are unaffected.
- The relay never sees plaintext (end-to-end encrypted by node keypairs), so it's a *availability/metadata* trust question, not a *confidentiality* one. n0 could in principle see "node A and node B are talking, at this time, this much" — not what they say.
- **Escape hatch:** the relay binary is open-source and self-hostable on a cheap VPS. spt-core ships a config knob to point at your own relay instead of n0's. So the dependency is escapable, not locked-in.

**What it means for end-user UX:**

- **Default path — "just works":** user does nothing. Cross-machine messaging works with zero config because n0's relays are the silent default. This is the seamless experience you want.
- **First-run cost:** the *first* time spt-core binds a network socket, the OS firewall prompts (Windows Defender "allow access?" dialog; some Linux setups too). Unavoidable for any app opening a port. spt-core can frame it in the CLI ("requesting network access — click Allow") but can't suppress it without a signed binary + pre-declared exception.
- **Power-user path:** anyone uncomfortable with the n0 dependency (privacy-conscious, air-gapped-ish, or just self-hosting purists) flips a config value to their own relay. Documented, but off the default path.
- **The honest disclosure:** docs must state plainly that default WAN routes initial connection + fallback traffic through n0-operated infrastructure, encrypted, and how to replace it. Hiding it would be the wrong call for a tool like this.

**Net:** for v1, accept n0 default = seamless onboarding; ship the self-host knob + clear disclosure as the principled escape. The dependency is real but bounded (metadata-only, escapable, degrades gracefully).

Capturing B1 + these in CONTEXT.md now.


Edit

**SptNode**:
A machine's participation in an SPT subnet. Owned by the planned `spt-node` daemon; identified by an Ed25519 public key generated on first run. First-class so networking primitives can address nodes directly as message targets, not only as transport peers.

**SptNode**:
A machine's participation in an SPT subnet, identified by an Ed25519 public key generated on first run. First-class so networking primitives can address nodes directly as message targets, not only as transport peers. The node identity and network endpoint are hosted by the machine's `spt-daemon` (see Networking), not a separate process.


The file C:\Users\decid\Documents\projects\spt-core\CONTEXT.md has been updated successfully. (file state is current in your context — no need to Read it back)

**SptNode**:
A machine's participation in an SPT subnet. Owned by the planned `spt-node` daemon; identified by an Ed25519 public key generated on first run. First-class so networking primitives can address nodes directly as message targets, not only as transport peers.

**SptNode**:
A machine's participation in an SPT subnet, identified by an Ed25519 public key generated on first run. First-class so networking primitives can address nodes directly as message targets, not only as transport peers. The node identity and network endpoint are hosted by the machine's `spt-daemon` (see Networking), not a separate process.

A subagent's perch under a parent LiveAgent. Created on subagent start, torn down on subagent stop. Replaces today's "working perch" concept; first-class type so cross-communication between a Self and its workers (and worker↔worker) is addressable.

-A machine's participation in an SPT subnet. Owned by the planned `spt-node` daemon; identified by an Ed25519 public key generated on first run. First-class so networking primitives can address nodes directly as message targets, not only as transport peers.

+A machine's participation in an SPT subnet, identified by an Ed25519 public key generated on first run. First-class so networking primitives can address nodes directly as message targets, not only as transport peers. The node identity and network endpoint are hosted by the machine's `spt-daemon` (see Networking), not a separate process.

An endpoint type designed for "find the user." A PresenceChannel collects presence signals across the user's nodes and lets an agent on any node figure out *which* machine the user most recently interacted with, then deliver a message via a presence-blueprint-specific delivery surface (notification, terminal, etc.). Distinguishes "deliver to the user" from "deliver to a specific machine."


Design spt-core binary and messaging library architecture


**spt-node** (planned follow-on):
The second sub-project. A standalone local daemon owning a node's identity and networking stack, built directly on spt-core's library crates rather than shelling out. Speaks SPT's wire protocol so any client can reach across machines through it.

**spt-daemon** (per-machine supervisor):
The single always-on, one-per-machine process. Owns the PTYs for all hosted sessions (term-daemon role) *and* the node's network identity + WAN endpoint (network-host role). Collapses what the sister project planned as a separate `spt-node` daemon into one process — see Networking. The `spt-node` separate-deliverable concept is retired (the reason it was separate, decoupling networking from the CC-tied legacy binary, is solved by spt-core itself).


**spt-node** (planned follow-on):
The second sub-project. A standalone local daemon owning a node's identity and networking stack, built directly on spt-core's library crates rather than shelling out. Speaks SPT's wire protocol so any client can reach across machines through it.

**spt-daemon** (per-machine supervisor):
The single always-on, one-per-machine process. Owns the PTYs for all hosted sessions (term-daemon role) *and* the node's network identity + WAN endpoint (network-host role). Collapses what the sister project planned as a separate `spt-node` daemon into one process — see Networking. The `spt-node` separate-deliverable concept is retired (the reason it was separate, decoupling networking from the CC-tied legacy binary, is solved by spt-core itself).

A rebuilt version of today's Claude Code `spt` plugin, the first sub-project to be built atop spt-core. Reaches feature parity with today's modern SPT while delegating all core functionality to spt-core (primarily via `spt.exe`, with deeper hooks where useful).

-The second sub-project. A standalone local daemon owning a node's identity and networking stack, built directly on spt-core's library crates rather than shelling out. Speaks SPT's wire protocol so any client can reach across machines through it.

+The single always-on, one-per-machine process. Owns the PTYs for all hosted sessions (term-daemon role) *and* the node's network identity + WAN endpoint (network-host role). Collapses what the sister project planned as a separate `spt-node` daemon into one process — see Networking. The `spt-node` separate-deliverable concept is retired (the reason it was separate, decoupling networking from the CC-tied legacy binary, is solved by spt-core itself).


- **Library crates** — the deeper integration path. Used by sub-projects that link Rust directly (`spt-node`, future first-party services).

- **Library crates** — the deeper integration path. Used by future first-party services that link Rust directly.


spt-core ships **both** a library workspace and a canonical binary:

-- **Library crates** — the deeper integration path. Used by sub-projects that link Rust directly (`spt-node`, future first-party services).

+- **Library crates** — the deeper integration path. Used by future first-party services that link Rust directly.

- **`spt.exe` / `spt`** — the canonical binary, built from the workspace. The primary integration path for harness plugins and external tooling, which mostly fire it as a subprocess at various surfaces (one-shot commands, poll listeners under a Monitor-tool-equivalent, hook tap-ins).

Both surfaces are first-class. Wire-protocol parity between them is a versioning concern from day one (a non-Rust client speaking to `spt.exe` and a Rust client linking the crates must see the same observable behavior).


The expected first non-binary consumer is `spt-node` (planned standalone networking daemon, follow-on project).

The expected non-binary consumers are future first-party services that link Rust directly.


The reference binary built from the workspace. Replaces today's `owl.exe`. Most external integrations (plugins, hooks, scripts in other harnesses) interact with spt-core *only* through this binary — fire-and-forget subcommands, long-running listeners under a parent harness's process supervisor, etc. Unix builds use the same name without `.exe`.

-The set of Rust crates that compose spt-core. Consumers that want a deeper integration than shelling out to `spt.exe` link these crates directly. The reference binary is itself a consumer of the workspace. The expected first non-binary consumer is `spt-node` (planned standalone networking daemon, follow-on project).

+The set of Rust crates that compose spt-core. Consumers that want a deeper integration than shelling out to `spt.exe` link these crates directly. The reference binary is itself a consumer of the workspace. The expected non-binary consumers are future first-party services that link Rust directly.


**scrollback**:
In-memory ring buffer per session for v1. **On-disk spillover for long sessions is an intended, tracked feature** (deferred past v1, not forgotten) — see ROADMAP backlog.

**scrollback**:
In-memory ring buffer per session for v1. **On-disk spillover for long sessions is an intended, tracked feature** (deferred past v1, not forgotten) — see `docs/DEFERRED.md`.

## Networking

WAN cross-machine messaging is a **first-class, day-one capability** of spt-core — not a follow-on project. Goal: minimal-to-zero config, "just works" cross-machine SPT. The transport stack (Iroh + mDNS LAN discovery + pairing) is baked into the core library crates, behind a compile-time feature flag (`net`) that is default-on in the reference binary and optional for embedded/stripped library consumers.

The persistent network presence required for "reachable even when no session is running" is hosted by the `spt-daemon` (the same one-per-machine process that supervises PTYs). There is no separate networking daemon. The node's Ed25519 identity lives at `{SPT_HOME}/owlery/node.key` (heir to the sister project's planned location).

**relay dependency (v1 stance)**:
Out of the box, WAN connection setup and the ~30% relay-fallback path route through relay servers operated by **n0.computer** (Iroh's authors) — free, accountless, the silent default that makes zero-config WAN work. This is a soft, metadata-only, escapable runtime dependency:
- It is operational, not a code dependency. n0 relays being unavailable degrades initial connection setup and the relay-fallback case; already-established direct connections are unaffected.
- Traffic is end-to-end encrypted by node keypairs; relays never see plaintext. The trust question is availability + metadata (who-talks-to-whom-when), not confidentiality.
- The relay binary is open-source and self-hostable; spt-core ships a config knob to point at a user-operated relay instead of n0's.
- v1 ships with n0 as default + the self-host escape hatch + plain-language disclosure in docs. Hiding the dependency is rejected.

First network socket bind triggers an OS firewall prompt (Windows Defender, some Linux); unavoidable without a signed binary + pre-declared exception. Framed in CLI UX, not suppressible.


**scrollback**:
In-memory ring buffer per session for v1. **On-disk spillover for long sessions is an intended, tracked feature** (deferred past v1, not forgotten) — see ROADMAP backlog.

**scrollback**:
In-memory ring buffer per session for v1. **On-disk spillover for long sessions is an intended, tracked feature** (deferred past v1, not forgotten) — see `docs/DEFERRED.md`.

## Networking

WAN cross-machine messaging is a **first-class, day-one capability** of spt-core — not a follow-on project. Goal: minimal-to-zero config, "just works" cross-machine SPT. The transport stack (Iroh + mDNS LAN discovery + pairing) is baked into the core library crates, behind a compile-time feature flag (`net`) that is default-on in the reference binary and optional for embedded/stripped library consumers.

The persistent network presence required for "reachable even when no session is running" is hosted by the `spt-daemon` (the same one-per-machine process that supervises PTYs). There is no separate networking daemon. The node's Ed25519 identity lives at `{SPT_HOME}/owlery/node.key` (heir to the sister project's planned location).

**relay dependency (v1 stance)**:
Out of the box, WAN connection setup and the ~30% relay-fallback path route through relay servers operated by **n0.computer** (Iroh's authors) — free, accountless, the silent default that makes zero-config WAN work. This is a soft, metadata-only, escapable runtime dependency:
- It is operational, not a code dependency. n0 relays being unavailable degrades initial connection setup and the relay-fallback case; already-established direct connections are unaffected.
- Traffic is end-to-end encrypted by node keypairs; relays never see plaintext. The trust question is availability + metadata (who-talks-to-whom-when), not confidentiality.
- The relay binary is open-source and self-hostable; spt-core ships a config knob to point at a user-operated relay instead of n0's.
- v1 ships with n0 as default + the self-host escape hatch + plain-language disclosure in docs. Hiding the dependency is rejected.

First network socket bind triggers an OS firewall prompt (Windows Defender, some Linux); unavoidable without a signed binary + pre-declared exception. Framed in CLI UX, not suppressible.

Two granularities, both first-class. `send-keys` — raw byte stream including escape sequences (e.g. `Ctrl-C`). `send-line` — cooked line-level input flushed on newline (the common case; matches how user input reaches an agent prompt).

-In-memory ring buffer per session for v1. **On-disk spillover for long sessions is an intended, tracked feature** (deferred past v1, not forgotten) — see ROADMAP backlog.

+In-memory ring buffer per session for v1. **On-disk spillover for long sessions is an intended, tracked feature** (deferred past v1, not forgotten) — see `docs/DEFERRED.md`.

+WAN cross-machine messaging is a **first-class, day-one capability** of spt-core — not a follow-on project. Goal: minimal-to-zero config, "just works" cross-machine SPT. The transport stack (Iroh + mDNS LAN discovery + pairing) is baked into the core library crates, behind a compile-time feature flag (`net`) that is default-on in the reference binary and optional for embedded/stripped library consumers.

+The persistent network presence required for "reachable even when no session is running" is hosted by the `spt-daemon` (the same one-per-machine process that supervises PTYs). There is no separate networking daemon. The node's Ed25519 identity lives at `{SPT_HOME}/owlery/node.key` (heir to the sister project's planned location).

+Out of the box, WAN connection setup and the ~30% relay-fallback path route through relay servers operated by **n0.computer** (Iroh's authors) — free, accountless, the silent default that makes zero-config WAN work. This is a soft, metadata-only, escapable runtime dependency:

+- It is operational, not a code dependency. n0 relays being unavailable degrades initial connection setup and the relay-fallback case; already-established direct connections are unaffected.

+- Traffic is end-to-end encrypted by node keypairs; relays never see plaintext. The trust question is availability + metadata (who-talks-to-whom-when), not confidentiality.

+- The relay binary is open-source and self-hostable; spt-core ships a config knob to point at a user-operated relay instead of n0's.

+- v1 ships with n0 as default + the self-host escape hatch + plain-language disclosure in docs. Hiding the dependency is rejected.

+First network socket bind triggers an OS firewall prompt (Windows Defender, some Linux); unavoidable without a signed binary + pre-declared exception. Framed in CLI UX, not suppressible.

The day-one headed frontend is a launcher/manager UX, not just a raw `attach`. The end user cares about *reaching a target agent*, not about PTY mechanics. The frontend must:


B1 is a deliberate reversal of the sister project's documented decision — exactly what an ADR is for. Writing 0002.


Write

# Bake networking into core; collapse spt-node into the per-machine daemon

## Status

accepted (2026-05-28) — reverses the sister project's separate-daemon decision (claude_skill_owl CONTEXT.md §Networking) for the spt-core context.

## Context

The sister project (`claude_skill_owl`) decided networking should live in a separate `spt-node` daemon, explicitly to decouple the network stack from SPT's Claude-Code-tied core binary, expose SPT-protocol-as-product, and let networking evolve on its own release cycle. That decision predates spt-core.

spt-core is a ground-up, harness-independent rebuild. The primary reason `spt-node` was separate — decoupling networking from a CC-tied binary — no longer exists, because spt-core is already decoupled from any harness. Meanwhile the product goal for networking sharpened: WAN cross-machine messaging should be **first-class and zero-config — "just works."**

## Decision

Bake the WAN transport stack (Iroh + mDNS LAN discovery + pairing) into spt-core's library crates, behind a compile-time `net` feature flag (default-on in the reference binary, optional for embedded library consumers).

"Just works WAN, reachable even when no session is running" requires a persistent per-machine network presence — the exact job `spt-node` was created to do. spt-core already commits to a single always-on, one-per-machine `spt-daemon` to supervise PTYs (the terminal wrapper). **Collapse the network-host role into that same daemon (option B1).** One process owns both PTYs and the node identity + WAN endpoint.

Rejected alternatives:
- **Seams-only in core, transport in spt-node (A):** inconsistent with first-class/zero-config networking; preserves a separate-deliverable boundary whose original justification is gone.
- **Two daemons — term-daemon + net-daemon (B2):** doubles the always-on process count and the crash/restart/binary-handoff surface for no benefit once both are in core.
- **LAN-in-core, WAN-in-node (C):** puts the repo boundary *inside* the transport layer, splitting shared discovery/identity/framing code across two repos.

**v1 relay stance:** default to n0.computer's free public relays for zero-config onboarding; ship a self-hostable-relay config knob and plain-language disclosure as the principled escape hatch. The dependency is operational, metadata-only (traffic is end-to-end encrypted), and escapable.

## Consequences

- `spt-node` is retired from the project vocabulary as a separate deliverable. WAN transport becomes a later *milestone* of spt-core, not a separate repo. The `SptNode` endpoint type survives unchanged — it remains how a machine is addressed.
- The "second project atop spt-core" is no longer mandated to be spt-node. The real next milestone is the rebuilt spt plugin reaching parity.
- The reference binary carries Iroh's footprint (~8MB) and the `noq` git-patch coupling when built with `net`. The feature flag keeps minimal library builds clean.
- v1 has a soft, documented runtime dependency on n0-operated relay infrastructure. Acceptable for v1; self-host path is the mitigation.
- The single daemon is a single point of failure for both PTY hosting and networking on a machine; its crash-recovery and binary-handoff story must cover both roles.


File created successfully at: C:\Users\decid\Documents\projects\spt-core\docs\adr\0002-bake-networking-into-core-collapse-spt-node.md (file state is current in your context — no need to Read it back)

# Bake networking into core; collapse spt-node into the per-machine daemon

## Status

accepted (2026-05-28) — reverses the sister project's separate-daemon decision (claude_skill_owl CONTEXT.md §Networking) for the spt-core context.

## Context

The sister project (`claude_skill_owl`) decided networking should live in a separate `spt-node` daemon, explicitly to decouple the network stack from SPT's Claude-Code-tied core binary, expose SPT-protocol-as-product, and let networking evolve on its own release cycle. That decision predates spt-core.

spt-core is a ground-up, harness-independent rebuild. The primary reason `spt-node` was separate — decoupling networking from a CC-tied binary — no longer exists, because spt-core is already decoupled from any harness. Meanwhile the product goal for networking sharpened: WAN cross-machine messaging should be **first-class and zero-config — "just works."**

## Decision

Bake the WAN transport stack (Iroh + mDNS LAN discovery + pairing) into spt-core's library crates, behind a compile-time `net` feature flag (default-on in the reference binary, optional for embedded library consumers).

"Just works WAN, reachable even when no session is running" requires a persistent per-machine network presence — the exact job `spt-node` was created to do. spt-core already commits to a single always-on, one-per-machine `spt-daemon` to supervise PTYs (the terminal wrapper). **Collapse the network-host role into that same daemon (option B1).** One process owns both PTYs and the node identity + WAN endpoint.

Rejected alternatives:
- **Seams-only in core, transport in spt-node (A):** inconsistent with first-class/zero-config networking; preserves a separate-deliverable boundary whose original justification is gone.
- **Two daemons — term-daemon + net-daemon (B2):** doubles the always-on process count and the crash/restart/binary-handoff surface for no benefit once both are in core.
- **LAN-in-core, WAN-in-node (C):** puts the repo boundary *inside* the transport layer, splitting shared discovery/identity/framing code across two repos.

**v1 relay stance:** default to n0.computer's free public relays for zero-config onboarding; ship a self-hostable-relay config knob and plain-language disclosure as the principled escape hatch. The dependency is operational, metadata-only (traffic is end-to-end encrypted), and escapable.

## Consequences

- `spt-node` is retired from the project vocabulary as a separate deliverable. WAN transport becomes a later *milestone* of spt-core, not a separate repo. The `SptNode` endpoint type survives unchanged — it remains how a machine is addressed.
- The "second project atop spt-core" is no longer mandated to be spt-node. The real next milestone is the rebuilt spt plugin reaching parity.
- The reference binary carries Iroh's footprint (~8MB) and the `noq` git-patch coupling when built with `net`. The feature flag keeps minimal library builds clean.
- v1 has a soft, documented runtime dependency on n0-operated relay infrastructure. Acceptable for v1; self-host path is the mitigation.
- The single daemon is a single point of failure for both PTY hosting and networking on a machine; its crash-recovery and binary-handoff story must cover both roles.


B1 locked, ADR-0002 + CONTEXT updated, spt-node retired from vocab. Q7.

---

**Q7 — PresenceChannel: what is it, concretely?**

Your day-one type set includes `PresenceChannel` — "novel outlets for agents to figure out what machine the user most recently interacted with, then reach the user via a unique presence blueprint." Most novel/least-specified concept. Need to pin the mechanism before it can be a first-class endpoint type. Three sub-questions:

**7a — How is "user presence" detected?** What signal says "user is active on machine X right now"?
- (A) Input-activity heartbeat — spt-daemon on each machine watches for keyboard/mouse/terminal activity, timestamps "last user interaction," gossips it across the subnet. Most-recent timestamp wins.
- (B) Agent-interaction heartbeat — narrower: only counts the user interacting *with an SPT agent* (typing in a live session, running `$OWL`). Doesn't touch OS-level input. Lighter, privacy-cleaner, but blind to "user is at the machine but not using SPT."
- (C) Explicit + passive hybrid — passive agent-interaction heartbeat (B) as baseline, plus an explicit "I'm here" claim the user/frontend can set.

**Recommend (B)** for v1. OS-level input monitoring (A) is a per-platform rabbit hole (Win hooks, X11/Wayland divergence, permissions prompts) and privacy-loaded. Agent-interaction is already observable by spt-core (sessions, commands), needs no new OS surface, and matches the actual goal — reach the user *where they're doing SPT things*. (A) can be a later opt-in signal source.

**7b — What's a "presence blueprint" / how does delivery actually reach the user?** A PresenceChannel resolves "which machine," but then *how* does a message surface to a human?
- (A) Inject into the user's most-recent active agent session — surface the message inside the live terminal they're using. Stays inside SPT's own surfaces. No external deps.
- (B) Native OS notification — toast/notification-center on the target machine. Reaches user even if not in an SPT session, but per-platform.
- (C) Pluggable delivery surfaces — "blueprint" = a configured delivery method (in-session inject, OS toast, future: push to phone, Discord, etc.). PresenceChannel picks the machine; blueprint picks the modality. Day-one ships in-session inject; others are blueprint plugins.

**Recommend (C) shape, (A) as the only day-one blueprint.** The word "blueprint" in your description implies pluggability/templating — bake the extension point in, but only implement in-session-inject for v1. OS toast is blueprint #2.

**7c — Is PresenceChannel an addressable endpoint, or a resolution service?** You listed it as an endpoint *type*, but it behaves like a router ("find user, then deliver").
- (A) True endpoint — an agent sends a message *to* the PresenceChannel ("deliver this to the user wherever they are"); the channel resolves + forwards. Sender treats "the user" as one stable address regardless of machine.
- (B) Resolution service — agents query it ("which node has the user?") then send to that node themselves.

**Recommend (A).** Matches your framing ("reach the user") and the endpoint-type listing. The whole value is that "the user" becomes one durable address an agent can fire at without knowing topology. (B) leaks resolution logic into every sender.

So combined recommendation: **PresenceChannel = an addressable endpoint (7c-A); presence detected via agent-interaction heartbeat gossiped across the subnet (7b... 7a-B); delivery via pluggable blueprints, in-session-inject the only one day-one (7b-C).**

Confirm all three, or adjust any?


7a. ok, (B) for v1. could also include user's latest endpoint interaction, in case the agent needs to message that specific endpoint over controlling a parallel instance of themselves 7b. frankly, the concept of presence channel may not be best served as an spt endpoint type. the concept was generally that an agent of any given ID could spin-up or control an instance of themselves, where that instance is not strictly of the same endpoint type. example 1: in a theoretical gamelike 2D world, there might be a `GameRobot` endpoint type. it features agent-driven commands, enabling the agent to communicate, gesture, and move the avatar which corresponds to that live agent's own "presence endpoint". it could even request a synthesized "screenshot" for it to visualize its location relative to the user. that GameRobot endpoint is still behind an SPT node, and that node might be on a novel platform i.e. a gaming handheld. so that machine would definitely have network connectivity, but there's a good chance it is incompatible with conventional agent harnesses. so that endpoint is more of a self-documenting shell which an agent running from another node can drive, rather than an outright agent harness. --- so all said, yes, your suggestion of (C) is more inline with my thoughts. 7c. (A) is closer to the desired behavior. agent should, by way of i.e. hooks, just "know" the best way to reach the user. if they're using a presence channel, they should use the unique tool(s) belonging to it which make the most sense. i.e. for GameRobot, the live agent might instruct it to -show an alert symbol over the robot's head -have the robot approach the user's avatar -preload a message that would be shared when the user interacts. this has me rethinking again whether or not PresenceChannel exists as an endpoint--perhaps it does, but serves to instantiate "shells" for agents to operate, and routes their operation commands to their respective shell? the original concept is also 2-way, so the user could send responses and potentially files back to the agent, and the agent could also potentially send files (text/audio/image/video) to presence channels that support those filetypes. --- feel free to challenge or hone in on any aspect of this consciousness dump


Honing. Your dump contains a bigger abstraction than "PresenceChannel" — let me name it, because it resolves the oscillation.

**The thing you're circling: a Shell is not an agent.**

Every day-one endpoint type so far (ReadyAgent, LiveAgent, Psyche, Worker) *hosts an agent* — backed by a harness. GameRobot is categorically different: it's a **driven surface**. Nothing intelligent runs there. A remote agent (on another node) drives it. It's a "self-documenting shell" exposing a typed toolset (show-alert, move-avatar, preload-message, request-screenshot), living behind a node on a platform that may have zero harness compatibility (the handheld).

So: **introduce `Shell` as a first-class concept distinct from agent endpoints.** A Shell advertises a capability toolset and a 2-way interaction relationship with the user. GameRobot is one Shell type. An OS-notification target is a trivial Shell type. The old "presence blueprint" collapses into this — a blueprint *is* a Shell type + its capabilities. This is the generalization; "blueprint" was the narrow version.

**Now PresenceChannel resolves cleanly — it's a broker, not a surface.** Your own words: "instantiate shells for agents to operate, and route their operation commands to their respective shell." That's a broker endpoint. Job:
1. **Presence resolution** — which node is the user on right now (7a-B heartbeat + last-endpoint-interaction datum),
2. **Shell brokering** — locate/instantiate the right Shell on that node, relay operation commands to it, relay user responses + files back.

So **both are endpoints** (resolves 7c-A): PresenceChannel is the addressable broker; Shell is the actual surface it brokers to. The agent "just knows how to reach the user" by firing at its PresenceChannel; the channel figures out the rest.

**New sub-decision this forces — two interaction styles:**

- **dispatch** (fire-and-forget): "reach the user with this payload." Channel resolves + delivers via the best available Shell, done. Covers notification / in-session-inject. Channel stays in path; "the user" is one durable address; auto-reroutes if user moved machines.
- **bind** (sustained drive): "give me a Shell of capability X near the user." Channel instantiates + hands back a Shell handle; agent drives it directly until teardown. Covers GameRobot (continuous move/gesture, low latency). Handle goes stale if user leaves — but for a driven shell like GameRobot, user leaving = session should suspend anyway, so that's acceptable.

**Recommend: PresenceChannel supports both dispatch and bind.** dispatch for "tell the user something," bind for "operate a surface in front of the user."

**7a addendum captured:** presence datum = `(last_active_node, last_active_endpoint, timestamp)`. The last-endpoint field lets an agent choose between *messaging that specific endpoint* vs *driving a parallel self-instance* — good, that's a real distinction worth preserving.

**Scope reality check — and this is the important one.** None of GameRobot / PresenceChannel / Shell is v1. What's genuinely day-one is the **seam that makes them possible without a later rewrite**:
- the open endpoint-type system (already locked),
- a messaging substrate whose payloads can carry **typed operation commands + arbitrary file blobs** (text/audio/image/video), not just text envelopes,
- the `Shell` vs agent-endpoint distinction baked into the type model so a Shell type can be registered later.

**Recommend day-one: ship the seams (typed+binary-capable message payloads, Shell-vs-agent in the type model). Defer all concrete Shells, PresenceChannel impl, and presence gossip to a later milestone.** Otherwise v1 balloons.

Two confirms:
1. **Shell as a first-class concept distinct from agent endpoints — yes?**
2. **dispatch + bind both, with day-one = seams only (typed/binary payloads + Shell in type model), concrete impls deferred — yes?**


`The agent "just knows how to reach the user" by firing at its PresenceChannel; the channel figures out the rest.` i'm mostly ok with this, except from the standpoint of "what if the agent wants to operate a specific Shell regardless of the user's activity". i also feel that the user should be able to reach the agent from any of their shells. so even if the agent sent a message to i.e. a "messaging" Shell on the users phone, the user should be able to surface that latest message and pick up at the same agent's GameRobot. so this is somewhere between dispatch and bind, more flexible in enabling agent transience. --- agreed about the scope check. not v1, but important to capture. --- confirms: 1. yes 2. ok --- one rough concept that is v1 and needs some fleshing out: -user should be able to directly interact with any endpoint from any machine -LiveAgents should be able to operate onboard any machine (node) they've ever been instantiated on, from any other machine. how do these concepts pan out within the context of spt-core?


Locked: Shell as first-class, PresenceChannel as broker with transience. Capturing model (impl deferred), then the v1 Instances question — which is the real meat.


Edit

**PresenceChannel** (novel concept):
An endpoint type designed for "find the user." A PresenceChannel collects presence signals across the user's nodes and lets an agent on any node figure out *which* machine the user most recently interacted with, then deliver a message via a presence-blueprint-specific delivery surface (notification, terminal, etc.). Distinguishes "deliver to the user" from "deliver to a specific machine."

**PresenceChannel** (broker endpoint — concept locked, impl deferred past v1):
A *broker* endpoint, not an interaction surface. Job: (1) **presence resolution** — track which node + endpoint the user most recently interacted with; (2) **shell brokering** — locate/instantiate the right Shell on that node and relay between the agent and the user. An agent "just knows how to reach the user" by firing at its PresenceChannel; the channel figures out the rest. Also a durable, **shell-agnostic 2-way thread**: messages persist in the channel, not in any one Shell, so the user can be sent a message via a phone messaging-Shell and surface/continue that same agent conversation later at a GameRobot Shell. Shells are interchangeable I/O windows onto the channel's thread.

Three interaction styles:
- **dispatch** — fire-and-forget: "reach the user with this payload"; channel delivers via the best available Shell.
- **bind** — sustained drive: "give me a Shell of capability X"; channel instantiates and the agent drives it directly until teardown. Supports operating a *specific* Shell regardless of where the user currently is (agent transience).
- **thread** — the persistent conversation that floats across Shells; the user can pick it up from any Shell, and 2-way payloads (text/audio/image/video, subject to the Shell's supported types) flow both directions.

Presence datum: `(last_active_node, last_active_endpoint, timestamp)`. The `last_active_endpoint` field lets an agent choose between messaging that specific endpoint vs. driving a parallel instance of itself.


**PresenceChannel** (novel concept):
An endpoint type designed for "find the user." A PresenceChannel collects presence signals across the user's nodes and lets an agent on any node figure out *which* machine the user most recently interacted with, then deliver a message via a presence-blueprint-specific delivery surface (notification, terminal, etc.). Distinguishes "deliver to the user" from "deliver to a specific machine."

**PresenceChannel** (broker endpoint — concept locked, impl deferred past v1):
A *broker* endpoint, not an interaction surface. Job: (1) **presence resolution** — track which node + endpoint the user most recently interacted with; (2) **shell brokering** — locate/instantiate the right Shell on that node and relay between the agent and the user. An agent "just knows how to reach the user" by firing at its PresenceChannel; the channel figures out the rest. Also a durable, **shell-agnostic 2-way thread**: messages persist in the channel, not in any one Shell, so the user can be sent a message via a phone messaging-Shell and surface/continue that same agent conversation later at a GameRobot Shell. Shells are interchangeable I/O windows onto the channel's thread.

Three interaction styles:
- **dispatch** — fire-and-forget: "reach the user with this payload"; channel delivers via the best available Shell.
- **bind** — sustained drive: "give me a Shell of capability X"; channel instantiates and the agent drives it directly until teardown. Supports operating a *specific* Shell regardless of where the user currently is (agent transience).
- **thread** — the persistent conversation that floats across Shells; the user can pick it up from any Shell, and 2-way payloads (text/audio/image/video, subject to the Shell's supported types) flow both directions.

Presence datum: `(last_active_node, last_active_endpoint, timestamp)`. The `last_active_endpoint` field lets an agent choose between messaging that specific endpoint vs. driving a parallel instance of itself.

A machine's participation in an SPT subnet, identified by an Ed25519 public key generated on first run. First-class so networking primitives can address nodes directly as message targets, not only as transport peers. The node identity and network endpoint are hosted by the machine's `spt-daemon` (see Networking), not a separate process.

-An endpoint type designed for "find the user." A PresenceChannel collects presence signals across the user's nodes and lets an agent on any node figure out *which* machine the user most recently interacted with, then deliver a message via a presence-blueprint-specific delivery surface (notification, terminal, etc.). Distinguishes "deliver to the user" from "deliver to a specific machine."

+**PresenceChannel** (broker endpoint — concept locked, impl deferred past v1):

+A *broker* endpoint, not an interaction surface. Job: (1) **presence resolution** — track which node + endpoint the user most recently interacted with; (2) **shell brokering** — locate/instantiate the right Shell on that node and relay between the agent and the user. An agent "just knows how to reach the user" by firing at its PresenceChannel; the channel figures out the rest. Also a durable, **shell-agnostic 2-way thread**: messages persist in the channel, not in any one Shell, so the user can be sent a message via a phone messaging-Shell and surface/continue that same agent conversation later at a GameRobot Shell. Shells are interchangeable I/O windows onto the channel's thread.

+- **dispatch** — fire-and-forget: "reach the user with this payload"; channel delivers via the best available Shell.

+- **bind** — sustained drive: "give me a Shell of capability X"; channel instantiates and the agent drives it directly until teardown. Supports operating a *specific* Shell regardless of where the user currently is (agent transience).

+- **thread** — the persistent conversation that floats across Shells; the user can pick it up from any Shell, and 2-way payloads (text/audio/image/video, subject to the Shell's supported types) flow both directions.

+Presence datum: `(last_active_node, last_active_endpoint, timestamp)`. The `last_active_endpoint` field lets an agent choose between messaging that specific endpoint vs. driving a parallel instance of itself.

The endpoint type system is **open**: harnesses and downstream projects may register additional types beyond the day-one set. Closed-vs-open semantics for capability advertisement (what operations each type accepts, how routing decides eligibility) are deferred to the design phase.


`The agent "just knows how to reach the user" by firing at its PresenceChannel; the channel figures out the rest.` i'm mostly ok with this, except from the standpoint of "what if the agent wants to op…


The endpoint type system is **open**: harnesses and downstream projects may register additional types beyond the day-one set. Closed-vs-open semantics for capability advertisement (what operations each type accepts, how routing decides eligibility) are deferred to the design phase.

The endpoint type system is **open**: harnesses and downstream projects may register additional types beyond the day-one set. Closed-vs-open semantics for capability advertisement (what operations each type accepts, how routing decides eligibility) are deferred to the design phase.

### Agent endpoints vs Shells

Endpoint types split into two families:

**agent endpoints** — *host* an agent, backed by a harness. ReadyAgent, LiveAgent, Psyche, Worker. Something intelligent runs there.

**Shell** (first-class concept — model locked, concrete types deferred past v1): a *driven surface*, not an agent. Nothing intelligent runs at a Shell; a remote agent (on another node) drives it. A Shell is a "self-documenting" endpoint that advertises a **typed capability toolset** and a 2-way interaction relationship with the user. It may live behind a node on a platform with zero conventional-harness compatibility (e.g. a gaming handheld). Examples (all deferred): `GameRobot` (a 2D-world avatar — move, gesture, alert-symbol, request-screenshot, preload-message), an OS-notification target, an in-session-inject target. The earlier "presence blueprint" idea collapses into this: a blueprint *is* a Shell type + its capabilities.

For day-one, only the **seams** that let Shells exist later without a rewrite are in scope (see Networking / Instances and `docs/DEFERRED.md`):
- the open endpoint-type system (above),
- a messaging substrate whose payloads carry **typed operation commands + arbitrary file blobs** (text/audio/image/video), not just text envelopes,
- the Shell-vs-agent-endpoint distinction present in the type model so a Shell type can be registered later.

All concrete Shells, the PresenceChannel implementation, and presence gossip are deferred.


The endpoint type system is **open**: harnesses and downstream projects may register additional types beyond the day-one set. Closed-vs-open semantics for capability advertisement (what operations each type accepts, how routing decides eligibility) are deferred to the design phase.

### Agent endpoints vs Shells

Endpoint types split into two families:

**agent endpoints** — *host* an agent, backed by a harness. ReadyAgent, LiveAgent, Psyche, Worker. Something intelligent runs there.

**Shell** (first-class concept — model locked, concrete types deferred past v1): a *driven surface*, not an agent. Nothing intelligent runs at a Shell; a remote agent (on another node) drives it. A Shell is a "self-documenting" endpoint that advertises a **typed capability toolset** and a 2-way interaction relationship with the user. It may live behind a node on a platform with zero conventional-harness compatibility (e.g. a gaming handheld). Examples (all deferred): `GameRobot` (a 2D-world avatar — move, gesture, alert-symbol, request-screenshot, preload-message), an OS-notification target, an in-session-inject target. The earlier "presence blueprint" idea collapses into this: a blueprint *is* a Shell type + its capabilities.

For day-one, only the **seams** that let Shells exist later without a rewrite are in scope (see Networking / Instances and `docs/DEFERRED.md`):
- the open endpoint-type system (above),
- a messaging substrate whose payloads carry **typed operation commands + arbitrary file blobs** (text/audio/image/video), not just text envelopes,
- the Shell-vs-agent-endpoint distinction present in the type model so a Shell type can be registered later.

All concrete Shells, the PresenceChannel implementation, and presence gossip are deferred.

+Endpoint types split into two families:

+**agent endpoints** — *host* an agent, backed by a harness. ReadyAgent, LiveAgent, Psyche, Worker. Something intelligent runs there.

+**Shell** (first-class concept — model locked, concrete types deferred past v1): a *driven surface*, not an agent. Nothing intelligent runs at a Shell; a remote agent (on another node) drives it. A Shell is a "self-documenting" endpoint that advertises a **typed capability toolset** and a 2-way interaction relationship with the user. It may live behind a node on a platform with zero conventional-harness compatibility (e.g. a gaming handheld). Examples (all deferred): `GameRobot` (a 2D-world avatar — move, gesture, alert-symbol, request-screenshot, preload-message), an OS-notification target, an in-session-inject target. The earlier "presence blueprint" idea collapses into this: a blueprint *is* a Shell type + its capabilities.

+For day-one, only the **seams** that let Shells exist later without a rewrite are in scope (see Networking / Instances and `docs/DEFERRED.md`):

+- the open endpoint-type system (above),

+- a messaging substrate whose payloads carry **typed operation commands + arbitrary file blobs** (text/audio/image/video), not just text envelopes,

+- the Shell-vs-agent-endpoint distinction present in the type model so a Shell type can be registered later.

+All concrete Shells, the PresenceChannel implementation, and presence gossip are deferred.

The multiplatform terminal wrapper is the backbone for hosting agent sessions. Supersedes the sister project's planned "capsule" (which leaned on psmux). Architecturally a **process supervisor** with an abstract session surface.


| Manifest `include` / composition (option C) | manifest v1 | Single flat manifest per harness is enough until a harness wants to override only part of a section | First sub-project that needs to inherit spt-core defaults but override one section |

| Manifest `include` / composition (option C) | manifest v1 | Single flat manifest per harness is enough until a harness wants to override only part of a section | First sub-project that needs to inherit spt-core defaults but override one section |
| Concrete Shell types (GameRobot, OS-notification, in-session-inject) | Shell concept v1 | Model is locked; concrete surfaces are large, platform-specific, and not needed for core | First real use case for a driven surface (e.g. the 2D-world experiment) |
| PresenceChannel implementation (broker, dispatch/bind/thread) | endpoint types v1 | Depends on presence gossip + Shells + multi-instance routing; only the seams ship v1 | Once Shells exist and the subnet registry is stable |
| Presence gossip (`last_active_node/endpoint/timestamp` across subnet) | networking v1 | Needs the multi-instance registry first; agent-interaction heartbeat (7a-B) is the chosen signal | When PresenceChannel work begins |
| OS-level input-activity presence signal (7a-A) | presence v1 | Per-platform, privacy-loaded; agent-interaction heartbeat covers the v1 goal | Opt-in enhancement after agent-interaction presence proves out |


The file C:\Users\decid\Documents\projects\spt-core\docs\DEFERRED.md has been updated successfully. (file state is current in your context — no need to Read it back)

| Manifest `include` / composition (option C) | manifest v1 | Single flat manifest per harness is enough until a harness wants to override only part of a section | First sub-project that needs to inherit spt-core defaults but override one section |
| Concrete Shell types (GameRobot, OS-notification, in-session-inject) | Shell concept v1 | Model is locked; concrete surfaces are large, platform-specific, and not needed for core | First real use case for a driven surface (e.g. the 2D-world experiment) |
| PresenceChannel implementation (broker, dispatch/bind/thread) | endpoint types v1 | Depends on presence gossip + Shells + multi-instance routing; only the seams ship v1 | Once Shells exist and the subnet registry is stable |
| Presence gossip (`last_active_node/endpoint/timestamp` across subnet) | networking v1 | Needs the multi-instance registry first; agent-interaction heartbeat (7a-B) is the chosen signal | When PresenceChannel work begins |
| OS-level input-activity presence signal (7a-A) | presence v1 | Per-platform, privacy-loaded; agent-interaction heartbeat covers the v1 goal | Opt-in enhancement after agent-interaction presence proves out |

# Deferred Features

Intended features explicitly cut from v1 scope but committed for later. Not backlog noise — these are decided directions parked for sequencing reasons. Keep this list short and real.

| Feature | Cut from | Why deferred | Trigger to revisit |
|---|---|---|---|
| Scrollback on-disk spillover | terminal wrapper v1 | In-memory ring covers the common case; spillover adds a persistence/rotation story | First long-running session that overflows the ring usefully, or any "scroll back further than the buffer" user need |
| Sidecar adapter process (long-running, wire-protocol) | harness contract v1 | Manifest + `spt.exe` subcommand surface covers v1 harnesses; sidecar only earns its keep for streaming / in-memory cross-event state | A harness outgrows manifest+hooks (needs streaming or stateful coordination) |
| Manifest `include` / composition (option C) | manifest v1 | Single flat manifest per harness is enough until a harness wants to override only part of a section | First sub-project that needs to inherit spt-core defaults but override one section |

+| Concrete Shell types (GameRobot, OS-notification, in-session-inject) | Shell concept v1 | Model is locked; concrete surfaces are large, platform-specific, and not needed for core | First real use case for a driven surface (e.g. the 2D-world experiment) |

+| PresenceChannel implementation (broker, dispatch/bind/thread) | endpoint types v1 | Depends on presence gossip + Shells + multi-instance routing; only the seams ship v1 | Once Shells exist and the subnet registry is stable |

+| Presence gossip (`last_active_node/endpoint/timestamp` across subnet) | networking v1 | Needs the multi-instance registry first; agent-interaction heartbeat (7a-B) is the chosen signal | When PresenceChannel work begins |

+| OS-level input-activity presence signal (7a-A) | presence v1 | Per-platform, privacy-loaded; agent-interaction heartbeat covers the v1 goal | Opt-in enhancement after agent-interaction presence proves out |


Captured. Now your two v1 concepts — these are the **Instances** model (Image #1). Let me map how they pan out in spt-core, name the new pieces they force, then pin the v1 line.

---

**Q8 — The Instances model.**

Your two requirements:
- (a) user interacts with any endpoint from any machine
- (b) a LiveAgent operates on any node it's been instantiated on, from any other machine

**Core data-model shift this forces:** today an agent ≈ a perch on one machine. Multi-instance breaks that into two layers:

- **endpoint ID** = the logical identity (`ling`). Shared, subnet-wide.
- **instance** = a materialization of that ID on a specific node. `ling@desktop`, `ling@laptop` are distinct instances of one ID.

Key question that decides everything downstream: **is an instance node-anchored or portable?**

I argue **node-anchored.** A LiveAgent's substance — project working dir, harness session history (CC's JSONL), Psyche context, spool — lives on *its* node's filesystem. You can't teleport the desktop's git repo to the laptop. So "all instances have equal *functionality*" (the image's wording) means *you can drive any instance with the full command surface from anywhere* — NOT that instances share state. Each instance is a distinct seat; the ID is shared identity for addressing. `ling@desktop` and `ling@laptop` are two seats of the same named agent, each anchored to its node's environment, possibly different projects.

**How (b) — "operate ling's desktop instance from the laptop" — actually works:** ling's session runs inside spt-daemon's PTY *on desktop*. Laptop's spt-daemon opens a network session-surface stream to desktop's spt-daemon, which proxies ling's PTY. This is **exactly the Q5(C) network-attached `session surface` impl + the B1 daemon collapse paying off** — the daemon already owns both the PTY and the network endpoint, so remote-drive is daemon-to-daemon. "Headed on a whim, from anywhere" generalizes to "from any node."

**New pieces these requirements force (none free, all v1-adjacent):**

1. **Subnet registry** — distributed, eventually-consistent map `endpoint_id → [instances: (node, perch, type, status)]`. This is the "agent name resolution" the sister project explicitly deferred — now mandatory and first-class. Every spt-daemon participates.
2. **Resolution policy** — bare `ling` with 3 instances must resolve deterministically. Recommend: local-node instance if present → else most-recently-active → with explicit `ling@<node>` override always available.
3. **Network session-surface** — the remote-PTY-drive seam (Q5(C) impl). Daemon-to-daemon PTY proxy.
4. **Instantiate-anywhere + consent** — from laptop, "spin up ling on desktop" routes a launch command to desktop's daemon, which runs ling's manifest locally. Image notes "possibly requiring user consent" — so a consent gate on remote instantiation.

**The v1 scope tension.** Full (b) — remote PTY-drive + instantiate-anywhere — sits at the *top* of the whole stack: needs WAN networking + registry + network-session-surface + consent, all working. It's the capstone, not the foundation. Three honest v1 lines:

- **(V1-min) Seams only.** Ship the endpoint-ID/instance split in the data model, the registry *interface*, and the network-session-surface *abstraction* — but v1 working behavior is local-only + cross-node *messaging* (reach a running instance by routing a message to its node). Remote PTY-drive and instantiate-anywhere are the next milestone. Nothing needs a rewrite to add them.
- **(V1-mid) + remote drive of running instances.** Above, plus laptop can attach/drive an *already-running* ling@desktop. No instantiate-anywhere yet (the instance must already be up on its node).
- **(V1-max) full Instances.** All of it: registry, remote drive, instantiate-anywhere with consent. Largest v1.

**Recommend V1-mid.** Reasons: (1) the data-model split (ID vs instance) and registry are non-negotiable foundations — defer them and everything is a rewrite, so they're in regardless; (2) remote-drive-of-running-instance is the single highest-value piece of your requirement and exercises the whole networking+daemon stack end-to-end (great forcing function for getting the seams right); (3) instantiate-anywhere + consent is genuinely separable — it's "launch flow over the network," additive once remote-drive works, and carries the thorny consent-UX design that shouldn't gate v1.

So: V1-mid behavior, V1-max seams (instantiate-anywhere designed-for but not built).

Two confirms:
1. **Instance = node-anchored** (shared identity, per-node state, "equal functionality" = drivable-from-anywhere not shared-state) — agree?
2. **V1 line = V1-mid** (data-model split + registry + remote-drive of running instances; instantiate-anywhere deferred but seam-compatible) — agree, or push to V1-max?


side tangent: before i forget, `psyches/tracked/` is backed up today via remote gh repo sync (modern spt). in `spt-node`, this should become a free, no-registration/no-setup cross-node sync facilitated by spt-node's networking infra. --- new pieces: 1. agree 2. agree 3. ok 4. ok --- also before i forget: by way of piece 4, all live parallel instances should--when they become active again--be unobtrusively fed the latest psyche context from the most recently active instance-->whichever instance has the newest context that is also newer than the current instance's context. --- another: agent should be able to identify if user is controlling from off-node + get files to/from, run commands on their node. --- confirms: 1. yes 2. agree


Captured all. Instance model + V1-mid locked — writing CONTEXT + ADR. Then the new items (psyche sync, context-feed, off-node reach-back) need scope calls.


First network socket bind triggers an OS firewall prompt (Windows Defender, some Linux); unavoidable without a signed binary + pre-declared exception. Framed in CLI UX, not suppressible.

First network socket bind triggers an OS firewall prompt (Windows Defender, some Linux); unavoidable without a signed binary + pre-declared exception. Framed in CLI UX, not suppressible.

### Cross-node Psyche sync

Today's modern SPT backs up `psyches/tracked/` via a remote GitHub repo (`/spt:psyche-sync-setup`, requires `gh` + account + setup). In spt-core this is **replaced** by free, no-registration, no-setup cross-node sync over the built-in P2P networking. Psyche context replicates between a user's nodes as ordinary file/blob payloads on the messaging substrate (the same typed/binary payload seam Shells need). The GitHub-repo sync mechanism is retired.

## Instances

The multi-instance identity model. An agent is no longer "a perch on one machine"; identity and materialization split into two layers:

**endpoint ID**:
The logical identity (`ling`). Shared subnet-wide. The thing you address.

**instance**:
A materialization of an endpoint ID on a specific node — `ling@desktop`, `ling@laptop` are distinct instances of one ID. **Node-anchored**: an instance's substance (project working dir, harness session history, Psyche context, spool) lives on *its* node's filesystem and cannot teleport. Instances of one ID are distinct *seats*, possibly on different projects — not shared state.

"All instances have equal functionality" means **any instance is drivable with the full command surface from any node** — not that instances share state. The ID is shared identity for addressing; the state is per-node.

**How remote drive works** (req: a LiveAgent operates on any node it's been instantiated on, from any other machine): the instance's session runs inside its node's `spt-daemon` PTY. A second node's `spt-daemon` opens a network session-surface stream to the hosting daemon, which proxies the PTY. This is the network-attached `session surface` implementation (the generalized form of the terminal wrapper's session-surface abstraction) running daemon-to-daemon — the collapse of PTY-host + network-host into one `spt-daemon` is what makes it direct. "Headed on a whim" generalizes to "from any node."

### Pieces the Instances model requires

**subnet registry**:
Distributed, eventually-consistent map `endpoint_id → [instances: (node, perch, type, status)]`. Every `spt-daemon` participates. This is the "agent name resolution" the sister project deferred — now first-class and mandatory.

**resolution policy**:
How bare `ling` resolves when multiple instances exist. Rule: local-node instance if present → else most-recently-active → explicit `ling@<node>` override always available.

**instantiate-anywhere** (seam day-one, behavior deferred):
From node Y, spin up an instance of an endpoint on node X by routing a launch command to X's daemon, which runs the endpoint's manifest locally. "Agents can spin up instances (including of themselves) on any node, possibly requiring user consent" — so a **consent gate** governs remote instantiation. The consent-UX design is deferred; the model is built to accommodate it.

**cross-instance context freshness**:
When a parallel instance becomes active again, it is unobtrusively fed the latest Psyche context from the most-recently-active instance — specifically whichever instance holds context that is both newest among peers *and* newer than the reactivating instance's own. Requires per-instance context versioning + the registry + cross-node Psyche sync.

### Off-node reach-back

When the user is driving an instance from off-node, the agent should be able to:
- **detect** that it is being driven remotely (and from which node) — observable from the `spt-daemon`, which knows whether the driving session-surface stream is local or network-attached;
- **transfer files** to/from the user's (driving) node — file payloads over the messaging substrate;
- **run commands** on the user's node — remote command execution, **security-gated** (shares the consent model with instantiate-anywhere; arbitrary cross-node exec is the highest-risk capability in the system).

### v1 scope line (V1-mid)

- **In v1:** the endpoint-ID / instance data-model split; the subnet registry; resolution policy; remote-drive of *already-running* instances (network session-surface); cross-node Psyche sync transport.
- **Seam-compatible but deferred:** instantiate-anywhere + its consent gate; remote command execution + its security gate; the cross-instance context-freshness *rule* (transport is v1, the auto-feed policy may follow); off-node file transfer (depends only on the binary-payload seam — may land in v1 if cheap).
- **Rationale:** the data-model split and registry are foundations — deferring them forces a later rewrite, so they are in regardless. Remote-drive of a running instance is the highest-value slice and exercises the whole networking+daemon stack end-to-end. Instantiate-anywhere and remote-exec carry consent/security design that should not gate v1.


side tangent: before i forget, `psyches/tracked/` is backed up today via remote gh repo sync (modern spt). in `spt-node`, this should become a free, no-registration/no-setup cross-node sync facilitate…


First network socket bind triggers an OS firewall prompt (Windows Defender, some Linux); unavoidable without a signed binary + pre-declared exception. Framed in CLI UX, not suppressible.

### Cross-node Psyche sync

Today's modern SPT backs up `psyches/tracked/` via a remote GitHub repo (`/spt:psyche-sync-setup`, requires `gh` + account + setup). In spt-core this is **replaced** by free, no-registration, no-setup cross-node sync over the built-in P2P networking. Psyche context replicates between a user's nodes as ordinary file/blob payloads on the messaging substrate (the same typed/binary payload seam Shells need). The GitHub-repo sync mechanism is retired.

## Instances

The multi-instance identity model. An agent is no longer "a perch on one machine"; identity and materialization split into two layers:

**endpoint ID**:
The logical identity (`ling`). Shared subnet-wide. The thing you address.

**instance**:
A materialization of an endpoint ID on a specific node — `ling@desktop`, `ling@laptop` are distinct instances of one ID. **Node-anchored**: an instance's substance (project working dir, harness session history, Psyche context, spool) lives on *its* node's filesystem and cannot teleport. Instances of one ID are distinct *seats*, possibly on different projects — not shared state.

"All instances have equal functionality" means **any instance is drivable with the full command surface from any node** — not that instances share state. The ID is shared identity for addressing; the state is per-node.

**How remote drive works** (req: a LiveAgent operates on any node it's been instantiated on, from any other machine): the instance's session runs inside its node's `spt-daemon` PTY. A second node's `spt-daemon` opens a network session-surface stream to the hosting daemon, which proxies the PTY. This is the network-attached `session surface` implementation (the generalized form of the terminal wrapper's session-surface abstraction) running daemon-to-daemon — the collapse of PTY-host + network-host into one `spt-daemon` is what makes it direct. "Headed on a whim" generalizes to "from any node."

### Pieces the Instances model requires

**subnet registry**:
Distributed, eventually-consistent map `endpoint_id → [instances: (node, perch, type, status)]`. Every `spt-daemon` participates. This is the "agent name resolution" the sister project deferred — now first-class and mandatory.

**resolution policy**:
How bare `ling` resolves when multiple instances exist. Rule: local-node instance if present → else most-recently-active → explicit `ling@<node>` override always available.

**instantiate-anywhere** (seam day-one, behavior deferred):
From node Y, spin up an instance of an endpoint on node X by routing a launch command to X's daemon, which runs the endpoint's manifest locally. "Agents can spin up instances (including of themselves) on any node, possibly requiring user consent" — so a **consent gate** governs remote instantiation. The consent-UX design is deferred; the model is built to accommodate it.

**cross-instance context freshness**:
When a parallel instance becomes active again, it is unobtrusively fed the latest Psyche context from the most-recently-active instance — specifically whichever instance holds context that is both newest among peers *and* newer than the reactivating instance's own. Requires per-instance context versioning + the registry + cross-node Psyche sync.

### Off-node reach-back

When the user is driving an instance from off-node, the agent should be able to:
- **detect** that it is being driven remotely (and from which node) — observable from the `spt-daemon`, which knows whether the driving session-surface stream is local or network-attached;
- **transfer files** to/from the user's (driving) node — file payloads over the messaging substrate;
- **run commands** on the user's node — remote command execution, **security-gated** (shares the consent model with instantiate-anywhere; arbitrary cross-node exec is the highest-risk capability in the system).

### v1 scope line (V1-mid)

- **In v1:** the endpoint-ID / instance data-model split; the subnet registry; resolution policy; remote-drive of *already-running* instances (network session-surface); cross-node Psyche sync transport.
- **Seam-compatible but deferred:** instantiate-anywhere + its consent gate; remote command execution + its security gate; the cross-instance context-freshness *rule* (transport is v1, the auto-feed policy may follow); off-node file transfer (depends only on the binary-payload seam — may land in v1 if cheap).
- **Rationale:** the data-model split and registry are foundations — deferring them forces a later rewrite, so they are in regardless. Remote-drive of a running instance is the highest-value slice and exercises the whole networking+daemon stack end-to-end. Instantiate-anywhere and remote-exec carry consent/security design that should not gate v1.

+Today's modern SPT backs up `psyches/tracked/` via a remote GitHub repo (`/spt:psyche-sync-setup`, requires `gh` + account + setup). In spt-core this is **replaced** by free, no-registration, no-setup cross-node sync over the built-in P2P networking. Psyche context replicates between a user's nodes as ordinary file/blob payloads on the messaging substrate (the same typed/binary payload seam Shells need). The GitHub-repo sync mechanism is retired.

+The multi-instance identity model. An agent is no longer "a perch on one machine"; identity and materialization split into two layers:

+The logical identity (`ling`). Shared subnet-wide. The thing you address.

+A materialization of an endpoint ID on a specific node — `ling@desktop`, `ling@laptop` are distinct instances of one ID. **Node-anchored**: an instance's substance (project working dir, harness session history, Psyche context, spool) lives on *its* node's filesystem and cannot teleport. Instances of one ID are distinct *seats*, possibly on different projects — not shared state.

+"All instances have equal functionality" means **any instance is drivable with the full command surface from any node** — not that instances share state. The ID is shared identity for addressing; the state is per-node.

+**How remote drive works** (req: a LiveAgent operates on any node it's been instantiated on, from any other machine): the instance's session runs inside its node's `spt-daemon` PTY. A second node's `spt-daemon` opens a network session-surface stream to the hosting daemon, which proxies the PTY. This is the network-attached `session surface` implementation (the generalized form of the terminal wrapper's session-surface abstraction) running daemon-to-daemon — the collapse of PTY-host + network-host into one `spt-daemon` is what makes it direct. "Headed on a whim" generalizes to "from any node."

+### Pieces the Instances model requires

+Distributed, eventually-consistent map `endpoint_id → [instances: (node, perch, type, status)]`. Every `spt-daemon` participates. This is the "agent name resolution" the sister project deferred — now first-class and mandatory.

+How bare `ling` resolves when multiple instances exist. Rule: local-node instance if present → else most-recently-active → explicit `ling@<node>` override always available.

+**instantiate-anywhere** (seam day-one, behavior deferred):

+From node Y, spin up an instance of an endpoint on node X by routing a launch command to X's daemon, which runs the endpoint's manifest locally. "Agents can spin up instances (including of themselves) on any node, possibly requiring user consent" — so a **consent gate** governs remote instantiation. The consent-UX design is deferred; the model is built to accommodate it.

+When a parallel instance becomes active again, it is unobtrusively fed the latest Psyche context from the most-recently-active instance — specifically whichever instance holds context that is both newest among peers *and* newer than the reactivating instance's own. Requires per-instance context versioning + the registry + cross-node Psyche sync.

+When the user is driving an instance from off-node, the agent should be able to:

+- **detect** that it is being driven remotely (and from which node) — observable from the `spt-daemon`, which knows whether the driving session-surface stream is local or network-attached;

+- **transfer files** to/from the user's (driving) node — file payloads over the messaging substrate;

+- **run commands** on the user's node — remote command execution, **security-gated** (shares the consent model with instantiate-anywhere; arbitrary cross-node exec is the highest-risk capability in the system).

+- **In v1:** the endpoint-ID / instance data-model split; the subnet registry; resolution policy; remote-drive of *already-running* instances (network session-surface); cross-node Psyche sync transport.

+- **Seam-compatible but deferred:** instantiate-anywhere + its consent gate; remote command execution + its security gate; the cross-instance context-freshness *rule* (transport is v1, the auto-feed policy may follow); off-node file transfer (depends only on the binary-payload seam — may land in v1 if cheap).

+- **Rationale:** the data-model split and registry are foundations — deferring them forces a later rewrite, so they are in regardless. Remote-drive of a running instance is the highest-value slice and exercises the whole networking+daemon stack end-to-end. Instantiate-anywhere and remote-exec carry consent/security design that should not gate v1.

The day-one headed frontend is a launcher/manager UX, not just a raw `attach`. The end user cares about *reaching a target agent*, not about PTY mechanics. The frontend must:


Write

# Multi-instance, node-anchored identity model

## Status

accepted (2026-05-29)

## Context

The sister project models an agent as essentially one perch on one machine, and explicitly deferred "agent name resolution" across a subnet. spt-core commits to two user-facing requirements that break that model: (a) the user can interact with any endpoint from any machine, and (b) a LiveAgent can be operated on any node it has ever been instantiated on, from any other machine. The Instances concept (each endpoint classifies as an instance; the same endpoint ID can have instances across multiple machines with equal functionality) requires deciding what an "instance" actually is before any of it can be built.

## Decision

Split agent identity into two layers: a subnet-wide **endpoint ID** (the logical identity, `ling`) and an **instance** (a materialization of that ID on a specific node, `ling@desktop`).

**Instances are node-anchored, not portable.** An instance's substance — project working directory, harness session history, Psyche context, spool — lives on its node's filesystem and does not teleport. Instances of one ID are distinct *seats* (possibly on different projects), sharing only identity. "Equal functionality across all machines" therefore means *any instance is drivable with the full command surface from any node*, NOT that instances share state.

Rejected alternative — **portable/synced instances** (one logical conversation+state replicated across nodes): a LiveAgent's whole point is operating inside a project on a machine; the project filesystem can't be teleported, and full state replication across nodes is a distributed-consistency problem with no payoff for the actual use case (drive my desktop agent from my laptop).

Remote drive is realized by daemon-to-daemon PTY proxying over the network session-surface abstraction — enabled by the prior decision (ADR-0002) to collapse PTY-host and network-host into one `spt-daemon`.

**v1 scope (V1-mid):** ship the data-model split, the subnet registry (the now-mandatory name-resolution layer), the resolution policy, remote-drive of already-running instances, and cross-node Psyche sync transport. Defer (seam-compatible) instantiate-anywhere + its consent gate, remote command execution + its security gate, and the cross-instance context-freshness auto-feed rule.

## Consequences

- The subnet registry becomes a mandatory, first-class subsystem (eventually-consistent `endpoint_id → [instances]`), not a deferred nicety.
- Bare-ID addressing needs a deterministic resolution policy (local → most-recently-active → explicit `id@node` override).
- The networking decision (ADR-0002) is now load-bearing for a flagship user feature, not just messaging — remote drive depends on it working well.
- Cross-node Psyche sync over P2P replaces the sister project's GitHub-repo sync, removing the `gh`/account/setup dependency.
- Remote command execution and remote instantiation are the highest-risk capabilities in the system; both are gated behind a consent/security model whose design is deferred but whose existence is assumed by the architecture.


File created successfully at: C:\Users\decid\Documents\projects\spt-core\docs\adr\0003-multi-instance-node-anchored-identity-model.md (file state is current in your context — no need to Read it back)

# Multi-instance, node-anchored identity model

## Status

accepted (2026-05-29)

## Context

The sister project models an agent as essentially one perch on one machine, and explicitly deferred "agent name resolution" across a subnet. spt-core commits to two user-facing requirements that break that model: (a) the user can interact with any endpoint from any machine, and (b) a LiveAgent can be operated on any node it has ever been instantiated on, from any other machine. The Instances concept (each endpoint classifies as an instance; the same endpoint ID can have instances across multiple machines with equal functionality) requires deciding what an "instance" actually is before any of it can be built.

## Decision

Split agent identity into two layers: a subnet-wide **endpoint ID** (the logical identity, `ling`) and an **instance** (a materialization of that ID on a specific node, `ling@desktop`).

**Instances are node-anchored, not portable.** An instance's substance — project working directory, harness session history, Psyche context, spool — lives on its node's filesystem and does not teleport. Instances of one ID are distinct *seats* (possibly on different projects), sharing only identity. "Equal functionality across all machines" therefore means *any instance is drivable with the full command surface from any node*, NOT that instances share state.

Rejected alternative — **portable/synced instances** (one logical conversation+state replicated across nodes): a LiveAgent's whole point is operating inside a project on a machine; the project filesystem can't be teleported, and full state replication across nodes is a distributed-consistency problem with no payoff for the actual use case (drive my desktop agent from my laptop).

Remote drive is realized by daemon-to-daemon PTY proxying over the network session-surface abstraction — enabled by the prior decision (ADR-0002) to collapse PTY-host and network-host into one `spt-daemon`.

**v1 scope (V1-mid):** ship the data-model split, the subnet registry (the now-mandatory name-resolution layer), the resolution policy, remote-drive of already-running instances, and cross-node Psyche sync transport. Defer (seam-compatible) instantiate-anywhere + its consent gate, remote command execution + its security gate, and the cross-instance context-freshness auto-feed rule.

## Consequences

- The subnet registry becomes a mandatory, first-class subsystem (eventually-consistent `endpoint_id → [instances]`), not a deferred nicety.
- Bare-ID addressing needs a deterministic resolution policy (local → most-recently-active → explicit `id@node` override).
- The networking decision (ADR-0002) is now load-bearing for a flagship user feature, not just messaging — remote drive depends on it working well.
- Cross-node Psyche sync over P2P replaces the sister project's GitHub-repo sync, removing the `gh`/account/setup dependency.
- Remote command execution and remote instantiation are the highest-risk capabilities in the system; both are gated behind a consent/security model whose design is deferred but whose existence is assumed by the architecture.


Written. Three borderline scope calls remain from your captures — already wrote my best guesses into CONTEXT, want explicit confirms since they're scope:

**Q9 — Borderline v1 scope (the new captures).**

1. **Cross-node Psyche sync (the sync itself, not just transport).** Replaces gh-repo-sync entirely. Recommend **v1** — it's the flagship "look what baking networking in buys you" feature, kills the `gh`+account+setup dependency, and the context-freshness rule depends on it. *v1?*

2. **Cross-instance context-freshness auto-feed** (reactivated instance pulls newest-if-newer context from peers). Recommend **v1 if (1) is v1** — once sync + registry + per-instance context versioning exist, the rule is a small policy on top. Cheap given its deps. *v1, contingent on (1)?*

3. **Off-node file transfer** (agent ↔ user's driving node). Recommend **v1** — only dep is the binary-payload substrate seam (already required for Shells), and it's directly useful with remote-drive (V1-mid). *v1?*

4. **Remote command execution** on the user's node. Recommend **deferred + hard security gate** — shares the consent model with instantiate-anywhere; arbitrary cross-node exec is the single highest-risk capability. Don't let it gate v1; design the gate alongside instantiate-anywhere's consent. *deferred?*

Confirm 1–4, or adjust.


1. yes, v1 2. also v1 3. yes v1 4. ok defer. if the user really wants commands run on their node, they can either A. have the remote-node agent message a local-node agent to do it, or B. interact with a local-node agent directly.


All locked. Updating scope line + remote-exec rationale.


Edit

- **detect** that it is being driven remotely (and from which node) — observable from the `spt-daemon`, which knows whether the driving session-surface stream is local or network-attached;
- **transfer files** to/from the user's (driving) node — file payloads over the messaging substrate;
- **run commands** on the user's node — remote command execution, **security-gated** (shares the consent model with instantiate-anywhere; arbitrary cross-node exec is the highest-risk capability in the system).

- **detect** that it is being driven remotely (and from which node) — observable from the `spt-daemon`, which knows whether the driving session-surface stream is local or network-attached; (v1)
- **transfer files** to/from the user's (driving) node — file payloads over the messaging substrate; (v1)
- **run commands** on the user's node — remote command execution, **deferred + security-gated** (shares the consent model with instantiate-anywhere; arbitrary cross-node exec is the highest-risk capability in the system). Deferred partly because workarounds exist: the remote-node agent can message a local-node agent to run the command, or the user can interact with a local-node agent directly. (deferred)


1. yes, v1 2. also v1 3. yes v1 4. ok defer. if the user really wants commands run on their node, they can either A. have the remote-node agent message a local-node agent to do it, or B. interact with…


- **detect** that it is being driven remotely (and from which node) — observable from the `spt-daemon`, which knows whether the driving session-surface stream is local or network-attached;
- **transfer files** to/from the user's (driving) node — file payloads over the messaging substrate;
- **run commands** on the user's node — remote command execution, **security-gated** (shares the consent model with instantiate-anywhere; arbitrary cross-node exec is the highest-risk capability in the system).

- **detect** that it is being driven remotely (and from which node) — observable from the `spt-daemon`, which knows whether the driving session-surface stream is local or network-attached; (v1)
- **transfer files** to/from the user's (driving) node — file payloads over the messaging substrate; (v1)
- **run commands** on the user's node — remote command execution, **deferred + security-gated** (shares the consent model with instantiate-anywhere; arbitrary cross-node exec is the highest-risk capability in the system). Deferred partly because workarounds exist: the remote-node agent can message a local-node agent to run the command, or the user can interact with a local-node agent directly. (deferred)

When the user is driving an instance from off-node, the agent should be able to:

-- **detect** that it is being driven remotely (and from which node) — observable from the `spt-daemon`, which knows whether the driving session-surface stream is local or network-attached;

-- **transfer files** to/from the user's (driving) node — file payloads over the messaging substrate;

-- **run commands** on the user's node — remote command execution, **security-gated** (shares the consent model with instantiate-anywhere; arbitrary cross-node exec is the highest-risk capability in the system).

+- **detect** that it is being driven remotely (and from which node) — observable from the `spt-daemon`, which knows whether the driving session-surface stream is local or network-attached; (v1)

+- **transfer files** to/from the user's (driving) node — file payloads over the messaging substrate; (v1)

+- **run commands** on the user's node — remote command execution, **deferred + security-gated** (shares the consent model with instantiate-anywhere; arbitrary cross-node exec is the highest-risk capability in the system). Deferred partly because workarounds exist: the remote-node agent can message a local-node agent to run the command, or the user can interact with a local-node agent directly. (deferred)


- **In v1:** the endpoint-ID / instance data-model split; the subnet registry; resolution policy; remote-drive of *already-running* instances (network session-surface); cross-node Psyche sync transport.
- **Seam-compatible but deferred:** instantiate-anywhere + its consent gate; remote command execution + its security gate; the cross-instance context-freshness *rule* (transport is v1, the auto-feed policy may follow); off-node file transfer (depends only on the binary-payload seam — may land in v1 if cheap).

- **In v1:** the endpoint-ID / instance data-model split; the subnet registry; resolution policy; remote-drive of *already-running* instances (network session-surface); cross-node Psyche sync (full — replaces gh-repo-sync); cross-instance context-freshness auto-feed; off-node file transfer; off-node remote-drive detection.
- **Seam-compatible but deferred:** instantiate-anywhere + its consent gate; remote command execution + its security gate (workarounds: remote agent messages a local agent, or user interacts with a local agent directly).


- **In v1:** the endpoint-ID / instance data-model split; the subnet registry; resolution policy; remote-drive of *already-running* instances (network session-surface); cross-node Psyche sync transport.
- **Seam-compatible but deferred:** instantiate-anywhere + its consent gate; remote command execution + its security gate; the cross-instance context-freshness *rule* (transport is v1, the auto-feed policy may follow); off-node file transfer (depends only on the binary-payload seam — may land in v1 if cheap).

- **In v1:** the endpoint-ID / instance data-model split; the subnet registry; resolution policy; remote-drive of *already-running* instances (network session-surface); cross-node Psyche sync (full — replaces gh-repo-sync); cross-instance context-freshness auto-feed; off-node file transfer; off-node remote-drive detection.
- **Seam-compatible but deferred:** instantiate-anywhere + its consent gate; remote command execution + its security gate (workarounds: remote agent messages a local agent, or user interacts with a local agent directly).

-- **In v1:** the endpoint-ID / instance data-model split; the subnet registry; resolution policy; remote-drive of *already-running* instances (network session-surface); cross-node Psyche sync transport.

-- **Seam-compatible but deferred:** instantiate-anywhere + its consent gate; remote command execution + its security gate; the cross-instance context-freshness *rule* (transport is v1, the auto-feed policy may follow); off-node file transfer (depends only on the binary-payload seam — may land in v1 if cheap).

+- **In v1:** the endpoint-ID / instance data-model split; the subnet registry; resolution policy; remote-drive of *already-running* instances (network session-surface); cross-node Psyche sync (full — replaces gh-repo-sync); cross-instance context-freshness auto-feed; off-node file transfer; off-node remote-drive detection.

+- **Seam-compatible but deferred:** instantiate-anywhere + its consent gate; remote command execution + its security gate (workarounds: remote agent messages a local agent, or user interacts with a local agent directly).

- **Rationale:** the data-model split and registry are foundations — deferring them forces a later rewrite, so they are in regardless. Remote-drive of a running instance is the highest-value slice and exercises the whole networking+daemon stack end-to-end. Instantiate-anywhere and remote-exec carry consent/security design that should not gate v1.


| OS-level input-activity presence signal (7a-A) | presence v1 | Per-platform, privacy-loaded; agent-interaction heartbeat covers the v1 goal | Opt-in enhancement after agent-interaction presence proves out |

| OS-level input-activity presence signal (7a-A) | presence v1 | Per-platform, privacy-loaded; agent-interaction heartbeat covers the v1 goal | Opt-in enhancement after agent-interaction presence proves out |
| Instantiate-anywhere + consent gate | Instances V1-mid | Remote launch over network is additive once remote-drive of running instances works; consent UX is its own design | After remote-drive ships and consent model is designed |
| Remote command execution on another node | off-node reach-back v1 | Highest-risk capability; needs the consent/security gate. Workarounds exist (message a local agent; interact with local agent directly) | Designed alongside instantiate-anywhere's consent gate |


| OS-level input-activity presence signal (7a-A) | presence v1 | Per-platform, privacy-loaded; agent-interaction heartbeat covers the v1 goal | Opt-in enhancement after agent-interaction presence proves out |
| Instantiate-anywhere + consent gate | Instances V1-mid | Remote launch over network is additive once remote-drive of running instances works; consent UX is its own design | After remote-drive ships and consent model is designed |
| Remote command execution on another node | off-node reach-back v1 | Highest-risk capability; needs the consent/security gate. Workarounds exist (message a local agent; interact with local agent directly) | Designed alongside instantiate-anywhere's consent gate |

# Deferred Features

Intended features explicitly cut from v1 scope but committed for later. Not backlog noise — these are decided directions parked for sequencing reasons. Keep this list short and real.

| Feature | Cut from | Why deferred | Trigger to revisit |
|---|---|---|---|
| Scrollback on-disk spillover | terminal wrapper v1 | In-memory ring covers the common case; spillover adds a persistence/rotation story | First long-running session that overflows the ring usefully, or any "scroll back further than the buffer" user need |
| Sidecar adapter process (long-running, wire-protocol) | harness contract v1 | Manifest + `spt.exe` subcommand surface covers v1 harnesses; sidecar only earns its keep for streaming / in-memory cross-event state | A harness outgrows manifest+hooks (needs streaming or stateful coordination) |
| Manifest `include` / composition (option C) | manifest v1 | Single flat manifest per harness is enough until a harness wants to override only part of a section | First sub-project that needs to inherit spt-core defaults but override one section |
| Concrete Shell types (GameRobot, OS-notification, in-session-inject) | Shell concept v1 | Model is locked; concrete surfaces are large, platform-specific, and not needed for core | First real use case for a driven surface (e.g. the 2D-world experiment) |
| PresenceChannel implementation (broker, dispatch/bind/thread) | endpoint types v1 | Depends on presence gossip + Shells + multi-instance routing; only the seams ship v1 | Once Shells exist and the subnet registry is stable |
| Presence gossip (`last_active_node/endpoint/timestamp` across subnet) | networking v1 | Needs the multi-instance registry first; agent-interaction heartbeat (7a-B) is the chosen signal | When PresenceChannel work begins |
| OS-level input-activity presence signal (7a-A) | presence v1 | Per-platform, privacy-loaded; agent-interaction heartbeat covers the v1 goal | Opt-in enhancement after agent-interaction presence proves out |

+| Instantiate-anywhere + consent gate | Instances V1-mid | Remote launch over network is additive once remote-drive of running instances works; consent UX is its own design | After remote-drive ships and consent model is designed |

+| Remote command execution on another node | off-node reach-back v1 | Highest-risk capability; needs the consent/security gate. Workarounds exist (message a local agent; interact with local agent directly) | Designed alongside instantiate-anywhere's consent gate |


Self-update is a named day-one pillar, untouched, and tangles with the always-on daemon. Q10.

---

**Q10 — Seamless realtime self-updating.**

Sister project has "binary handoff" — running process spawns the new binary as a child, relays stdio, new process takes over; Psyche wrappers rehydrate from `wrapper-state.json`. You want spt-core's version "seamless realtime." Several decisions:

**10a — Update *delivery*: how does a node learn an update exists + get bytes?**
- (A) Out-of-band (today's model) — harness/plugin manager (cplugs marketplace) drops new binary on disk; spt-core just detects + hands off. spt-core doesn't fetch anything.
- (B) Self-fetch — spt-daemon polls a release channel (GitHub releases, or a URL), downloads, verifies signature, hands off. Networking already present (B1).
- (C) **Peer-propagated** — once one node on your subnet updates, it offers the new binary to your other nodes over the P2P layer. No per-node download; the subnet self-heals to latest. Out-of-band OR self-fetch seeds the first node.

**Recommend (C) layered on (B), with (A) still supported.** "Realtime, seamless" + "every node latest" + you already have P2P = peer propagation is the standout feature here. One node gets the update (via marketplace drop *or* self-fetch), gossips availability, peers pull over the subnet. (A) remains the floor for harness-managed installs. Big sub-question: **binary trust** — peer-propagated binaries must be signature-verified or a compromised node poisons the subnet. Need a signing key. Recommend: spt-core releases are signed; nodes verify before handoff regardless of source. (Ties to the Windows-codesigning open-Q in the research brief, but that's OS-publisher trust — separate from spt's own release signature.)

**10b — Handoff scope: what self-migrates?** Sister handed off poll listeners + Psyche wrappers. Now there's also the always-on spt-daemon (PTYs + network endpoint + registry). Hardest case: **handing off the daemon without dropping live PTY sessions or network connections.**
- (A) Full live handoff — new daemon inherits PTY file descriptors + open sockets via FD-passing (Unix `SCM_RIGHTS`; Windows handle duplication), zero session interruption.
- (B) Drain + restart — daemon stops accepting new, finishes/suspends sessions, restarts on new binary, sessions resume from persisted state. Brief interruption.
- (C) Staged — non-daemon processes (sessions' own listeners) do live handoff (A-style); the daemon does drain+restart (B) but fast, with sessions reconnecting.

**Recommend (B) for v1, (A) as a goal.** FD-passing across a binary swap — especially ConPTY handles on Windows — is genuinely hard and platform-divergent. Drain+restart with robust state persistence (`daemon-state.json` heir to `wrapper-state.json`) is achievable and correct; sessions reconnect their session-surface streams after the ~sub-second restart. Live FD-passing is a polish milestone. The PTY ring-buffer + session-surface abstraction means a reconnect is invisible-ish (scrollback preserved in state).

**10c — Update *cadence/consent*: who decides when?**
- (A) Auto-apply immediately on detection (most "seamless," least control).
- (B) Auto-apply at a safe boundary (session idle, between turns) — seamless but non-disruptive.
- (C) User-gated (prompt before applying).

**Recommend (B).** Matches "seamless realtime" without yanking the rug mid-operation. Apply at the next idle boundary per process. Sister project's binary-handoff already aims at invisibility; (B) preserves that. Offer a config to force (C) for cautious users.

Confirm 10a (C-on-B, +signing), 10b (B for v1 / A goal), 10c (B)? Adjust any?


10a. agree with recommendation. note that the plugin shape changes a lot --> it's only responsible for initial spt-core setup. from then on spt.exe self-updates, and maybe the adapter manifests for i.e. spt-plugin, spt-codex, spt-pi should all expose avenues for spt-core to ripple-update those as well (i.e. they cite a plugin directory lookup regex + gh repo for the adapter's latest files, or else they cite a binary command for update like `claude.exe plugin update`) --- 10b. some notes. -for spt-plugin, psyche and echo communes (driven by psyche wrapper today) will need to transition away from `claude --p` flag due to imminent changes in claude code billing. so they will need to run as headless `claude` (with `--resume` for psyche) sessions. -the update handoff needs to happen without terminating any endpoint binaries. we can't assume that every endpoint can safely suspend on a whim. --- 10c. self-updates shouldn't (by default) be fully automatic. should gate on user confirmation. update prompt should be delivered via spt to user's most recently active live session, offer a choice to enable full-auto