--- meta-description: spt-core: a harness-independent core for an agent ecosystem — messaging, live-agent lifecycle, terminal hosting, P2P networking, and the runtime-manifest harness contract. meta-theme-color: #ffffff meta-viewport: width=device-width, initial-scale=1 title: The spt api surface - SPT developer docs --- ## Keyboard shortcuts Press `←` or `→` to navigate between chapters Press `S` or `/` to search in the book Press `?` to show this help Press `Esc` to hide this help ![SVG Image](data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0NDggNTEyIj48cGF0aCBkPSJNMCA5NkMwIDc4LjMgMTQuMyA2NCAzMiA2NEg0MTZjMTcuNyAwIDMyIDE0LjMgMzIgMzJzLTE0LjMgMzItMzIgMzJIMzJDMTQuMyAxMjggMCAxMTMuNyAwIDk2ek0wIDI1NmMwLTE3LjcgMTQuMy0zMiAzMi0zMkg0MTZjMTcuNyAwIDMyIDE0LjMgMzIgMzJzLTE0LjMgMzItMzIgMzJIMzJjLTE3LjcgMC0zMi0xNC4zLTMyLTMyek00NDggNDE2YzAgMTcuNy0xNC4zIDMyLTMyIDMySDMyYy0xNy43IDAtMzItMTQuMy0zMi0zMnMxNC4zLTMyIDMyLTMySDQxNmMxNy43IDAgMzIgMTQuMyAzMiAzMnoiIC8+PC9zdmc+) ![SVG Image](data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1NzYgNTEyIj48cGF0aCBkPSJNMzcxLjMgMzY3LjFjMjcuMy0zLjkgNTEuOS0xOS40IDY3LjItNDIuOUw2MDAuMiA3NC4xYzEyLjYtMTkuNSA5LjQtNDUuMy03LjYtNjEuMlM1NDkuNy00LjQgNTMxLjEgOS42TDI5NC40IDE4Ny4yYy0yNCAxOC0zOC4yIDQ2LjEtMzguNCA3Ni4xTDM3MS4zIDM2Ny4xem0tMTkuNiAyNS40bC0xMTYtMTA0LjRDMTc1LjkgMjkwLjMgMTI4IDMzOS42IDEyOCA0MDBjMCAzLjkgLjIgNy44IC42IDExLjZjMS44IDE3LjUtMTAuMiAzNi40LTI3LjggMzYuNEg5NmMtMTcuNyAwLTMyIDE0LjMtMzIgMzJzMTQuMyAzMiAzMiAzMkgyNDBjNjEuOSAwIDExMi01MC4xIDExMi0xMTJjMC0yLjUtLjEtNS0uMi03LjV6IiAvPjwvc3ZnPg==) - Auto - Light - Rust - Coal - Navy - Ayu ![SVG Image](data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48cGF0aCBkPSJNNDE2IDIwOGMwIDQ1LjktMTQuOSA4OC4zLTQwIDEyMi43TDUwMi42IDQ1Ny40YzEyLjUgMTIuNSAxMi41IDMyLjggMCA0NS4zcy0zMi44IDEyLjUtNDUuMyAwTDMzMC43IDM3NmMtMzQuNCAyNS4yLTc2LjggNDAtMTIyLjcgNDBDOTMuMSA0MTYgMCAzMjIuOSAwIDIwOFM5My4xIDAgMjA4IDBTNDE2IDkzLjEgNDE2IDIwOHpNMjA4IDM1MmM3OS41IDAgMTQ0LTY0LjUgMTQ0LTE0NHMtNjQuNS0xNDQtMTQ0LTE0NFM2NCAxMjguNSA2NCAyMDhzNjQuNSAxNDQgMTQ0IDE0NHoiIC8+PC9zdmc+) # SPT developer docs [![SVG Image](data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48cGF0aCBkPSJNMTI4IDBDOTIuNyAwIDY0IDI4LjcgNjQgNjR2OTZoNjRWNjRIMzU0LjdMMzg0IDkzLjNWMTYwaDY0VjkzLjNjMC0xNy02LjctMzMuMy0xOC43LTQ1LjNMNDAwIDE4LjdDMzg4IDYuNyAzNzEuNyAwIDM1NC43IDBIMTI4ek0zODQgMzUydjMyIDY0SDEyOFYzODQgMzY4IDM1MkgzODR6bTY0IDMyaDMyYzE3LjcgMCAzMi0xNC4zIDMyLTMyVjI1NmMwLTM1LjMtMjguNy02NC02NC02NEg2NGMtMzUuMyAwLTY0IDI4LjctNjQgNjR2OTZjMCAxNy43IDE0LjMgMzIgMzIgMzJINjR2NjRjMCAzNS4zIDI4LjcgNjQgNjQgNjRIMzg0YzM1LjMgMCA2NC0yOC43IDY0LTY0VjM4NHptLTE2LTg4Yy0xMy4zIDAtMjQtMTAuNy0yNC0yNHMxMC43LTI0IDI0LTI0czI0IDEwLjcgMjQgMjRzLTEwLjcgMjQtMjQgMjR6IiAvPjwvc3ZnPg==)](../print.html "Print this book") [![SVG Image](data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0OTYgNTEyIj48cGF0aCBkPSJNMTY1LjkgMzk3LjRjMCAyLTIuMyAzLjYtNS4yIDMuNi0zLjMuMy01LjYtMS4zLTUuNi0zLjYgMC0yIDIuMy0zLjYgNS4yLTMuNiAzLS4zIDUuNiAxLjMgNS42IDMuNnptLTMxLjEtNC41Yy0uNyAyIDEuMyA0LjMgNC4zIDQuOSAyLjYgMSA1LjYgMCA2LjItMnMtMS4zLTQuMy00LjMtNS4yYy0yLjYtLjctNS41LjMtNi4yIDIuM3ptNDQuMi0xLjdjLTIuOS43LTQuOSAyLjYtNC42IDQuOS4zIDIgMi45IDMuMyA1LjkgMi42IDIuOS0uNyA0LjktMi42IDQuNi00LjYtLjMtMS45LTMtMy4yLTUuOS0yLjl6TTI0NC44IDhDMTA2LjEgOCAwIDExMy4zIDAgMjUyYzAgMTEwLjkgNjkuOCAyMDUuOCAxNjkuNSAyMzkuMiAxMi44IDIuMyAxNy4zLTUuNiAxNy4zLTEyLjEgMC02LjItLjMtNDAuNC0uMy02MS40IDAgMC03MCAxNS04NC43LTI5LjggMCAwLTExLjQtMjkuMS0yNy44LTM2LjYgMCAwLTIyLjktMTUuNyAxLjYtMTUuNCAwIDAgMjQuOSAyIDM4LjYgMjUuOCAyMS45IDM4LjYgNTguNiAyNy41IDcyLjkgMjAuOSAyLjMtMTYgOC44LTI3LjEgMTYtMzMuNy01NS45LTYuMi0xMTIuMy0xNC4zLTExMi4zLTExMC41IDAtMjcuNSA3LjYtNDEuMyAyMy42LTU4LjktMi42LTYuNS0xMS4xLTMzLjMgMi42LTY3LjkgMjAuOS02LjUgNjkgMjcgNjkgMjcgMjAtNS42IDQxLjUtOC41IDYyLjgtOC41czQyLjggMi45IDYyLjggOC41YzAgMCA0OC4xLTMzLjYgNjktMjcgMTMuNyAzNC43IDUuMiA2MS40IDIuNiA2Ny45IDE2IDE3LjcgMjUuOCAzMS41IDI1LjggNTguOSAwIDk2LjUtNTguOSAxMDQuMi0xMTQuOCAxMTAuNSA5LjIgNy45IDE3IDIyLjkgMTcgNDYuNCAwIDMzLjctLjMgNzUuNC0uMyA4My42IDAgNi41IDQuNiAxNC40IDE3LjMgMTIuMUM0MjguMiA0NTcuOCA0OTYgMzYyLjkgNDk2IDI1MiA0OTYgMTEzLjMgMzgzLjUgOCAyNDQuOCA4ek05Ny4yIDM1Mi45Yy0xLjMgMS0xIDMuMy43IDUuMiAxLjYgMS42IDMuOSAyLjMgNS4yIDEgMS4zLTEgMS0zLjMtLjctNS4yLTEuNi0xLjYtMy45LTIuMy01LjItMXptLTEwLjgtOC4xYy0uNyAxLjMuMyAyLjkgMi4zIDMuOSAxLjYgMSAzLjYuNyA0LjMtLjcuNy0xLjMtLjMtMi45LTIuMy0zLjktMi0uNi0zLjYtLjMtNC4zLjd6bTMyLjQgMzUuNmMtMS42IDEuMy0xIDQuMyAxLjMgNi4yIDIuMyAyLjMgNS4yIDIuNiA2LjUgMSAxLjMtMS4zLjctNC4zLTEuMy02LjItMi4yLTIuMy01LjItMi42LTYuNS0xem0tMTEuNC0xNC43Yy0xLjYgMS0xLjYgMy42IDAgNS45IDEuNiAyLjMgNC4zIDMuMyA1LjYgMi4zIDEuNi0xLjMgMS42LTMuOSAwLTYuMi0xLjQtMi4zLTQtMy4zLTUuNi0yeiIgLz48L3N2Zz4=)](https://github.com/SaberMage/spt-releases "Git repository") ![SVG Image](data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48cGF0aCBkPSJNMzA0IDQ4YzAtMjYuNS0yMS41LTQ4LTQ4LTQ4cy00OCAyMS41LTQ4IDQ4czIxLjUgNDggNDggNDhzNDgtMjEuNSA0OC00OHptMCA0MTZjMC0yNi41LTIxLjUtNDgtNDgtNDhzLTQ4IDIxLjUtNDggNDhzMjEuNSA0OCA0OCA0OHM0OC0yMS41IDQ4LTQ4ek00OCAzMDRjMjYuNSAwIDQ4LTIxLjUgNDgtNDhzLTIxLjUtNDgtNDgtNDhzLTQ4IDIxLjUtNDggNDhzMjEuNSA0OCA0OCA0OHptNDY0LTQ4YzAtMjYuNS0yMS41LTQ4LTQ4LTQ4cy00OCAyMS41LTQ4IDQ4czIxLjUgNDggNDggNDhzNDgtMjEuNSA0OC00OHpNMTQyLjkgNDM3YzE4LjctMTguNyAxOC43LTQ5LjEgMC02Ny45cy00OS4xLTE4LjctNjcuOSAwcy0xOC43IDQ5LjEgMCA2Ny45czQ5LjEgMTguNyA2Ny45IDB6bTAtMjk0LjJjMTguNy0xOC43IDE4LjctNDkuMSAwLTY3LjlTOTMuNyA1Ni4yIDc1IDc1cy0xOC43IDQ5LjEgMCA2Ny45czQ5LjEgMTguNyA2Ny45IDB6TTM2OS4xIDQzN2MxOC43IDE4LjcgNDkuMSAxOC43IDY3LjkgMHMxOC43LTQ5LjEgMC02Ny45cy00OS4xLTE4LjctNjcuOSAwcy0xOC43IDQ5LjEgMCA2Ny45eiIgLz48L3N2Zz4=) # [The `spt api` surface](#the-spt-api-surface) The imperative half of the harness contract: the inbound entry points a harness’s hooks (and a shell’s binary) fire to keep spt-core’s on-disk state in sync. This page is the complete command reference plus the two startup flows that tie it together. Three rules apply to `api` calls: 1. **`--adapter ` is an optional override** (since v0.9.0). For a harness-hosted session you normally **omit it**: `listen` resolves the owning adapter/profile at bind, from the seed’s parent pid → the harness exe basename → the adapter(s) that declare it in [`[adapter] host_binaries`](manifest.html) → the active-profile pointer (set by [`spt adapter use`](../cli/reference.html)) or, with no pointer, the freshest-registered hosting adapter. Pass `--adapter` only to **pin** a specific adapter/profile (adapter dev, or explicit disambiguation). The profile qualifier `:` is **runtime selection** — retained onto the perch record, and the daemon resolves the profile **overlay** when it later spawns the session’s lifecycle roles. So a `live` profile whose `[session.psyche_init]` is in the resolved manifest is a **LiveAgent** (spt-core drives the Psyche as a bounded per-event turn); a profile without it is a **ReadyAgent**. Ready-vs-live is a profile choice, not a separate “go-live” verb. 2. **Prove association.** Commands that touch an existing perch take `--session-id ` (matching the perch’s record) or a capability `--token`; shell commands authenticate with `--link ` (the link token minted at launch *is* the credential — no token, no access). This includes the read-side drain: an unauthenticated `api poll` is **refused** (exit 1, nothing printed) — see [`api poll`](#api-poll-id---include-deferred---link-token). 3. **Status rides stderr; stdout is payload; the exit code is authoritative.** Action-command status lines (`BOUND: token=…`, `READY:`, `SENT:`, `QUEUED:`, `WORKER_STARTED:…`, failure tags) print to **stderr** — always, piped or not (only the *color* is tty-gated; a redirect gets the bare tag unchanged). **stdout** is reserved for machine payloads: `--json` output, polled message frames, and documented payload emissions. A program shelling out must therefore capture **stderr** to read a status tag (`2>&1`, or capture the streams separately) — discarding stderr (`2>$null` / `2>/dev/null`) discards the status line by design — and should treat the **exit code** as the success contract: `0` = the action took effect, non-zero = it did not. spt api [--adapter ] [--manifest ] … `--manifest` points at the adapter’s manifest for the commands that need it (e.g. `capability`). ## [The two startup flows](#the-two-startup-flows) **Harness-hosted** — the harness owns the process; spt-core is invoked from inside it (hooks): SessionStart hook ──► api seed --pid {parent_pid} --session-id {session_id} session's listener ──► api listen (consumes the seed, holds the perch) `seed` records an ephemeral hand-off keyed by parent pid; `listen` consumes it, registers the perch, drains backlog, and blocks relaying events into the session. **spt-hosted** — spt-core spawns the session itself from the manifest’s `[session.self]` template, in its own terminal layer: spt-core spawns the template ──► session comes up session (or its wrapper) ──► api bind --set-session-id No seed file is involved; `bind` attaches the live session to its perch post-spawn. **Going ONLINE (the presence badge).** The `endpoint list` ONLINE badge means one thing: a live process is **holding the relay** — an `api listen ` that consumed a seed and is blocked relaying events. Binding alone does not light it. A headless adapter binary (a gateway or any `[session.self]` host that wants ONLINE presence and a live event stream) uses the same two-step the harness-hosted flow does, against its **own** process: api seed --pid --session-id (hand-off keyed to itself) api listen (consume, hold the perch, stay ONLINE) Drop the listener and the endpoint decays to Dormant/Offline as its last-seen ages out. ## [Session lifecycle](#session-lifecycle) ### [`api seed --pid --session-id `](#api-seed---pid-pid---session-id-id) Harness-hosted startup, step 1: record an ephemeral seed keyed by the parent process id. Fired by the harness’s session-start hook. Prints `SEEDED:`. **Seed lifetime.** The seed lives **in the daemon’s memory only** — no file — and survives until exactly one of: a successful `listen` bind consumes it, a newer `seed` for the same pid overwrites it, or the daemon process restarts (which drops the whole map). Nothing re-fires it until the harness’s **next** SessionStart. So an adapter must not rely on the seed for a session that goes live late (hours after SessionStart) or after a daemon restart — that is what `listen --session-id` (below) is for. ### [`api listen [--once] [--parent-pid ] [--subnet ] [--session-id ]`](#api-listen-id---once---parent-pid-pid---subnet-name---session-id-sid) Harness-hosted startup, step 2: consume the seed, register/hold the perch, drain spooled backlog, then block relaying messages. `--once` runs a single drain+receive cycle (testing). `--subnet` names the home subnet when this creates a brand-new endpoint on a multi-subnet node (home is assigned deterministically at creation). **Recoverable refusals do not consume the seed.** The seed is consumed by a **successful bind** — or by a refusal that proves the seed itself dead (see spend-vs-restore below). A recoverable refusal that never bound — `HOME_REFUSED` on a multi-subnet node without `--subnet`, `ADAPTER_UNRESOLVED`, a live-perch conflict — leaves the seed consumable, so the corrected retry on the same pid binds instead of dead-ending on `NO_SEED`. (Effect before irreversible consume: the destructive step follows the successful effect, never a recoverable refusal.) **`--session-id ` — binding when the seed is gone.** A session that goes live late, or after a daemon restart, finds no live seed; with `--session-id` the listener binds directly from the given harness session id (loud `SID_BIND:` marker). The fallback fires **only** on `NO_SEED` — every other refusal keeps its own diagnostic — and carries the same identity/auth gates as a seeded bind (live-conflict refusal, dead-anchor refusal on the parent pid). Without a live seed **and** without `--session-id`, `listen` refuses with `NO_SEED`. > **Provenance caveat — same gates, weaker provenance.** A seed is a > consume-once capability minted by the harness for exactly one anchor pid; `--session-id` is a **bearer string** — any local caller who knows a live > session id can present it and revive that perch. Treat session ids as > secrets: never log or publish them. (The local surface already trusts local > callers — `--parent-pid` is an override — so this is a contract > qualification for adapter authors, not a sandbox.) **Refusals and the seed, spend vs restore.** A refusal that proves the seed itself dead — a stale (dead-pid) anchor, an empty session id — **spends** it: that seed can never retry as itself, and restoring it would re-arm a dead-keyed seed for a recycled pid to steal. Recoverable refusals (the `HOME_REFUSED` retry case above, a live-perch conflict) restore it. ### [`api bind [--set-session-id ]`](#api-bind-id---set-session-id-sid) spt-hosted startup: bind a freshly spawned session to its perch, recording the session id discovered post-spawn. Identity precedes sessions — rebinding never mints a new endpoint. **Auth is intrinsic — `bind` takes no association proof.** It is an *establishing* call (the exception to Rule 2), not a touch-an-existing-perch call: spt-core spawned this session into its own broker-held terminal layer, so that parentage *is* the credential. The only guard is ownership — an existing *live* perch under a different session id is refused (you can only bind your own). The broker injects **no** capability token into the spawned environment, so there is nothing to echo back and no `[env.*]` entry to author for one; the endpoint id arrives via the `{id}` fill in `[session.self]`, and that is the only identity spt-core plants. `--set-session-id` *records* the discovered id into the perch — it is not a proof. `bind` prints `BOUND: token=`. The token is a freshly minted local credential the session *may* retain for later authenticated calls, but it is optional: every subsequent mutating call can instead prove association the Rule 2 way, passing `--session-id ` for spt-core to match against the record this bind wrote. ### [`api boundary --to-session-id --session-id `](#api-boundary-clearcompact-id---to-session-id-new-sid---session-id-prior-sid) The session was reset (context cleared or compacted) and continues under a new session id: rebind the perch, preserving the endpoint’s identity, spool, and history across the boundary. **The rotation catch-22 — read this before wiring the hook.** Rule 2 applies to `boundary` like every mutating verb, but here the “matching” `--session-id` is the sid on the perch record — i.e. the session being **departed**, not the one you are rotating to. `--to-session-id` is the *payload*, never the *proof*. By the time your rotation hook fires, the old session’s context (its env, its per-session files) is typically gone and the hook payload carries only the new sid — so a hook that only knows “the current sid” cannot authenticate this one verb. Two hard requirements for adapter authors: 1. **Persist the current session id at every SessionStart** in adapter-owned state keyed by the *endpoint id* (reference pattern: `{adapter_dir}/state/session/.sid`, single line, rewritten each SessionStart). At clear/compact, read it back as the **prior** sid and pass it as `--session-id`. Do **not** persist it in a per-session env file — that file dies with the session, which is the catch-22 itself. 2. **Never skip or swallow this call.** A rotation that is silently skipped (unresolvable endpoint id) or silently refused (`AUTH_REFUSED` on stderr, exit 1) leaves the perch pinned to the dead sid — after which **every** id-scoped call from the live session refuses, including a boundary retry, and the endpoint strands with zero message delivery until a full relaunch. Surface the id-resolution failure and the refusal reason loudly in your hook output. Resolve the endpoint id from your stable identity (`$SPT_ENDPOINT_ID`), not by looking up the new sid (it is not registered yet — the same catch-22). A perch stranded by a *crashed* session (recorded pid dead) self-heals on the next call via the dead-owner re-pin; a **live** session’s rotation always requires the prior-sid (or `--token`) proof. The long-term design (ADR-0032) adds an OS-verified ancestry proof keyed on the endpoint’s stable `parent_pid` anchor, which will make the persisted-sid pattern optional; until then it is required. ### [`api psyche-download [--session-id ]`](#api-psyche-download-id---session-id-sid) Pull the agent’s **resume context** to stdout, for a SessionStart hook to inject as the session’s additional context after a `/clear`, `/compact`, or fresh resume. Emits the durable two-tier mind — the agent’s role, its cross-project live context, and the current project’s context — **plus** any commune/signoff drop that has been written but **not yet synthesized** into that durable context (as `` / `` slices), so a just-written delta is never invisible on resume. The project is resolved from the perch’s recorded cwd. Read-only — it never writes the mind store. Prints `NO-CONTEXT:` on stderr (exit 0) when nothing is stored yet, the adapter’s fresh-init signal. > The *read-back-in* half of the commune/signoff file-drops (the *write* side): the agent drops its delta, spt-core synthesizes it into the durable > mind, and `psyche-download` is how the next session reads that mind back in. > Wire it into your SessionStart hook alongside `seed`/`listen`, and inject > its stdout — that is how a resumed session keeps its accumulated context. ### [`api session-end [--erase]`](#api-session-end-id---erase) Soft teardown: the session is over; the perch’s spool and history are preserved (that’s what makes the next `poll`/`listen` drain work). `--erase` hard-wipes instead — the exception, not the rule. ### [`api shutdown `](#api-shutdown-id) Graceful live-agent signoff: runs the final echo-commune **before** teardown (the context delta is never lost to ordering), then soft-stops. This is what the `spt endpoint shutdown` lifecycle path calls. ## [Activity and presence](#activity-and-presence) ### [`api state [--no-gate]`](#api-state-busyidle-id---no-gate) Report the session’s activity state. Activity/idleness comes from these explicit reports — **never** from terminal quiescence, which lies. Reporting `idle` also arms the echo gate (below) unless `--no-gate`. ### [`api echo-gate `](#api-echo-gate-setclear-id) Manage the echo-gate sentinel directly. The gate marks “a summarization may be needed when this session ends without a graceful signoff” — `state idle` sets it as a side effect; a graceful signoff clears it. ### [`api presence `](#api-presence-id) Report user/agent presence at this endpoint (feeds most-recently-active resolution across the subnet). ### [`api driven-by `](#api-driven-by-id) Print which node (if any) is currently remote-driving this endpoint, so a session can tell whether input is local or remote. ## [Messages](#messages) ### [`api poll [--include-deferred] [--link ]`](#api-poll-id---include-deferred---link-token) Drain delivered messages over the hook channel (the pull-based path for harnesses whose hooks can’t inject). Deferred-flagged rows are excluded unless `--include-deferred`. With `--link` this is the shell-flavored drain: the link token authenticates, and the rows are the shell’s stamped command/text/file frames. **Authentication is required** (rule 2 above): the drain must prove association with `--session-id ` (the perch’s recorded session) or a capability `--token` (`--link ` for the shell flavor). An unauthenticated `poll` is refused with **exit 1 and no output** — messages are addressed to the endpoint’s occupant, not to whoever asks. ### [`api history-log `](#api-history-log-id) Append normalized history (body on stdin) to the endpoint’s native history store — the push half of `[history] strategy = "native"`. ## [Workers](#workers) Nested, short-lived agents under a parent endpoint. A worker is process-local machinery — it authenticates with its parent’s session id and carries no capability token of its own. ### [`api worker-start [--agent-id ] [--agent-type ]`](#api-worker-start-parent---agent-id-id---agent-type-type) Create a nested worker perch under `parent`. The worker id is **minted by spt-core**, not supplied by the caller: `{parent}-w{N}` with a persistent, per-parent counter. The caller does **not** pass an id (a stray positional id is rejected). Output channels follow the api status-line discipline: - **stdout** carries the bare minted id and nothing else (the machine-readable result — empty on any refusal). Read this to learn the worker’s id. - **stderr** carries the human line `WORKER_STARTED:{parent}-w{N} under {parent}`. `--agent-id` / `--agent-type` are optional: the caller’s own agent identifiers, recorded on the worker as **correlation metadata only** — never the perch identity. Authenticates against the **parent** (the parent’s session id or token); the worker record stores the parent’s current session id as its registration sid. ### [`api worker-stop --session-id ` · `api worker-poll --session-id `](#api-worker-stop-id---session-id-sid--api-worker-poll-id---session-id-sid) Soft-stop (drop the ready marker; info + spool preserved) or drain a worker. Both authenticate **symmetrically by session id** — no token. A presented sid is accepted when it matches **either** the worker’s stored registration sid **or** the parent’s *current* session id, so a context clear/compact that rotates the parent’s sid between start and stop does not lock the worker out. The natural call `worker-stop --session-id ` is therefore correct as-is. ## [Shells](#shells) The driven-surface flavor of the contract. The **link token** minted at launch is the only credential a shell binary ever holds or needs: ### [`api bind-shell --link `](#api-bind-shell---link-token) The shell binary’s first call: resolve the instance **by link token alone** (the spawn template carries only `{link_token}`; the owner is derived from the link) and flip it online. ### [`api emit --type --link `](#api-emit-id-payload---type-type---link-token) Push a sensory payload (one of the manifest’s declared `[shell.sensory]` types) to the owner’s **live** session. REST-only by definition: never spooled — if the owner isn’t live, it’s dropped with a diagnostic. Sensors report the present, not the past. ### [`api owner-shutdown --link `](#api-owner-shutdown-id---link-token) A shell suspends its linked owner directly (e.g. a power-button surface), bypassing agent messaging. Gated by the manifest’s `can_shutdown` pre-consent flag — fail-closed; an undeclared shell gets a refusal. The firing shell cascades offline with its siblings, by design. ## [Introspection](#introspection) ### [`api capability`](#api-capability) Print the adapter’s declared `hostable_types` (requires `--manifest`). The cheap way to smoke-test that spt-core reads your manifest the way you meant it. ## [Conventions](#conventions) - **Output is line-oriented and stable**: `SEEDED:`, `READY:`, `SENT:`, `QUEUED:`, error lines as `CODE:detail`. Parse lines, not prose. - **Exit codes**: `0` success; non-zero = refused or failed, with the reason on stderr. - **Commune/signoff are file-drops, not api commands.** An agent writes `-commune.md` / `-signoff.md` into the manifest’s watched directory; spt-core’s watcher ingests it. There is deliberately no `api commune`.