# Shells

A **shell** is the non-agent endpoint kind: a *driven surface*. Notifiers,
robots, lamps, game characters, sensor feeds — anything an agent should be
able to command, and that might sense things back. Shells join the same
network as agents: addressable, discoverable, owned.

## The model in five facts

1. **A shell adapter declares it; instances are minted.** The
   `kind = "shell"` [manifest](../harness-contract/manifest.md#shell-adapters-kind--shell)
   declares the binary, its command vocabulary (`[shell.capabilities]`), and
   its sensory vocabulary (`[shell.sensory]`). `spt shell spawn <adapter>`
   mints a new instance (`notify-1`) — spawn is the creation act, not an
   on/off switch; bringing an existing instance back is relink/wake.
2. **The link token is the credential.** The broker mints a per-launch link
   token into the spawn template; the binary binds with it
   (`api bind-shell --link`), drains commands with it, emits with it. No
   token, no access.
3. **Commands are vocabulary-checked and durable.** `spt shell cmd notify-1
   notify "title" "body"` is validated against the manifest's declared verbs
   and arity before delivery — agents can't drive a shell outside its
   contract. Commands are discrete and durable: they spool and a persistent
   shell wakes to drain them.
4. **Sensory is live-only.** `api emit` payloads reach a *live* owner
   session or are dropped with a diagnostic — sensors report the present,
   never the past.
5. **Instantiation is governed.** Per-spawn approval
   (`require_approval: none / remembered / always`), per-owner instance caps
   (`max_instances_per_owner` + `over_cap`), and node-local discovery scope
   (`broadcast`) are all manifest-declared floors.

## Four channels between owner and shell

A link can carry up to four distinct channels — each with its own delivery
contract, all keyed to the same link token:

- **Command** (owner→shell, durable): the vocabulary-checked verbs above —
  discrete, spooled, replayed to a waking persistent shell.
- **Sensory** (shell→owner, live-only): `[shell.sensory]` emits to a live owner
  or drops with a diagnostic.
- **Drive** (owner→shell, ephemeral): `[shell.drive]` + `spt shell drive` — a
  continuous control channel for real-time input (scroll, stick, avatar pose).
  **Latest-wins, never spooled**: a newer frame supersedes an undelivered one,
  and an offline shell drops the frame (no queue, no wake, no replay). Use it
  for *continuous* control; use commands for *discrete, must-arrive* actions.
- **Tunnel** (owner↔shell, opaque bytes): `[shell.tunnel]` + `spt shell tunnel`
  — an optional reliable-ordered byte stream pair the taxonomy never
  interprets (first consumer: usbip URB). Not enveloped, not framed, not
  spooled; the link lifecycle governs it (a link-break closes it). Reliable
  ordering means congestion surfaces as **lag, never loss** — so the tunnel is
  **on-LAN only** by design (not for use across a WAN). Its byte relay is
  proven same-node; cross-node operation is on-LAN only and exercised on the
  two-host rig.

## Two safety properties

- **Per-capability approval gates.** Beyond the per-*spawn* gate, an individual
  `[shell.capabilities.<verb>]` may carry its own `require_approval` (with an
  optional `class_key` scoping the grant finer than the verb — e.g. a remembered
  HID-class attach never authorizes a storage-class attach). Spawn gates govern
  whether an instance may *exist*; capability gates govern whether a dangerous
  *act* may run.
- **Ownership is owner-type-agnostic.** Any non-shell endpoint may own, spawn,
  drive, command, link, and tunnel a shell — a Gateway as readily as an agent.
  Control-exclusivity keys on the **owner's endpoint id**, never its type: a
  different endpoint (even of the same type) cannot drive your shell.

Lifecycle extras: `persistent` shells auto-online with their owner;
`wake_command` runs a watcher while offline (exit code 86 = wake); a shell
with `can_shutdown = true` may suspend its own owner (`api owner-shutdown`)
— fail-closed otherwise.

## Start here

[Getting started: a notification shell](getting-started.md) — install the
shipping `spt-shell-notify` adapter, drive a native toast from an agent, and
copy its manifest for your own surface.
