# 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.

Two rules apply to `api` calls:

1. **`--adapter <name[:profile]>` 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.md) → the
   active-profile pointer (set by [`spt adapter use`](../cli/reference.md)) 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 `<adapter>:<profile>` 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
   runs the Psyche companion); 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 <id>` (matching the perch's record) or a capability
   `--token`; shell commands authenticate with `--link <token>` (the link
   token minted at launch *is* the credential — no token, no access).

<!-- [doc->REQ-START-5] -->
```text
spt api [--adapter <name[:profile]>] [--manifest <path>] <command> …
```

`--manifest` points at the adapter's manifest for the commands that need it
(e.g. `capability`).

## The two startup flows

**Harness-hosted** — the harness owns the process; spt-core is invoked from
inside it (hooks):

```text
SessionStart hook ──► api seed --pid {parent_pid} --session-id {session_id}
session's listener ──► api listen <id>      (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:

```text
spt-core spawns the template ──► session comes up
session (or its wrapper) ──► api bind <id> --set-session-id <discovered-id>
```

No seed file is involved; `bind` attaches the live session to its perch
post-spawn.

## Session lifecycle

### `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:<pid>`.

### `api listen <id> [--once] [--parent-pid <pid>] [--subnet <name>]`

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).

### `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:<id> token=<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 <that same id>` for spt-core to match against
the record this bind wrote.

### `api boundary <clear|compact> <id> --to-session-id <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.

### `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 <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

### `api state <busy|idle> <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 <set|clear> <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 <id>`

Report user/agent presence at this endpoint (feeds most-recently-active
resolution across the subnet).

### `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

### `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.

### `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

Nested, short-lived agents under a parent endpoint:

- `api worker-start <parent> <id>` — create a nested worker perch.
- `api worker-poll <id>` — drain the worker's messages.
- `api worker-stop <id>` — tear the worker perch down.

## 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 <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 <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 <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

### `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

- **Output is line-oriented and stable**: `SEEDED:<pid>`, `READY:<id>`,
  `SENT:<id>`, `QUEUED:<id>`, 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
  `<endpoint_id>-commune.md` / `<endpoint_id>-signoff.md` into the manifest's
  watched directory; spt-core's watcher ingests it. There is deliberately no
  `api commune`.
