# Always-on endpoints: resident supervised adapter binaries, `#`-sigil addressed

Status: accepted (2026-06-21)

<!-- [doc->REQ-EP-8] [doc->REQ-EP-9] -->

## Context & decision

We needed a way for an adapter to bridge an external multi-party substrate — first case: a Discord "forum" where many agents converse in one channel — into the SPT subnet, *including* drawing an **offline** agent online when it is addressed there. The substrate itself must never enter spt-core (it is substrate-independent), so the core gains only a generic mechanism.

We introduce the **always-on endpoint**: a resident, addressable endpoint that hosts **no mind**, declared by an adapter's `[always-on]` manifest section. The daemon boot-launches and supervises **one such binary per registered adapter-option** (`<adapter>[:profile]`), running it continuously and **independent of any agent's liveness** (so it is up to receive the first interaction even when zero agents are online). The binary **self-manages its channel endpoints via the existing `api bind`** — one connection fronts many `#`-addressed endpoints, no new "create endpoint" command. It may call `endpoint wake <id>` to revive an offline target; authorization is **target-side** (`resolve_wake` / `forward_wake` — "the target's access gate decides" — plus the access whitelist and the `shell_wake_spawn_anywhere` pre-consent), so **no caller-ownership gate is added**.

Always-on endpoints are addressed with a **mandatory leading `#` sigil**, `[subnet:]#id[@node]`, **bijective** with the class (`#name` ⟺ always-on, bare `name` ⟺ agent endpoint) so the router resolves the endpoint class from the address alone, before any registry lookup.

## Considered options

- **Make it a Shell.** Rejected: Shells are single-owner, *driven*, and control-exclusive (`REQ-SHELL-5`). A multi-party bridge with N peer participants inverts every one of those invariants.
- **Generalize the shell wake-watcher (`shellwake.rs`) into a persistent binary.** Rejected: the watcher is intrinsically the *offline half* of a mutual-exclusivity flip — keyed `(owner, shell_id)`, gated on `SHELL_STATUS_OFFLINE`, killed when the thing comes online. An always-on, owner-less binary fights that design. We **reuse its supervision scaffolding** (backoff, give-up latch, one-per-instance lock, orphan-kill, brain-side reconcile) but as a **distinct kind**, not by overloading the watcher.
- **Faceless supervised service (no perch/identity).** Rejected: agents must address it two-way to drive the bridged channel, so it needs a real, addressable identity — not a faceless process.
- **Bake the substrate (Discord) into core / the daemon.** Rejected: spt-core is substrate-independent. Discord lives entirely in the downstream `spt-discord` adapter; core gains only the generic always-on mechanism.

## Consequences

- There are now **two** classes of third-party binary spt-core boot-launches (previously one — the shell wake-watcher). `CONTEXT.md` §Gateway revival updated from "the one class" accordingly.
- The `#` sigil is reserved at the **address-grammar layer only**; `REQ-HAZARD-ID-CHARSET` is **unchanged** — the parser strips a single leading `#` before id validation, so bare/stored ids stay charset-clean and a mid-id `#` is still rejected.
- An always-on binary is **least-trusted third-party code**, the same accepted-risk posture as shells and harness binaries (`CONTEXT.md` §binary-trust disclosure). The existing endpoint access whitelist and target-side wake gating apply unchanged.
- New tracked requirements `REQ-EP-8` (the kind + supervision) and `REQ-EP-9` (the `#` sigil), both **inactive** (`required_stages = []`) until the delivering milestone.
- All Discord-specific design (webhooks, per-agent threads, inbound routing, hybrid delivery, echo-loop prevention, secrets) is recorded in the sister repo **`../spt-discord/`**, deliberately not here.
