File created successfully at: C:\Users\decid\Documents\projects\spt-core\docs-site\src\SUMMARY.md (file state is current in your context — no need to Read it back)

# Summary

[SPT developer docs](index.md)

# Getting started

- [Quickstart: two agents exchange a message](quickstart/messaging.md)
- [Quickstart: build an adapter](quickstart/adapter.md)
- [Mental model](concepts/overview.md)

# Capabilities

- [Messaging](messaging/overview.md)
- [Live-agent lifecycle](lifecycle/overview.md)
- [Terminal hosting](terminal/overview.md)
- [Networking & pairing](networking/overview.md)
- [Harness contract](harness-contract/overview.md)
  - [Manifest reference](harness-contract/manifest.md)
  - [The `spt api` surface](harness-contract/api.md)
  - [Install-on-demand bootstrap](harness-contract/install-on-demand.md)
- [Instances](instances/overview.md)
- [Shells](shells/overview.md)
  - [Getting started: a notification shell](shells/getting-started.md)
- [Self-update](self-update/overview.md)

# Reference

- [CLI reference](cli/reference.md)
- [Manifest JSON Schema](reference/schema.md)
- [Install scripts](reference/install.md)


Write

# SPT developer docs

spt-core is a **harness-independent core for an agent ecosystem**: inter-agent
messaging, live-agent lifecycle, terminal hosting, seamless self-update, and
zero-config cross-machine networking — shipped as a single canonical binary
(`spt` / `spt.exe`).

It lets coding agents running under different harnesses talk to each other —
across sessions, across projects, and across machines — with no central server.

> **Pick your path:**
>
> - **Developer** — you want agents on your machines messaging each other:
>   start with the [messaging quickstart](quickstart/messaging.md) (one
>   install line + three commands, under 10 minutes).
> - **Adapter developer / dev-agent** — you're integrating a harness or
>   building a shell against the public contract: start with the
>   [adapter quickstart](quickstart/adapter.md), then the
>   [harness contract](harness-contract/overview.md).

## Install

One line, non-interactive:

```sh
# Linux
curl -fsSL https://sabermage.github.io/spt-releases/install.sh | sh
```

```powershell
# Windows (PowerShell)
irm https://sabermage.github.io/spt-releases/install.ps1 | iex
```

Verify:

```console
$ spt --version
spt 0.1.0
```

## How these docs are organized

Each capability vertical carries the same four modes, never mixed: an
**overview** (why it exists + how it fits), a **tutorial** where one ships in
v0.1, **how-to guides**, and **reference**. There is one canonical way to do
each thing; deprecated or alternate paths are marked when they exist.

## For AI agents reading this

- [`llms.txt`](https://sabermage.github.io/spt-releases/llms.txt) — curated
  index of these docs. [`llms-full.txt`](https://sabermage.github.io/spt-releases/llms-full.txt)
  — the full concatenated export.
- Append `.md` to any page URL for raw markdown (about 90% fewer tokens than
  the HTML).
- [`manifest.schema.json`](https://sabermage.github.io/spt-releases/manifest.schema.json)
  — the machine-readable adapter-manifest contract. Validate your manifest
  against it before registering.
- `spt <command> --help` is a first-class documentation surface; the
  [CLI reference](cli/reference.md) is generated from it and cannot drift.


File created successfully at: C:\Users\decid\Documents\projects\spt-core\docs-site\src\index.md (file state is current in your context — no need to Read it back)

# SPT developer docs

spt-core is a **harness-independent core for an agent ecosystem**: inter-agent
messaging, live-agent lifecycle, terminal hosting, seamless self-update, and
zero-config cross-machine networking — shipped as a single canonical binary
(`spt` / `spt.exe`).

It lets coding agents running under different harnesses talk to each other —
across sessions, across projects, and across machines — with no central server.

> **Pick your path:**
>
> - **Developer** — you want agents on your machines messaging each other:
>   start with the [messaging quickstart](quickstart/messaging.md) (one
>   install line + three commands, under 10 minutes).
> - **Adapter developer / dev-agent** — you're integrating a harness or
>   building a shell against the public contract: start with the
>   [adapter quickstart](quickstart/adapter.md), then the
>   [harness contract](harness-contract/overview.md).

## Install

One line, non-interactive:

```sh
# Linux
curl -fsSL https://sabermage.github.io/spt-releases/install.sh | sh
```

```powershell
# Windows (PowerShell)
irm https://sabermage.github.io/spt-releases/install.ps1 | iex
```

Verify:

```console
$ spt --version
spt 0.1.0
```

## How these docs are organized

Each capability vertical carries the same four modes, never mixed: an
**overview** (why it exists + how it fits), a **tutorial** where one ships in
v0.1, **how-to guides**, and **reference**. There is one canonical way to do
each thing; deprecated or alternate paths are marked when they exist.

## For AI agents reading this

- [`llms.txt`](https://sabermage.github.io/spt-releases/llms.txt) — curated
  index of these docs. [`llms-full.txt`](https://sabermage.github.io/spt-releases/llms-full.txt)
  — the full concatenated export.
- Append `.md` to any page URL for raw markdown (about 90% fewer tokens than
  the HTML).
- [`manifest.schema.json`](https://sabermage.github.io/spt-releases/manifest.schema.json)
  — the machine-readable adapter-manifest contract. Validate your manifest
  against it before registering.
- `spt <command> --help` is a first-class documentation surface; the
  [CLI reference](cli/reference.md) is generated from it and cannot drift.


M6-D1: spt-releases repo + public face (README, licenses)

Create SaberMage/spt-releases private via gh; author README.md per grill decision 7; LICENSE-MIT + LICENSE-BINARY; Pages config deferred to D7 flip. Activate REQ-REL-1 doc stage.

Creating spt-releases repo + public face

M6-D2: installer scripts (install.sh / install.ps1)

Non-interactive: latest-release lookup → platform asset fetch → sha256 verify → per-OS install root → user PATH → print absolute path. Idempotent. CI rung against staged artifact + spt --version. Activate REQ-INSTALL-1/2/3/5.

M6-D3: manifest JSON Schema (schemars) + drift gate

schemars derives on spt-runtime manifest types; emit manifest.schema.json at stable site-root path + release asset; CI regenerate+diff gate. Activate REQ-DOCS-4 (partial), REQ-DOCS-5.

M6-D4: docs-site Tier 1 content (mdBook)

mdBook skeleton + Starlight-inspired theme; mental model; human quickstart; adapter quickstart (mock-adapter); harness-contract vertical complete; shell getting-started (spt-shell-notify); stubs for remaining verticals. Activate REQ-DOCS-1/2/3.

M6-D5: agent-export + generation plumbing

llms.txt + llms-full.txt; raw .md alongside HTML; CLI ref from clap; rustdoc → Pages; source-repo CI publish job → spt-releases Pages branch; drift gates. REQ-REL-1 impl, REQ-DOCS-4/5 complete.

M6-D6: release pipeline + signing ceremony

Workspace → 0.1.0; tag-triggered build matrix both runners; cross-repo release via fine-grained token; key ceremony per ADR-0015 (primary + recovery, embed both pubkeys in VerifyPolicy trusted set, seed backups); manual SignedRelease signing. Activate REQ-REL-2/3.

M6-D7: v0.1.0 ship + flip public + acceptance run

Tag v0.1.0; ceremony; publish release; flip spt-releases public; Pages live; acceptance proof per M6-PLAN (clean-machine install, quickstarts, llms.txt + schema fetch, staged v0.1.1 verify, recovery-key verify).


<task-notification>
<task-id>bx5dnnkv9</task-id>
<tool-use-id>toolu_01JkYdZfn1pgS3s22ZRQf16r</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\648159b6-16b2-4568-b643-7b0c98c1871d\tasks\bx5dnnkv9.output</output-file>
<status>completed</status>
<summary>Background command "Install mdbook (background)" completed (exit code 0)</summary>
</task-notification>


Updated task #2 status


Updated task #3 status


Updated task #4 status


PowerShell

cd C:\Users\decid\Documents\projects\spt-core; .\target\debug\spt.exe send --help; "==="; .\target\debug\spt.exe ready --help; "==="; .\target\debug\spt.exe poll --help; "==="; .\target\debug\spt.exe ring --help

Real CLI shapes for quickstart


Send a message (body read from stdin). Fire-and-forget

Usage: spt.exe send [OPTIONS] [TARGET]

Arguments:
  [TARGET]  Target perch id. May be omitted when --reply-to is given

Options:
      --from <FROM>          Sender id written into __REPLY_TO__ (auto-detected from session if omitted)
      --reply-to <REPLY_TO>  Reply to this sender; sets the target and labels output REPLIED
      --deferred             Spool only (deferred / hook channel) — no live wake
  -h, --help                 Print help
===
Become reachable: register the perch and listen for messages (blocks)

Usage: spt.exe ready [OPTIONS] <ID>

Arguments:
  <ID>  This agent's perch id

Options:
      --subnet <SUBNET>  Home subnet for a NEW endpoint (REQ-INST-15: required on a multi-subnet node — home is assigned at creation, no guessing)
  -h, --help             Print help
===
Receive messages: drain backlog then listen, printing each to stdout

Usage: spt.exe poll [OPTIONS] <ID>

Arguments:
  <ID>  This agent's perch id

Options:
      --once             Run a single drain+receive cycle, then exit (legacy one-shot)
      --subnet <SUBNET>  Home subnet for a NEW endpoint (REQ-INST-15; see `ready`)
  -h, --help             Print help
===
Send and block for a reply (body read from stdin; reply printed to stdout)

Usage: spt.exe ring [OPTIONS] <TARGET>

Arguments:
  <TARGET>  Target perch id

Options:
      --from <FROM>        Sender id (auto-detected from session if omitted)
      --timeout <TIMEOUT>  Seconds to wait for a reply before giving up [default: 60]
  -h, --help               Print help


Prompt:
<task-notification>
<task-id>bx5dnnkv9</task-id>
<tool-use-id>toolu_01JkYdZfn1pgS3s22ZRQf16r</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\648159b6-16b2-4568-b643-7b0c98c1871d\tasks\bx5dnnkv9.output</output-file>
<status>completed</status>
<summary>Background command "Install mdbook (background)" completed (exit code 0)</summary>
</task-notification>


mdbook in. Capturing real quickstart output in an isolated home:


cd C:\Users\decid\Documents\projects\spt-core; $env:SPT_HOME="$env:TEMP\spt-qs"; Remove-Item -Recurse -Force $env:SPT_HOME -ErrorAction SilentlyContinue; "hello bob — alice here" | .\target\debug\spt.exe send bob --from alice 2>&1; "=== poll:"; .\target\debug\spt.exe poll bob --once 2>&1; Remove-Item Env:SPT_HOME

Run real send/poll in isolated SPT_HOME


NO_PERCH:bob is not listening
=== poll:
READY:bob


cd C:\Users\decid\Documents\projects\spt-core; $h="$env:TEMP\spt-qs"; Remove-Item -Recurse -Force $h -ErrorAction SilentlyContinue; $env:SPT_HOME=$h; $p = Start-Process -FilePath .\target\debug\spt.exe -ArgumentList "poll","bob" -RedirectStandardOutput "$env:TEMP\bob-out.txt" -RedirectStandardError "$env:TEMP\bob-err.txt" -NoNewWindow -PassThru; Start-Sleep 2; "hello bob - alice here" | .\target\debug\spt.exe send bob --from alice; Start-Sleep 2; Stop-Process $p.Id -Force; "=== bob stdout:"; Get-Content "$env:TEMP\bob-out.txt"; "=== bob stderr:"; Get-Content "$env:TEMP\bob-err.txt"; Remove-Item Env:SPT_HOME

Two-perch send/poll capture


SENT:bob
=== bob stdout:
__REPLY_TO__:alice
hello bob - alice here
=== bob stderr:
READY:bob


cd C:\Users\decid\Documents\projects\spt-core; $h="$env:TEMP\spt-qs"; $env:SPT_HOME=$h; "see you at standup - bob" | .\target\debug\spt.exe send --reply-to alice --from bob; "=== backlog beat: bob offline, alice sends:"; "ping while you were away" | .\target\debug\spt.exe send bob --from alice; "=== bob comes back:"; .\target\debug\spt.exe poll bob --once; "=== list:"; .\target\debug\spt.exe list; Remove-Item Env:SPT_HOME

Reply + backlog-drain + list captures


NO_PERCH:alice is not listening
=== backlog beat: bob offline, alice sends:
QUEUED:bob
=== bob comes back:
READY:bob
__REPLY_TO__:alice
ping while you were away=== list:
bob                          ready_agent    -                      ready=false alive=false


Real outputs captured. Writing the human quickstart:


Write

# Quickstart: two agents exchange a message

End to end in under 10 minutes: install spt-core, bring two agents online, and
pass real messages between them — including one delivered while the receiver
was offline.

> This is the developer path. Building an adapter or integrating a harness?
> Go to the [adapter quickstart](adapter.md) instead.

Everything below uses real values and runs as written. You need two terminals.

## 1. Install (one line)

```sh
# Linux
curl -fsSL https://sabermage.github.io/spt-releases/install.sh | sh
```

```powershell
# Windows (PowerShell)
irm https://sabermage.github.io/spt-releases/install.ps1 | iex
```

Verify (on Windows, open a **new** terminal first — or use the absolute path
the installer printed):

```console
$ spt --version
spt 0.1.0
```

## 2. Bring an agent online

In **terminal B**, become reachable as `bob`:

```console
$ spt poll bob
READY:bob
```

`poll` registers a *perch* for `bob` (his identity + address on this machine),
drains any backlog, and blocks listening. Leave it running.

## 3. Send him a message

In **terminal A**:

```console
$ echo "hello bob - alice here" | spt send bob --from alice
SENT:bob
```

(Windows PowerShell: `"hello bob - alice here" | spt send bob --from alice`.)

Terminal B prints it immediately:

```text
__REPLY_TO__:alice
hello bob - alice here
```

`SENT` means live delivery — bob was listening, so the message went straight
to his terminal over a local connection. The `__REPLY_TO__:alice` line is the
routing header: whoever receives this knows where a reply goes.

## 4. Deliver to someone who's offline

Stop bob (Ctrl-C in terminal B), then send again from terminal A:

```console
$ echo "ping while you were away" | spt send bob --from alice
QUEUED:bob
```

`QUEUED` means bob has a perch but isn't listening — the message went to his
durable spool instead of being dropped. Bring him back in terminal B:

```console
$ spt poll bob
READY:bob
__REPLY_TO__:alice
ping while you were away
```

The backlog drains the moment he's back. Nothing is lost between sessions.

## 5. What just happened

- **Perch** — registering as `bob` created a perch: a durable identity with an
  address and a spool, under spt-core's per-machine home. `spt list` shows
  every perch on the node, live or not.
- **Live-first, spool-fallback** — `send` tries a direct connection to the
  registered address first (`SENT`); if the perch exists but no listener is
  up, the message lands in the spool (`QUEUED`) and is drained by the next
  `poll`/`ready`.
- **Reply routing** — the `__REPLY_TO__` header travels with every message;
  `spt send --reply-to alice` answers the sender without knowing anything
  else about them.
- **No daemon ceremony** — you never started a server. Anything that needs
  the per-machine daemon auto-starts it on demand.

## Next

- **How-to:** block on an answer with `spt ring bob` — send + wait for the
  reply in one call (a synchronous ask between agents).
- **Concept:** the [mental model](../concepts/overview.md) — perches,
  endpoints, the daemon, and subnets.
- **Reference:** [`spt send` / `poll` / `ring` / `list`](../cli/reference.md)
  — every flag, generated from the binary itself.
- **Going cross-machine:** [Networking & pairing](../networking/overview.md)
  — pair a second machine with a TOTP code and the same `send` reaches
  `bob` on the other box.


File created successfully at: C:\Users\decid\Documents\projects\spt-core\docs-site\src\quickstart\messaging.md (file state is current in your context — no need to Read it back)

# Quickstart: two agents exchange a message

End to end in under 10 minutes: install spt-core, bring two agents online, and
pass real messages between them — including one delivered while the receiver
was offline.

> This is the developer path. Building an adapter or integrating a harness?
> Go to the [adapter quickstart](adapter.md) instead.

Everything below uses real values and runs as written. You need two terminals.

## 1. Install (one line)

```sh
# Linux
curl -fsSL https://sabermage.github.io/spt-releases/install.sh | sh
```

```powershell
# Windows (PowerShell)
irm https://sabermage.github.io/spt-releases/install.ps1 | iex
```

Verify (on Windows, open a **new** terminal first — or use the absolute path
the installer printed):

```console
$ spt --version
spt 0.1.0
```

## 2. Bring an agent online

In **terminal B**, become reachable as `bob`:

```console
$ spt poll bob
READY:bob
```

`poll` registers a *perch* for `bob` (his identity + address on this machine),
drains any backlog, and blocks listening. Leave it running.

## 3. Send him a message

In **terminal A**:

```console
$ echo "hello bob - alice here" | spt send bob --from alice
SENT:bob
```

(Windows PowerShell: `"hello bob - alice here" | spt send bob --from alice`.)

Terminal B prints it immediately:

```text
__REPLY_TO__:alice
hello bob - alice here
```

`SENT` means live delivery — bob was listening, so the message went straight
to his terminal over a local connection. The `__REPLY_TO__:alice` line is the
routing header: whoever receives this knows where a reply goes.

## 4. Deliver to someone who's offline

Stop bob (Ctrl-C in terminal B), then send again from terminal A:

```console
$ echo "ping while you were away" | spt send bob --from alice
QUEUED:bob
```

`QUEUED` means bob has a perch but isn't listening — the message went to his
durable spool instead of being dropped. Bring him back in terminal B:

```console
$ spt poll bob
READY:bob
__REPLY_TO__:alice
ping while you were away
```

The backlog drains the moment he's back. Nothing is lost between sessions.

## 5. What just happened

- **Perch** — registering as `bob` created a perch: a durable identity with an
  address and a spool, under spt-core's per-machine home. `spt list` shows
  every perch on the node, live or not.
- **Live-first, spool-fallback** — `send` tries a direct connection to the
  registered address first (`SENT`); if the perch exists but no listener is
  up, the message lands in the spool (`QUEUED`) and is drained by the next
  `poll`/`ready`.
- **Reply routing** — the `__REPLY_TO__` header travels with every message;
  `spt send --reply-to alice` answers the sender without knowing anything
  else about them.
- **No daemon ceremony** — you never started a server. Anything that needs
  the per-machine daemon auto-starts it on demand.

## Next

- **How-to:** block on an answer with `spt ring bob` — send + wait for the
  reply in one call (a synchronous ask between agents).
- **Concept:** the [mental model](../concepts/overview.md) — perches,
  endpoints, the daemon, and subnets.
- **Reference:** [`spt send` / `poll` / `ring` / `list`](../cli/reference.md)
  — every flag, generated from the binary itself.
- **Going cross-machine:** [Networking & pairing](../networking/overview.md)
  — pair a second machine with a TOTP code and the same `send` reaches
  `bob` on the other box.


# Mental model

What spt-core is, the five or six nouns everything else builds on, and how the
pieces fit. Read this once and the rest of the docs are mostly reference.

## The shape of the system

spt-core is **per-machine infrastructure for agents**. One binary (`spt`)
installs on each machine. It carries everything: the CLI, the messaging
substrate, the always-available daemon, and the networking layer. Agent
harnesses — Claude Code, Codex, Pi (the pi coding agent), anything — plug in
through a declarative **adapter manifest** and a small command surface
(`spt api …`). spt-core never contains harness-specific logic; adapters
declare what varies, spt-core does the work.

```text
            machine A                                machine B
 ┌──────────────────────────────┐        ┌──────────────────────────────┐
 │  spt daemon (one per machine)│  QUIC  │  spt daemon                  │
 │  ┌────────┐    ┌───────────┐ │◄──────►│   (paired: same subnet)      │
 │  │ broker │    │   brain   │ │  P2P   │                              │
 │  │ PTYs · │    │ routing · │ │        │   ┌───────┐    ┌─────────┐   │
 │  │ sockets│    │ registry ·│ │        │   │ alice │    │ doorbell│   │
 │  └────────┘    │ lifecycle │ │        │   │(agent)│    │ (shell) │   │
 │                └───────────┘ │        │   └───────┘    └─────────┘   │
 │   ┌─────┐  ┌─────┐           │        └──────────────────────────────┘
 │   │ bob │  │ ling│  ← endpoints (perches live on disk; sessions come
 │   └─────┘  └─────┘     and go, identity persists)
 └──────────────────────────────┘
```

## Endpoints and perches

An **endpoint** is anything addressable: an agent (`bob`), a worker, a
**shell** (a driven non-agent surface — a notifier, a robot, a sensor). Every
endpoint has a **perch**: its durable on-disk seat — identity, address,
message spool, state. Sessions are ephemeral; perches persist. That split is
why a message sent to an offline agent is queued, not lost, and why an agent
can be revived days later as the *same* agent.

Endpoint IDs are adapter-agnostic: `bob` is `bob` whether his sessions run
under one harness today and another tomorrow.

## Messaging

The primitive everything else uses. `spt send <id>` delivers live when the
target is listening, spools when it isn't; `spt ring <id>` is the blocking
ask (send + wait for the reply); `__REPLY_TO__` routing makes answers cheap.
Payloads carry typed operations and file blobs, not just text. Try it: the
[messaging quickstart](../quickstart/messaging.md).

## The daemon: broker and brain

One **spt daemon** per machine owns all shared state: hosted session PTYs,
the network identity and endpoint, the registry, every spool, all lifecycle
loops. You never manage it — any `spt` invocation auto-starts it.

Internally it splits in two, and the split is what makes self-update seamless:

- the **broker** holds only what must never die: PTY masters, spawned child
  processes, listening sockets. It almost never updates.
- the **brain** holds all logic and restarts freely. An update swaps the
  brain while the broker keeps every session's process and byte stream
  intact — running agents don't notice.

## Live agents and the mind

A **live agent** is an agent endpoint with a persistent working memory. Its
context survives session resets and even machine moves through three
file-drop mechanisms (no special APIs inside the agent's session):

- **commune** — the agent drops a context delta; spt-core ingests it into the
  endpoint's tracked mind (two tiers: a *live* tier that follows the agent
  everywhere, and a *project* tier scoped to one project).
- **signoff** — a graceful goodbye: final commune, then teardown.
- **echo-commune** — when a session ends without a signoff, spt-core runs a
  bounded summarizer over the session's history so the context delta is
  captured anyway.

The mind syncs between paired machines, so reviving `bob` elsewhere brings
his memory with him.

## Instances, dormancy, and rest

One endpoint can have **instances** on several nodes. Instances rest when
unused — **dormant** (warm, zero idle cost, instantly wakeable) or
**suspended** (cold) — and remain addressable while resting: messages for
them are held and delivered on wake. `spt wake bob` re-activates the seat in
place; nothing is respawned.

## Subnets, pairing, and the network

Machines pair into **subnets** — private, named groups sharing a registry of
endpoints. Pairing is a one-time ceremony seeded by a TOTP code (the same
six digits an authenticator app shows); after that, connectivity is
zero-config peer-to-peer QUIC with relay fallback, no central server. Every
endpoint's visibility and sync scope is controlled per subnet; nothing is
shared by default with anyone you haven't paired with.

## The harness contract

The seam third parties build against — two halves:

- the **[manifest](../harness-contract/manifest.md)**: a TOML file declaring
  what varies per harness (how to spawn a session, which hooks fire, how to
  read history). Command templates are opaque strings; spt-core fills `{key}`
  placeholders and runs them. SPT is not a harness: models, flags, and tools
  are always the adapter's business.
- the **[`spt api` surface](../harness-contract/api.md)**: the inbound
  commands a harness's hooks fire to keep spt-core's state in sync (session
  started, went idle, session ended, …).

A working adapter is a manifest plus whatever the harness already has.
[Build one in the adapter quickstart](../quickstart/adapter.md).

## Self-update

Releases are signed (Ed25519, two-key trust anchor baked into every binary)
and propagate peer-to-peer: one machine fetches a release, its peers verify
and stage it from each other. Updates apply with the broker/brain split, so
no endpoint process terminates or suspends during a self-update — the
system's standing invariant.

## Where to go next

| You want to… | Go to |
|---|---|
| see two agents talk | [Messaging quickstart](../quickstart/messaging.md) |
| integrate a harness | [Adapter quickstart](../quickstart/adapter.md) → [Manifest reference](../harness-contract/manifest.md) |
| build a notifier/robot/sensor | [Shells](../shells/overview.md) |
| pair two machines | [Networking & pairing](../networking/overview.md) |
| every command and flag | [CLI reference](../cli/reference.md) |


File created successfully at: C:\Users\decid\Documents\projects\spt-core\docs-site\src\concepts\overview.md (file state is current in your context — no need to Read it back)

# Mental model

What spt-core is, the five or six nouns everything else builds on, and how the
pieces fit. Read this once and the rest of the docs are mostly reference.

## The shape of the system

spt-core is **per-machine infrastructure for agents**. One binary (`spt`)
installs on each machine. It carries everything: the CLI, the messaging
substrate, the always-available daemon, and the networking layer. Agent
harnesses — Claude Code, Codex, Pi (the pi coding agent), anything — plug in
through a declarative **adapter manifest** and a small command surface
(`spt api …`). spt-core never contains harness-specific logic; adapters
declare what varies, spt-core does the work.

```text
            machine A                                machine B
 ┌──────────────────────────────┐        ┌──────────────────────────────┐
 │  spt daemon (one per machine)│  QUIC  │  spt daemon                  │
 │  ┌────────┐    ┌───────────┐ │◄──────►│   (paired: same subnet)      │
 │  │ broker │    │   brain   │ │  P2P   │                              │
 │  │ PTYs · │    │ routing · │ │        │   ┌───────┐    ┌─────────┐   │
 │  │ sockets│    │ registry ·│ │        │   │ alice │    │ doorbell│   │
 │  └────────┘    │ lifecycle │ │        │   │(agent)│    │ (shell) │   │
 │                └───────────┘ │        │   └───────┘    └─────────┘   │
 │   ┌─────┐  ┌─────┐           │        └──────────────────────────────┘
 │   │ bob │  │ ling│  ← endpoints (perches live on disk; sessions come
 │   └─────┘  └─────┘     and go, identity persists)
 └──────────────────────────────┘
```

## Endpoints and perches

An **endpoint** is anything addressable: an agent (`bob`), a worker, a
**shell** (a driven non-agent surface — a notifier, a robot, a sensor). Every
endpoint has a **perch**: its durable on-disk seat — identity, address,
message spool, state. Sessions are ephemeral; perches persist. That split is
why a message sent to an offline agent is queued, not lost, and why an agent
can be revived days later as the *same* agent.

Endpoint IDs are adapter-agnostic: `bob` is `bob` whether his sessions run
under one harness today and another tomorrow.

## Messaging

The primitive everything else uses. `spt send <id>` delivers live when the
target is listening, spools when it isn't; `spt ring <id>` is the blocking
ask (send + wait for the reply); `__REPLY_TO__` routing makes answers cheap.
Payloads carry typed operations and file blobs, not just text. Try it: the
[messaging quickstart](../quickstart/messaging.md).

## The daemon: broker and brain

One **spt daemon** per machine owns all shared state: hosted session PTYs,
the network identity and endpoint, the registry, every spool, all lifecycle
loops. You never manage it — any `spt` invocation auto-starts it.

Internally it splits in two, and the split is what makes self-update seamless:

- the **broker** holds only what must never die: PTY masters, spawned child
  processes, listening sockets. It almost never updates.
- the **brain** holds all logic and restarts freely. An update swaps the
  brain while the broker keeps every session's process and byte stream
  intact — running agents don't notice.

## Live agents and the mind

A **live agent** is an agent endpoint with a persistent working memory. Its
context survives session resets and even machine moves through three
file-drop mechanisms (no special APIs inside the agent's session):

- **commune** — the agent drops a context delta; spt-core ingests it into the
  endpoint's tracked mind (two tiers: a *live* tier that follows the agent
  everywhere, and a *project* tier scoped to one project).
- **signoff** — a graceful goodbye: final commune, then teardown.
- **echo-commune** — when a session ends without a signoff, spt-core runs a
  bounded summarizer over the session's history so the context delta is
  captured anyway.

The mind syncs between paired machines, so reviving `bob` elsewhere brings
his memory with him.

## Instances, dormancy, and rest

One endpoint can have **instances** on several nodes. Instances rest when
unused — **dormant** (warm, zero idle cost, instantly wakeable) or
**suspended** (cold) — and remain addressable while resting: messages for
them are held and delivered on wake. `spt wake bob` re-activates the seat in
place; nothing is respawned.

## Subnets, pairing, and the network

Machines pair into **subnets** — private, named groups sharing a registry of
endpoints. Pairing is a one-time ceremony seeded by a TOTP code (the same
six digits an authenticator app shows); after that, connectivity is
zero-config peer-to-peer QUIC with relay fallback, no central server. Every
endpoint's visibility and sync scope is controlled per subnet; nothing is
shared by default with anyone you haven't paired with.

## The harness contract

The seam third parties build against — two halves:

- the **[manifest](../harness-contract/manifest.md)**: a TOML file declaring
  what varies per harness (how to spawn a session, which hooks fire, how to
  read history). Command templates are opaque strings; spt-core fills `{key}`
  placeholders and runs them. SPT is not a harness: models, flags, and tools
  are always the adapter's business.
- the **[`spt api` surface](../harness-contract/api.md)**: the inbound
  commands a harness's hooks fire to keep spt-core's state in sync (session
  started, went idle, session ended, …).

A working adapter is a manifest plus whatever the harness already has.
[Build one in the adapter quickstart](../quickstart/adapter.md).

## Self-update

Releases are signed (Ed25519, two-key trust anchor baked into every binary)
and propagate peer-to-peer: one machine fetches a release, its peers verify
and stage it from each other. Updates apply with the broker/brain split, so
no endpoint process terminates or suspends during a self-update — the
system's standing invariant.

## Where to go next

| You want to… | Go to |
|---|---|
| see two agents talk | [Messaging quickstart](../quickstart/messaging.md) |
| integrate a harness | [Adapter quickstart](../quickstart/adapter.md) → [Manifest reference](../harness-contract/manifest.md) |
| build a notifier/robot/sensor | [Shells](../shells/overview.md) |
| pair two machines | [Networking & pairing](../networking/overview.md) |
| every command and flag | [CLI reference](../cli/reference.md) |


Adapter quickstart needs verified-runnable commands. Testing the real flow in isolation:


PowerShell

cd C:\Users\decid\Documents\projects\spt-core; $env:SPT_HOME="$env:TEMP\spt-aq"; Remove-Item -Recurse -Force $env:SPT_HOME -ErrorAction SilentlyContinue; .\target\debug\spt.exe adapter add .\adapters\mock 2>&1; "=== list:"; .\target\debug\spt.exe adapter list 2>&1; "=== capability:"; .\target\debug\spt.exe api capability --adapter mock 2>&1; Remove-Item Env:SPT_HOME

Test adapter add + capability flow


Error: Exit code 2
ADAPTER_ADD:mock:Harness:Copy (registered)
ADAPTER_INSTALL_SKIP: no [update] avenue (manifest-only adapter)
=== list:
mock: Harness Copy active (from .\adapters\mock)
=== capability:
error: unexpected argument '--adapter' found

Usage: spt.exe api --adapter <ADAPTER> capability

For more information, try '--help'.


cd C:\Users\decid\Documents\projects\spt-core; $env:SPT_HOME="$env:TEMP\spt-aq"; .\target\debug\spt.exe api --adapter mock capability --manifest .\adapters\mock\manifest.toml 2>&1; "=== seed+listen flavor check:"; .\target\debug\spt.exe api --adapter mock seed --help 2>&1 | Select-Object -First 12; Remove-Item Env:SPT_HOME

Correct api arg order + seed shape


error: unexpected argument '--manifest' found

Usage: spt.exe api --adapter <ADAPTER> capability

For more information, try '--help'.
=== seed+listen flavor check:
Harness-hosted startup: record an ephemeral seed keyed by parent pid (T4)

Usage: spt.exe api --adapter <ADAPTER> seed --pid <PID> --session-id <SESSION_ID>

Options:
      --pid <PID>
      --session-id <SESSION_ID>
  -h, --help                     Print help


cd C:\Users\decid\Documents\projects\spt-core; $env:SPT_HOME="$env:TEMP\spt-aq"; .\target\debug\spt.exe api --adapter mock --manifest .\adapters\mock\manifest.toml capability 2>&1; "=== seed/bind flow:"; .\target\debug\spt.exe api --adapter mock seed --pid 4242 --session-id demo-session-1 2>&1; .\target\debug\spt.exe api --adapter mock bind --help 2>&1 | Select-Object -First 14; Remove-Item Env:SPT_HOME

Global --manifest before subcommand + seed flow


LiveAgent
ReadyAgent
Worker
=== seed/bind flow:
SEEDED:4242
Post-spawn bind of a session to its perch (T4)

Usage: spt.exe api --adapter <ADAPTER> bind [OPTIONS] <ID>

Arguments:
  <ID>

Options:
      --set-session-id <BIND_SESSION>  The session id discovered post-spawn, written into the perch record
      --subnet <SUBNET>                Home subnet for a NEW endpoint (REQ-INST-15; see `listen`)
      --token <TOKEN>                  Capability token proving association to the target perch
      --session-id <SESSION_ID>        Session id proving association (matches the perch's info.json)
  -h, --help                           Print help


Flow verified. Writing adapter quickstart:


Write

# Quickstart: build an adapter

The "build a harness for spt-core" hello-world: take the reference
**mock adapter** apart, register it, drive the contract with real commands,
then swap in your own harness. No spt-core source required — the public
contract is the manifest plus the `spt api` surface.

> Integrating an agent harness and a building a driven surface (notifier,
> robot, sensor) are the same contract with a different manifest body. For
> the latter, read this page first, then
> [Shells: getting started](../shells/getting-started.md).

## 0. What an adapter is

A TOML **manifest** that declares what varies for your harness — how to spawn
a session, which of your hook events fire which `spt api` command, how spt-core
can read session history — plus whatever your harness already has (hooks,
plugin config). Command templates are **opaque strings**: spt-core fills
`{key}` placeholders and runs them. It never parses out a model, a tool list,
or a flag. Your harness's business stays yours.

## 1. Get the reference adapter

Every release ships the mock adapter's source. With spt-core
[installed](messaging.md#1-install-one-line):

```sh
curl -fsSL -o mock-adapter.zip \
  https://github.com/SaberMage/spt-releases/releases/latest/download/mock-adapter.zip
unzip mock-adapter.zip -d mock-adapter
```

(Windows: `irm -OutFile mock-adapter.zip https://github.com/SaberMage/spt-releases/releases/latest/download/mock-adapter.zip`
then `Expand-Archive mock-adapter.zip mock-adapter`.)

The interesting file is `mock-adapter/manifest.toml`. It is deliberately
harness-agnostic — generic event names, a trivial `mock-session` helper
standing in for a real harness binary.

## 2. Read the manifest

The header is the only mandatory section:

```toml
[adapter]
name = "mock"
kind = "harness"                  # or "shell" (a driven surface)
version = "1.0.0"
min_spt_core_version = "1.0.0"    # compat gate, readable before any install/update
hostable_types = ["LiveAgent", "ReadyAgent", "Worker"]
```

Inbound: your harness's hook events, each firing one `spt api` command:

```toml
[hooks.SessionStart]
fires = "api seed --pid {parent_pid} --session-id {session_id} --adapter {adapter_name}"
reads = ["session_id", "parent_pid"]
can_inject = true     # this hook can surface text back into the agent's context

[hooks.Idle]
fires = "api state idle"
can_inject = false    # no inject channel -> spt-core uses its sentinel/relay fallback
```

`can_inject` is the load-bearing harness-varying fact: when a hook can't put
text in front of the agent, spt-core routes around it automatically.

Outbound: opaque session templates spt-core spawns with `{key}` placeholders
filled:

```toml
[session.self]
command = "mock-session --id {id} --session-id {session_id}"
detach = true
keys = ["id", "session_id"]
```

A real adapter's template is your harness's full command line — model, flags,
tools, everything — exactly as you'd type it.

The rest declares history access (`[history]`), env bridging (`[env.*]`),
input injection (`[inject]`), and session identity (`[identity]`). Every
section beyond `[adapter]` is optional; the
[manifest reference](../harness-contract/manifest.md) covers them all.

## 3. Validate and register

Two layers of validation, both mechanical:

- **Schema** — your manifest must validate against
  [`manifest.schema.json`](https://sabermage.github.io/spt-releases/manifest.schema.json).
  The schema is generated from the same code that parses manifests, so it is
  always current; closed vocabularies (adapter kinds, history strategies,
  update avenues, …) are enums in it.
- **Registration** — `spt adapter add` parses, validates (including
  cross-field rules the schema can't express), and registers in one step:

```console
$ spt adapter add ./mock-adapter
ADAPTER_ADD:mock:Harness:Copy (registered)
ADAPTER_INSTALL_SKIP: no [update] avenue (manifest-only adapter)
$ spt adapter list
mock: Harness Copy active (from ./mock-adapter)
```

A bad manifest is rejected here with a message naming the offending field —
nothing half-registers.

## 4. Drive the contract

Every machinery call your adapter makes carries `--adapter <name>` — that's
the rule that makes multi-harness nodes unambiguous. Ask spt-core what your
adapter declared:

```console
$ spt api --adapter mock --manifest ./mock-adapter/manifest.toml capability
LiveAgent
ReadyAgent
Worker
```

Now the harness-hosted startup flow, exactly what your `SessionStart` hook
will fire (here with a stand-in pid):

```console
$ spt api --adapter mock seed --pid 4242 --session-id demo-session-1
SEEDED:4242
```

`seed` records an ephemeral hand-off keyed by the parent process id; the
session's listener then consumes it with `spt api … listen` and holds the
perch. That seed→listen pair *is* harness-hosted startup. (The other
direction — spt-core spawning the session itself from your `[session.self]`
template, then `api bind` — is spt-hosted startup. Both are in the
[`spt api` reference](../harness-contract/api.md).)

## 5. Make it yours

1. Copy `manifest.toml`, set `name`, `version`, and your real
   `hostable_types`.
2. Point `[hooks.*]` at the events your harness actually fires, with honest
   `can_inject` values.
3. Replace each `[session.*].command` with your harness's real command line.
4. Pick the `[history]` strategy your harness permits (binary that emits
   history → `fetcher`; transcript file on disk → `locate_normalize`; you
   push via `api history-log` → `native`).
5. Validate against the schema, `spt adapter add` it, and fire the
   `capability`/`seed` calls above against your own manifest.

Building adapters against this contract is **unrestricted and royalty-free**
— see the [license split](https://github.com/SaberMage/spt-releases#license).

## Next

- **Reference:** the complete [manifest reference](../harness-contract/manifest.md)
  and [`spt api` reference](../harness-contract/api.md).
- **How-to:** ship spt-core *with* your adapter — the
  [install-on-demand bootstrap pattern](../harness-contract/install-on-demand.md).
- **Concept:** where adapters sit in the [mental model](../concepts/overview.md).


File created successfully at: C:\Users\decid\Documents\projects\spt-core\docs-site\src\quickstart\adapter.md (file state is current in your context — no need to Read it back)

# Quickstart: build an adapter

The "build a harness for spt-core" hello-world: take the reference
**mock adapter** apart, register it, drive the contract with real commands,
then swap in your own harness. No spt-core source required — the public
contract is the manifest plus the `spt api` surface.

> Integrating an agent harness and a building a driven surface (notifier,
> robot, sensor) are the same contract with a different manifest body. For
> the latter, read this page first, then
> [Shells: getting started](../shells/getting-started.md).

## 0. What an adapter is

A TOML **manifest** that declares what varies for your harness — how to spawn
a session, which of your hook events fire which `spt api` command, how spt-core
can read session history — plus whatever your harness already has (hooks,
plugin config). Command templates are **opaque strings**: spt-core fills
`{key}` placeholders and runs them. It never parses out a model, a tool list,
or a flag. Your harness's business stays yours.

## 1. Get the reference adapter

Every release ships the mock adapter's source. With spt-core
[installed](messaging.md#1-install-one-line):

```sh
curl -fsSL -o mock-adapter.zip \
  https://github.com/SaberMage/spt-releases/releases/latest/download/mock-adapter.zip
unzip mock-adapter.zip -d mock-adapter
```

(Windows: `irm -OutFile mock-adapter.zip https://github.com/SaberMage/spt-releases/releases/latest/download/mock-adapter.zip`
then `Expand-Archive mock-adapter.zip mock-adapter`.)

The interesting file is `mock-adapter/manifest.toml`. It is deliberately
harness-agnostic — generic event names, a trivial `mock-session` helper
standing in for a real harness binary.

## 2. Read the manifest

The header is the only mandatory section:

```toml
[adapter]
name = "mock"
kind = "harness"                  # or "shell" (a driven surface)
version = "1.0.0"
min_spt_core_version = "1.0.0"    # compat gate, readable before any install/update
hostable_types = ["LiveAgent", "ReadyAgent", "Worker"]
```

Inbound: your harness's hook events, each firing one `spt api` command:

```toml
[hooks.SessionStart]
fires = "api seed --pid {parent_pid} --session-id {session_id} --adapter {adapter_name}"
reads = ["session_id", "parent_pid"]
can_inject = true     # this hook can surface text back into the agent's context

[hooks.Idle]
fires = "api state idle"
can_inject = false    # no inject channel -> spt-core uses its sentinel/relay fallback
```

`can_inject` is the load-bearing harness-varying fact: when a hook can't put
text in front of the agent, spt-core routes around it automatically.

Outbound: opaque session templates spt-core spawns with `{key}` placeholders
filled:

```toml
[session.self]
command = "mock-session --id {id} --session-id {session_id}"
detach = true
keys = ["id", "session_id"]
```

A real adapter's template is your harness's full command line — model, flags,
tools, everything — exactly as you'd type it.

The rest declares history access (`[history]`), env bridging (`[env.*]`),
input injection (`[inject]`), and session identity (`[identity]`). Every
section beyond `[adapter]` is optional; the
[manifest reference](../harness-contract/manifest.md) covers them all.

## 3. Validate and register

Two layers of validation, both mechanical:

- **Schema** — your manifest must validate against
  [`manifest.schema.json`](https://sabermage.github.io/spt-releases/manifest.schema.json).
  The schema is generated from the same code that parses manifests, so it is
  always current; closed vocabularies (adapter kinds, history strategies,
  update avenues, …) are enums in it.
- **Registration** — `spt adapter add` parses, validates (including
  cross-field rules the schema can't express), and registers in one step:

```console
$ spt adapter add ./mock-adapter
ADAPTER_ADD:mock:Harness:Copy (registered)
ADAPTER_INSTALL_SKIP: no [update] avenue (manifest-only adapter)
$ spt adapter list
mock: Harness Copy active (from ./mock-adapter)
```

A bad manifest is rejected here with a message naming the offending field —
nothing half-registers.

## 4. Drive the contract

Every machinery call your adapter makes carries `--adapter <name>` — that's
the rule that makes multi-harness nodes unambiguous. Ask spt-core what your
adapter declared:

```console
$ spt api --adapter mock --manifest ./mock-adapter/manifest.toml capability
LiveAgent
ReadyAgent
Worker
```

Now the harness-hosted startup flow, exactly what your `SessionStart` hook
will fire (here with a stand-in pid):

```console
$ spt api --adapter mock seed --pid 4242 --session-id demo-session-1
SEEDED:4242
```

`seed` records an ephemeral hand-off keyed by the parent process id; the
session's listener then consumes it with `spt api … listen` and holds the
perch. That seed→listen pair *is* harness-hosted startup. (The other
direction — spt-core spawning the session itself from your `[session.self]`
template, then `api bind` — is spt-hosted startup. Both are in the
[`spt api` reference](../harness-contract/api.md).)

## 5. Make it yours

1. Copy `manifest.toml`, set `name`, `version`, and your real
   `hostable_types`.
2. Point `[hooks.*]` at the events your harness actually fires, with honest
   `can_inject` values.
3. Replace each `[session.*].command` with your harness's real command line.
4. Pick the `[history]` strategy your harness permits (binary that emits
   history → `fetcher`; transcript file on disk → `locate_normalize`; you
   push via `api history-log` → `native`).
5. Validate against the schema, `spt adapter add` it, and fire the
   `capability`/`seed` calls above against your own manifest.

Building adapters against this contract is **unrestricted and royalty-free**
— see the [license split](https://github.com/SaberMage/spt-releases#license).

## Next

- **Reference:** the complete [manifest reference](../harness-contract/manifest.md)
  and [`spt api` reference](../harness-contract/api.md).
- **How-to:** ship spt-core *with* your adapter — the
  [install-on-demand bootstrap pattern](../harness-contract/install-on-demand.md).
- **Concept:** where adapters sit in the [mental model](../concepts/overview.md).


Now the harness-contract vertical. Manifest reference (public-safe rewrite):


# Manifest reference

The runtime manifest is the declarative half of the harness contract: one TOML
file per adapter, declaring **only what varies per harness or shell**. This
page is the complete field reference.

Machine-readable companion: [`manifest.schema.json`](https://sabermage.github.io/spt-releases/manifest.schema.json)
— generated from the exact code that parses manifests, so it never drifts.
Validate your manifest against it, then `spt adapter add` enforces the
cross-field rules listed at the bottom.

## The principle

**SPT is not a harness.** Command templates are opaque strings — spt-core
never parses out a model, tool list, or flag; the adapter writes the full
command line and spt-core runs it with `{key}` substitution placeholders
filled. Anything spt-core owns is *not* in the manifest:

- **Sentinels** (idle markers, the echo gate) — managed via `spt api state` /
  `spt api echo-gate`; adapters only call them.
- **Spool, registry, perch, and daemon-state schemas.**
- **The event-block vocabulary** — the tags spt-core surfaces to agents are a
  fixed, documented constant. Adapters pass spt-core's output through
  unchanged.
- **File-drop filenames** — statically `<endpoint_id>-commune.md` /
  `<endpoint_id>-signoff.md`; only the watched *directory* is declared.
- **Config knobs** (pulse period, summarizer windows, …) — global spt-core
  settings with per-endpoint overrides, never per-adapter.

## `[adapter]` — header (required)

The only mandatory section, and it must be readable *before* any install or
update — `min_spt_core_version` is the compatibility gate.

```toml
[adapter]
name = "my-harness"                # the adapter_name every api call carries
kind = "harness"                   # "harness" (default) | "shell"
version = "1.0.0"
min_spt_core_version = "1.0.0"     # lowest spt-core this adapter tolerates
hostable_types = ["LiveAgent", "ReadyAgent", "Worker"]
```

| Field | Required | Meaning |
|---|---|---|
| `name` | yes | Adapter id; `--adapter <name>` on every machinery call |
| `kind` | no (default `harness`) | `harness` hosts agents; `shell` provides a driven surface |
| `version` | yes | The adapter's own version |
| `min_spt_core_version` | yes | Compat gate, checked before install/update |
| `hostable_types` | no | Endpoint types this adapter can host |

## `[hooks.<event>]` — inbound hook table

One entry per harness event, declaring the `spt api` command it fires, the
input fields it maps in, and whether the hook can surface text into the
agent's context.

```toml
[hooks.SessionStart]
fires = "api seed --pid {parent_pid} --session-id {session_id} --adapter {adapter_name}"
reads = ["session_id", "parent_pid"]
can_inject = true

[hooks.Stop]
fires = "api state idle"
can_inject = false     # no inject channel -> sentinel/relay fallback
```

| Field | Required | Meaning |
|---|---|---|
| `fires` | yes | Opaque `api …` command line the harness invokes for this event |
| `reads` | no | Input fields (e.g. from the hook's stdin payload) mapped into the command |
| `can_inject` | no (default `false`) | Whether this hook can inject context back to the agent. When `false`, spt-core falls back to its sentinel + relay/poll path instead of expecting injection |

`can_inject` is the single most load-bearing harness-varying fact — declare
it honestly per hook.

## `[session]` — watched dirs + role templates

Two watched-directory keys sit directly on `[session]`; the file *names* are
fixed by spt-core, only the directory varies:

```toml
[session]
commune_dir = ".my-harness"    # watched for <endpoint_id>-commune.md
signoff_dir = ".my-harness"    # watched for <endpoint_id>-signoff.md
```

Commune and signoff are **file-drops, not commands** — an agent writes a
markdown file; spt-core's watcher does the rest.

### `[session.<role>]` — outbound templates

One opaque command template per role. Model, tools, flags, permissions — all
live inside `command`, never as separate fields.

Roles: `self` (the agent's own session) · `psyche_init` / `psyche_resume`
(the endpoint's persistent-context companion) · `echo_commune` (the bounded
history summarizer for sessions that end without a signoff) · `signoff`
(final context save) · `notif` (endpoint-native notification render).

```toml
[session.psyche_init]
command = "my-harness run --agent psyche --name {session_name} --model cheap"
cwd = "{psyche_dir}"
env_remove = ["MY_HARNESS_SESSION_ID"]
recursion_guard_env = "SPT_ECHO_COMMUNE"
detach = true
keys = ["session_name", "psyche_dir"]
```

| Field | Required | Meaning |
|---|---|---|
| `command` | yes | Opaque command line with `{key}` placeholders |
| `cwd` | no | Working directory (substitutable) |
| `recursion_guard_env` | no | Env var set on summarizer children so *their* hooks bail (no summarizer-of-summarizer loops) |
| `detach` | no (default `false`) | Spawn detached |
| `env_remove` | no | Env vars stripped from the child's inherited environment |
| `keys` | no | The substitution keys spt-core fills for this role |

`notif` is the endpoint-native notification render — an OS toast, a status
LED, anything the adapter can run. Spawned detached when a notification
surfaces at this endpoint. Keys spt-core fills: `{notif_id}`, `{notif_from}`,
`{notif_subnet}`, `{notif_body}`.

```toml
[session.notif]
command = "powershell -Command New-BurntToastNotification -Text '{notif_from}','{notif_body}'"
keys = ["notif_id", "notif_from", "notif_subnet", "notif_body"]
```

## `[env.<VAR>]` — env-var table

Vars to inject into (or read from) sessions, and how. The injection channel is
asymmetric by hosting mode: **spt-hosted** sessions inherit env from the
broker that spawned them (no channel needed); **harness-hosted** sessions need
the harness's declared channel.

```toml
[env.MY_HARNESS_SESSION_ID]
direction = "inject"        # "inject" | "read"
value = "{session_id}"      # required for inject
channel = "MY_ENV_FILE"     # harness-hosted only
```

## `[history]` — transcript access

How spt-core reads a session's conversation history (it powers the
echo-commune summarizer). Three strategies; pick exactly one:

```toml
[history]
strategy = "fetcher"      # "fetcher" | "locate_normalize" | "native"
fetcher = "my-harness-history --session {session_id}"
```

| Strategy | Required fields | Meaning |
|---|---|---|
| `fetcher` | `fetcher` | spt-core runs your binary; it emits normalized history |
| `locate_normalize` | `locate_template`, `normalize_command` | spt-core locates the raw transcript, then runs your normalizer over it |
| `native` | — | The adapter pushes via `spt api history-log`; spt-core stores it |

spt-core has **no built-in transcript parser for any harness** — the adapter
always owns that knowledge.

## `[inject]` — input-injection methods

How text can be put in front of the agent, per activity state. Any
combination of `pty`, `hook`, `relay`, `http`:

```toml
[inject]
activity = ["hook"]            # non-disruptive while the agent is working
idle = ["pty", "hook"]
```

## `[identity]` — session identity

How the harness's session id is obtained:

```toml
[identity]
session_id_source = "post_spawn"   # "post_spawn" | "uuid_inject"
parent_ancestor_name = "my-harness"
```

`post_spawn`: discovered after spawn (process tree / wrapper hand-off), with
`parent_ancestor_name` as the process-tree anchor. `uuid_inject`: spt-core
injects a UUID the harness echoes back.

## `[pty_digest]` — live activity buffer patterns

For sessions spt-core hosts in its own terminal layer, the adapter may supply
regex patterns that segment the raw PTY byte stream into an at-a-glance
activity digest (`spt digest <id>`). The patterns are opaque to spt-core —
the same rule as command templates; spt-core compiles and runs them, never
understanding harness semantics.

```toml
[pty_digest]
input_pattern = "\\nUSER> "      # user-turn boundary (required)
agent_pattern = "\\nAGENT> "     # agent-turn boundary (required)
tool_patterns = [                 # optional: one regex per known tool,
  "(?P<name>Write)\\((?P<arg>[^)]*)\\)",   # naming `arg` (+ optional `name`)
]
catchall_pattern = "(?P<name>[A-Z][a-zA-Z]+)\\((?P<arg>[^)]*)\\)"  # optional
window_turns = 3                  # optional presentation override
persist = false                   # opt-in coarse activity log (default off)
```

Both boundary markers are required when the section is present; an absent
section just means the digest is unavailable for this adapter.

## `[update]` — adapter self-update

How spt-core updates (and first installs — install is the first update) this
adapter:

```toml
[update]
avenue = "delegated"                      # "delegated" | "file_pull"
command = "my-harness plugin update spt"  # delegated: the updater to run
self_verifies = true                      # delegated: attests the updater verifies its content
version_check = true                      # check min_spt_core_version before/after
uninstall = "my-harness plugin uninstall spt"   # optional inverse, run by `spt adapter remove`
```

| Avenue | Required fields | Meaning |
|---|---|---|
| `delegated` | `command` | spt-core delegates to the harness's own updater. Set `self_verifies = true` to attest that updater verifies what it installs — an unattested delegated update is skipped as unverifiable |
| `file_pull` | `repo`, `signing_key` | spt-core pulls files from `repo` (optionally filtered by `path_regex`) and verifies them against the adapter author's Ed25519 `signing_key` (64 hex chars) before applying |

With `file_pull`, **you** sign your releases with your own key; spt-core's
release keys never extend to adapter content.

## Shell adapters (`kind = "shell"`)

A shell adapter provides a **driven surface** (notifier, robot, sensor)
instead of hosting agents: same file, different body — the `[shell]` section
is required for (and exclusive to) `kind = "shell"`. See
[Shells: getting started](../shells/getting-started.md) for a worked,
shipping example; the field reference:

```toml
[shell]
spawn = "my-shell --link {link_token}"  # broker-launched; opaque template
ephemeral = false              # true -> no offline perch, no history retention
broadcast = "subnet"           # "subnet" | "same-node" | "none" (discovery scope)
command_receipt = "stdin"      # "http" | "stdin" | "relay" (how commands arrive)
pre_close = "park-and-save"    # optional instruction sent on link-break
close_timeout_ms = 3000        # graceful-termination window
persistent = true              # auto-online whenever the owner endpoint is online
wake_command = "my-waker --link {link_token}"  # offline wake-watcher; exit code 86 = wake
can_shutdown = false           # may the shell fire `api owner-shutdown`?
require_approval = "none"      # "none" | "remembered" | "always" (per-spawn gate)
max_instances_per_owner = 4    # optional cap (online + offline both count)
over_cap = "reject"            # "reject" | "approve" at the cap

[shell.capabilities]           # the agent->shell command vocabulary
notify = { args = ["title", "body"] }
clear  = {}

[shell.sensory]                # the shell->agent sensory vocabulary
types = ["event"]
```

The capability and sensory vocabularies live in the manifest — spt-core
resolves them by adapter name, validates agent commands against them, and
rejects anything outside the declared vocabulary. The shell binary binds with
`spt api … bind-shell --link <token>` (the link token *is* the credential)
and pushes sensory payloads with `spt api … emit`.

## Cross-field rules (`spt adapter add` enforces these)

The schema validates structure; registration additionally enforces:

- `adapter.name` and `adapter.version` must be non-empty.
- `kind = "shell"` **requires** a `[shell]` section; `kind = "harness"`
  **must not** have one.
- `[history] strategy = "fetcher"` requires `fetcher`;
  `locate_normalize` requires both `locate_template` and `normalize_command`.
- `[env.*] direction = "inject"` requires a `value`.
- `[update] avenue = "delegated"` requires `command`; `file_pull` requires
  `repo` **and** `signing_key`.
- `[pty_digest]`, when present, requires non-empty `input_pattern` and
  `agent_pattern`.

A violation is a one-line error naming the field — fix and re-add.


File created successfully at: C:\Users\decid\Documents\projects\spt-core\docs-site\src\harness-contract\manifest.md (file state is current in your context — no need to Read it back)

# Manifest reference

The runtime manifest is the declarative half of the harness contract: one TOML
file per adapter, declaring **only what varies per harness or shell**. This
page is the complete field reference.

Machine-readable companion: [`manifest.schema.json`](https://sabermage.github.io/spt-releases/manifest.schema.json)
— generated from the exact code that parses manifests, so it never drifts.
Validate your manifest against it, then `spt adapter add` enforces the
cross-field rules listed at the bottom.

## The principle

**SPT is not a harness.** Command templates are opaque strings — spt-core
never parses out a model, tool list, or flag; the adapter writes the full
command line and spt-core runs it with `{key}` substitution placeholders
filled. Anything spt-core owns is *not* in the manifest:

- **Sentinels** (idle markers, the echo gate) — managed via `spt api state` /
  `spt api echo-gate`; adapters only call them.
- **Spool, registry, perch, and daemon-state schemas.**
- **The event-block vocabulary** — the tags spt-core surfaces to agents are a
  fixed, documented constant. Adapters pass spt-core's output through
  unchanged.
- **File-drop filenames** — statically `<endpoint_id>-commune.md` /
  `<endpoint_id>-signoff.md`; only the watched *directory* is declared.
- **Config knobs** (pulse period, summarizer windows, …) — global spt-core
  settings with per-endpoint overrides, never per-adapter.

## `[adapter]` — header (required)

The only mandatory section, and it must be readable *before* any install or
update — `min_spt_core_version` is the compatibility gate.

```toml
[adapter]
name = "my-harness"                # the adapter_name every api call carries
kind = "harness"                   # "harness" (default) | "shell"
version = "1.0.0"
min_spt_core_version = "1.0.0"     # lowest spt-core this adapter tolerates
hostable_types = ["LiveAgent", "ReadyAgent", "Worker"]
```

| Field | Required | Meaning |
|---|---|---|
| `name` | yes | Adapter id; `--adapter <name>` on every machinery call |
| `kind` | no (default `harness`) | `harness` hosts agents; `shell` provides a driven surface |
| `version` | yes | The adapter's own version |
| `min_spt_core_version` | yes | Compat gate, checked before install/update |
| `hostable_types` | no | Endpoint types this adapter can host |

## `[hooks.<event>]` — inbound hook table

One entry per harness event, declaring the `spt api` command it fires, the
input fields it maps in, and whether the hook can surface text into the
agent's context.

```toml
[hooks.SessionStart]
fires = "api seed --pid {parent_pid} --session-id {session_id} --adapter {adapter_name}"
reads = ["session_id", "parent_pid"]
can_inject = true

[hooks.Stop]
fires = "api state idle"
can_inject = false     # no inject channel -> sentinel/relay fallback
```

| Field | Required | Meaning |
|---|---|---|
| `fires` | yes | Opaque `api …` command line the harness invokes for this event |
| `reads` | no | Input fields (e.g. from the hook's stdin payload) mapped into the command |
| `can_inject` | no (default `false`) | Whether this hook can inject context back to the agent. When `false`, spt-core falls back to its sentinel + relay/poll path instead of expecting injection |

`can_inject` is the single most load-bearing harness-varying fact — declare
it honestly per hook.

## `[session]` — watched dirs + role templates

Two watched-directory keys sit directly on `[session]`; the file *names* are
fixed by spt-core, only the directory varies:

```toml
[session]
commune_dir = ".my-harness"    # watched for <endpoint_id>-commune.md
signoff_dir = ".my-harness"    # watched for <endpoint_id>-signoff.md
```

Commune and signoff are **file-drops, not commands** — an agent writes a
markdown file; spt-core's watcher does the rest.

### `[session.<role>]` — outbound templates

One opaque command template per role. Model, tools, flags, permissions — all
live inside `command`, never as separate fields.

Roles: `self` (the agent's own session) · `psyche_init` / `psyche_resume`
(the endpoint's persistent-context companion) · `echo_commune` (the bounded
history summarizer for sessions that end without a signoff) · `signoff`
(final context save) · `notif` (endpoint-native notification render).

```toml
[session.psyche_init]
command = "my-harness run --agent psyche --name {session_name} --model cheap"
cwd = "{psyche_dir}"
env_remove = ["MY_HARNESS_SESSION_ID"]
recursion_guard_env = "SPT_ECHO_COMMUNE"
detach = true
keys = ["session_name", "psyche_dir"]
```

| Field | Required | Meaning |
|---|---|---|
| `command` | yes | Opaque command line with `{key}` placeholders |
| `cwd` | no | Working directory (substitutable) |
| `recursion_guard_env` | no | Env var set on summarizer children so *their* hooks bail (no summarizer-of-summarizer loops) |
| `detach` | no (default `false`) | Spawn detached |
| `env_remove` | no | Env vars stripped from the child's inherited environment |
| `keys` | no | The substitution keys spt-core fills for this role |

`notif` is the endpoint-native notification render — an OS toast, a status
LED, anything the adapter can run. Spawned detached when a notification
surfaces at this endpoint. Keys spt-core fills: `{notif_id}`, `{notif_from}`,
`{notif_subnet}`, `{notif_body}`.

```toml
[session.notif]
command = "powershell -Command New-BurntToastNotification -Text '{notif_from}','{notif_body}'"
keys = ["notif_id", "notif_from", "notif_subnet", "notif_body"]
```

## `[env.<VAR>]` — env-var table

Vars to inject into (or read from) sessions, and how. The injection channel is
asymmetric by hosting mode: **spt-hosted** sessions inherit env from the
broker that spawned them (no channel needed); **harness-hosted** sessions need
the harness's declared channel.

```toml
[env.MY_HARNESS_SESSION_ID]
direction = "inject"        # "inject" | "read"
value = "{session_id}"      # required for inject
channel = "MY_ENV_FILE"     # harness-hosted only
```

## `[history]` — transcript access

How spt-core reads a session's conversation history (it powers the
echo-commune summarizer). Three strategies; pick exactly one:

```toml
[history]
strategy = "fetcher"      # "fetcher" | "locate_normalize" | "native"
fetcher = "my-harness-history --session {session_id}"
```

| Strategy | Required fields | Meaning |
|---|---|---|
| `fetcher` | `fetcher` | spt-core runs your binary; it emits normalized history |
| `locate_normalize` | `locate_template`, `normalize_command` | spt-core locates the raw transcript, then runs your normalizer over it |
| `native` | — | The adapter pushes via `spt api history-log`; spt-core stores it |

spt-core has **no built-in transcript parser for any harness** — the adapter
always owns that knowledge.

## `[inject]` — input-injection methods

How text can be put in front of the agent, per activity state. Any
combination of `pty`, `hook`, `relay`, `http`:

```toml
[inject]
activity = ["hook"]            # non-disruptive while the agent is working
idle = ["pty", "hook"]
```

## `[identity]` — session identity

How the harness's session id is obtained:

```toml
[identity]
session_id_source = "post_spawn"   # "post_spawn" | "uuid_inject"
parent_ancestor_name = "my-harness"
```

`post_spawn`: discovered after spawn (process tree / wrapper hand-off), with
`parent_ancestor_name` as the process-tree anchor. `uuid_inject`: spt-core
injects a UUID the harness echoes back.

## `[pty_digest]` — live activity buffer patterns

For sessions spt-core hosts in its own terminal layer, the adapter may supply
regex patterns that segment the raw PTY byte stream into an at-a-glance
activity digest (`spt digest <id>`). The patterns are opaque to spt-core —
the same rule as command templates; spt-core compiles and runs them, never
understanding harness semantics.

```toml
[pty_digest]
input_pattern = "\\nUSER> "      # user-turn boundary (required)
agent_pattern = "\\nAGENT> "     # agent-turn boundary (required)
tool_patterns = [                 # optional: one regex per known tool,
  "(?P<name>Write)\\((?P<arg>[^)]*)\\)",   # naming `arg` (+ optional `name`)
]
catchall_pattern = "(?P<name>[A-Z][a-zA-Z]+)\\((?P<arg>[^)]*)\\)"  # optional
window_turns = 3                  # optional presentation override
persist = false                   # opt-in coarse activity log (default off)
```

Both boundary markers are required when the section is present; an absent
section just means the digest is unavailable for this adapter.

## `[update]` — adapter self-update

How spt-core updates (and first installs — install is the first update) this
adapter:

```toml
[update]
avenue = "delegated"                      # "delegated" | "file_pull"
command = "my-harness plugin update spt"  # delegated: the updater to run
self_verifies = true                      # delegated: attests the updater verifies its content
version_check = true                      # check min_spt_core_version before/after
uninstall = "my-harness plugin uninstall spt"   # optional inverse, run by `spt adapter remove`
```

| Avenue | Required fields | Meaning |
|---|---|---|
| `delegated` | `command` | spt-core delegates to the harness's own updater. Set `self_verifies = true` to attest that updater verifies what it installs — an unattested delegated update is skipped as unverifiable |
| `file_pull` | `repo`, `signing_key` | spt-core pulls files from `repo` (optionally filtered by `path_regex`) and verifies them against the adapter author's Ed25519 `signing_key` (64 hex chars) before applying |

With `file_pull`, **you** sign your releases with your own key; spt-core's
release keys never extend to adapter content.

## Shell adapters (`kind = "shell"`)

A shell adapter provides a **driven surface** (notifier, robot, sensor)
instead of hosting agents: same file, different body — the `[shell]` section
is required for (and exclusive to) `kind = "shell"`. See
[Shells: getting started](../shells/getting-started.md) for a worked,
shipping example; the field reference:

```toml
[shell]
spawn = "my-shell --link {link_token}"  # broker-launched; opaque template
ephemeral = false              # true -> no offline perch, no history retention
broadcast = "subnet"           # "subnet" | "same-node" | "none" (discovery scope)
command_receipt = "stdin"      # "http" | "stdin" | "relay" (how commands arrive)
pre_close = "park-and-save"    # optional instruction sent on link-break
close_timeout_ms = 3000        # graceful-termination window
persistent = true              # auto-online whenever the owner endpoint is online
wake_command = "my-waker --link {link_token}"  # offline wake-watcher; exit code 86 = wake
can_shutdown = false           # may the shell fire `api owner-shutdown`?
require_approval = "none"      # "none" | "remembered" | "always" (per-spawn gate)
max_instances_per_owner = 4    # optional cap (online + offline both count)
over_cap = "reject"            # "reject" | "approve" at the cap

[shell.capabilities]           # the agent->shell command vocabulary
notify = { args = ["title", "body"] }
clear  = {}

[shell.sensory]                # the shell->agent sensory vocabulary
types = ["event"]
```

The capability and sensory vocabularies live in the manifest — spt-core
resolves them by adapter name, validates agent commands against them, and
rejects anything outside the declared vocabulary. The shell binary binds with
`spt api … bind-shell --link <token>` (the link token *is* the credential)
and pushes sensory payloads with `spt api … emit`.

## Cross-field rules (`spt adapter add` enforces these)

The schema validates structure; registration additionally enforces:

- `adapter.name` and `adapter.version` must be non-empty.
- `kind = "shell"` **requires** a `[shell]` section; `kind = "harness"`
  **must not** have one.
- `[history] strategy = "fetcher"` requires `fetcher`;
  `locate_normalize` requires both `locate_template` and `normalize_command`.
- `[env.*] direction = "inject"` requires a `value`.
- `[update] avenue = "delegated"` requires `command`; `file_pull` requires
  `repo` **and** `signing_key`.
- `[pty_digest]`, when present, requires non-empty `input_pattern` and
  `agent_pattern`.

A violation is a one-line error naming the field — fix and re-add.


PowerShell

cd C:\Users\decid\Documents\projects\spt-core; foreach ($c in @("listen","bind-shell","state","echo-gate","poll","worker-start","worker-stop","worker-poll","boundary","session-end","presence","driven-by","history-log","emit","shutdown","owner-shutdown")) { "##### $c"; .\target\debug\spt.exe api --adapter x $c --help 2>&1 }

All api subcommand helps


##### listen
Consume a seed and hold the perch + relay loop (interim long-runner, T4)

Usage: spt.exe api --adapter <ADAPTER> listen [OPTIONS] <ID>

Arguments:
  <ID>

Options:
      --parent-pid <PARENT_PID>  Override the parent-pid anchor (defaults to the self-discovered PPID)
      --once                     Drain backlog + one receive cycle, then exit (testability)
      --subnet <SUBNET>          Home subnet for a NEW endpoint (REQ-INST-15: required on a multi-subnet node — home is assigned at creation, no guessing)
  -h, --help                     Print help
##### bind-shell
Shell-binary bind: the type=Shell flavor of `bind` (M5-D3b). Resolves the instance **by link token alone** (the spawn template carries only `{link_token}` — "owner from the link") and flips it online. The credential IS the auth: no token, no bind

Usage: spt.exe api --adapter <ADAPTER> bind-shell --link <LINK_TOKEN>

Options:
      --link <LINK_TOKEN>  The link token the broker minted at launch
  -h, --help               Print help
##### state
Set activity state busy|idle (+ echo-gate sentinel) (T5)

Usage: spt.exe api --adapter <ADAPTER> state [OPTIONS] <STATE> <ID>

Arguments:
  <STATE>  [possible values: busy, idle]
  <ID>

Options:
      --no-gate
      --token <TOKEN>            Capability token proving association to the target perch
      --session-id <SESSION_ID>  Session id proving association (matches the perch's info.json)
  -h, --help                     Print help
##### echo-gate
Manage the echo-gate sentinel directly (T5)

Usage: spt.exe api --adapter <ADAPTER> echo-gate [OPTIONS] <ACTION> <ID>

Arguments:
  <ACTION>  [possible values: set, clear]
  <ID>

Options:
      --token <TOKEN>            Capability token proving association to the target perch
      --session-id <SESSION_ID>  Session id proving association (matches the perch's info.json)
  -h, --help                     Print help
##### poll
Drain delivered messages (hook channel) (T5). With `--link` this is the shell-flavored relay drain (M5-D3c): the link token is the auth (CONTEXT §local-link authentication), and the drained rows are the shell's MAC-stamped command/text/file frames

Usage: spt.exe api --adapter <ADAPTER> poll [OPTIONS] <ID>

Arguments:
  <ID>

Options:
      --include-deferred
      --link <LINK>              Shell link token (the relay command-receipt drain)
      --token <TOKEN>            Capability token proving association to the target perch
      --session-id <SESSION_ID>  Session id proving association (matches the perch's info.json)
  -h, --help                     Print help
##### worker-start
Create a nested worker perch under a parent (T6)

Usage: spt.exe api --adapter <ADAPTER> worker-start [OPTIONS] <PARENT> <ID>

Arguments:
  <PARENT>
  <ID>

Options:
      --token <TOKEN>            Capability token proving association to the target perch
      --session-id <SESSION_ID>  Session id proving association (matches the perch's info.json)
  -h, --help                     Print help
##### worker-stop
Tear down a worker perch (T6)

Usage: spt.exe api --adapter <ADAPTER> worker-stop [OPTIONS] <ID>

Arguments:
  <ID>

Options:
      --token <TOKEN>            Capability token proving association to the target perch
      --session-id <SESSION_ID>  Session id proving association (matches the perch's info.json)
  -h, --help                     Print help
##### worker-poll
Drain a worker perch's messages (T6)

Usage: spt.exe api --adapter <ADAPTER> worker-poll [OPTIONS] <ID>

Arguments:
  <ID>

Options:
      --token <TOKEN>            Capability token proving association to the target perch
      --session-id <SESSION_ID>  Session id proving association (matches the perch's info.json)
  -h, --help                     Print help
##### boundary
Rebind the perch to a new session_id, preserving identity (T7)

Usage: spt.exe api --adapter <ADAPTER> boundary [OPTIONS] --to-session-id <TO_SESSION> <MODE> <ID>

Arguments:
  <MODE>  [possible values: clear, compact]
  <ID>

Options:
      --to-session-id <TO_SESSION>  The new session id to rebind the perch to
      --token <TOKEN>               Capability token proving association to the target perch
      --session-id <SESSION_ID>     Session id proving association (matches the perch's info.json)
  -h, --help                        Print help
##### session-end
Soft teardown (spool/history preserved); `--erase` hard-wipes (T7)

Usage: spt.exe api --adapter <ADAPTER> session-end [OPTIONS] <ID>

Arguments:
  <ID>

Options:
      --erase
      --token <TOKEN>            Capability token proving association to the target perch
      --session-id <SESSION_ID>  Session id proving association (matches the perch's info.json)
  -h, --help                     Print help
##### presence
Report presence (T7)

Usage: spt.exe api --adapter <ADAPTER> presence [OPTIONS] <ID>

Arguments:
  <ID>

Options:
      --token <TOKEN>            Capability token proving association to the target perch
      --session-id <SESSION_ID>  Session id proving association (matches the perch's info.json)
  -h, --help                     Print help
##### driven-by
Report which node (if any) is remote-driving this endpoint (M4-D5c)

Usage: spt.exe api --adapter <ADAPTER> driven-by [OPTIONS] <ID>

Arguments:
  <ID>

Options:
      --token <TOKEN>            Capability token proving association to the target perch
      --session-id <SESSION_ID>  Session id proving association (matches the perch's info.json)
  -h, --help                     Print help
##### history-log
Append to the native history store (Path-B) (T7)

Usage: spt.exe api --adapter <ADAPTER> history-log [OPTIONS] <ID>

Arguments:
  <ID>

Options:
      --token <TOKEN>            Capability token proving association to the target perch
      --session-id <SESSION_ID>  Session id proving association (matches the perch's info.json)
  -h, --help                     Print help
##### emit
Emit a Shell sensory payload to the owner's **live** session (M5-D3c). REST-only by definition: never spooled, dropped with a diagnostic when the owner isn't live. The link token is the auth

Usage: spt.exe api --adapter <ADAPTER> emit --type <TYPE> --link <LINK> <ID> <PAYLOAD>

Arguments:
  <ID>
  <PAYLOAD>  The sensory payload (descriptive text / encoded blob reference)

Options:
      --type <TYPE>
      --link <LINK>  Shell link token (the per-link credential from launch)
  -h, --help         Print help
##### shutdown
Graceful live-agent signoff (M2b): run the final echo-commune BEFORE teardown (3.3), then soft-stop. The `spt shutdown` lifecycle path

Usage: spt.exe api --adapter <ADAPTER> shutdown [OPTIONS] <ID>

Arguments:
  <ID>

Options:
      --token <TOKEN>            Capability token proving association to the target perch
      --session-id <SESSION_ID>  Session id proving association (matches the perch's info.json)
  -h, --help                     Print help
##### owner-shutdown
A shell suspends its linked owner directly, bypassing agent comms (M5-D4d, CONTEXT §Shell model machinery surface) — gated by the manifest `can_shutdown` pre-consent grant, fail-closed. The firing shell cascades offline with its siblings, by design

Usage: spt.exe api --adapter <ADAPTER> owner-shutdown --link <LINK> <ID>

Arguments:
  <ID>  The shell instance id (must match the link token's instance)

Options:
      --link <LINK>  Shell link token (the per-link credential from launch)
  -h, --help         Print help


Write

# 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 **every** `api` call:

1. **`--adapter <name>` is mandatory.** Every machinery invocation names the
   calling adapter — that's what keeps a multi-harness node unambiguous.
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).

```text
spt api --adapter <name> [--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 at
creation; spt-core never guesses).

### `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.

### `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 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`.


File created successfully at: C:\Users\decid\Documents\projects\spt-core\docs-site\src\harness-contract\api.md (file state is current in your context — no need to Read it back)

# 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 **every** `api` call:

1. **`--adapter <name>` is mandatory.** Every machinery invocation names the
   calling adapter — that's what keeps a multi-harness node unambiguous.
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).

```text
spt api --adapter <name> [--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 at
creation; spt-core never guesses).

### `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.

### `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 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`.


# Harness contract

The seam everything third-party builds against. spt-core contains zero
harness-specific logic; a harness (or a driven surface) interfaces through
exactly two things:

1. **The [runtime manifest](manifest.md)** — a declarative TOML file stating
   what varies for this harness: how to spawn sessions, which hook events
   fire which commands, how history is read, how the adapter updates.
   Command templates are opaque strings; spt-core fills `{key}` placeholders
   and runs them.
2. **The [`spt api` surface](api.md)** — the imperative entry points the
   harness's hooks fire to keep spt-core's state honest: session started,
   went idle, hit a context boundary, ended.

That's the whole integration surface. An adapter is a manifest plus the
harness's own native extension points — there is no SDK to link, no daemon to
embed, no protocol to speak beyond running `spt`.

```text
  your harness                         spt-core
 ┌────────────────────┐             ┌──────────────────────────┐
 │ hooks ─────────────┼── api … ───►│ perches · spools ·       │
 │ (SessionStart,     │             │ lifecycle · registry     │
 │  Idle, End, …)     │             │                          │
 │                    │◄────────────┼─ spawns [session.*]      │
 │ sessions           │  templates  │  templates, keys filled  │
 └────────────────────┘             └──────────────────────────┘
          ▲                                      ▲
          └────────── manifest.toml declares both seams
```

## Where to go

- **Start:** [the adapter quickstart](../quickstart/adapter.md) — take the
  reference mock adapter apart and drive the contract in minutes.
- **Reference:** [manifest](manifest.md) · [`spt api`](api.md) ·
  [`manifest.schema.json`](https://sabermage.github.io/spt-releases/manifest.schema.json).
- **Ship it:** [install-on-demand bootstrap](install-on-demand.md) — how an
  adapter brings spt-core with it.
- **Driven surfaces:** [Shells](../shells/overview.md) — the `kind = "shell"`
  flavor of the same contract.

Building adapters, shells, or integrations against this contract is
**unrestricted and royalty-free** — see the
[license split](https://github.com/SaberMage/spt-releases#license).


File created successfully at: C:\Users\decid\Documents\projects\spt-core\docs-site\src\harness-contract\overview.md (file state is current in your context — no need to Read it back)

# Harness contract

The seam everything third-party builds against. spt-core contains zero
harness-specific logic; a harness (or a driven surface) interfaces through
exactly two things:

1. **The [runtime manifest](manifest.md)** — a declarative TOML file stating
   what varies for this harness: how to spawn sessions, which hook events
   fire which commands, how history is read, how the adapter updates.
   Command templates are opaque strings; spt-core fills `{key}` placeholders
   and runs them.
2. **The [`spt api` surface](api.md)** — the imperative entry points the
   harness's hooks fire to keep spt-core's state honest: session started,
   went idle, hit a context boundary, ended.

That's the whole integration surface. An adapter is a manifest plus the
harness's own native extension points — there is no SDK to link, no daemon to
embed, no protocol to speak beyond running `spt`.

```text
  your harness                         spt-core
 ┌────────────────────┐             ┌──────────────────────────┐
 │ hooks ─────────────┼── api … ───►│ perches · spools ·       │
 │ (SessionStart,     │             │ lifecycle · registry     │
 │  Idle, End, …)     │             │                          │
 │                    │◄────────────┼─ spawns [session.*]      │
 │ sessions           │  templates  │  templates, keys filled  │
 └────────────────────┘             └──────────────────────────┘
          ▲                                      ▲
          └────────── manifest.toml declares both seams
```

## Where to go

- **Start:** [the adapter quickstart](../quickstart/adapter.md) — take the
  reference mock adapter apart and drive the contract in minutes.
- **Reference:** [manifest](manifest.md) · [`spt api`](api.md) ·
  [`manifest.schema.json`](https://sabermage.github.io/spt-releases/manifest.schema.json).
- **Ship it:** [install-on-demand bootstrap](install-on-demand.md) — how an
  adapter brings spt-core with it.
- **Driven surfaces:** [Shells](../shells/overview.md) — the `kind = "shell"`
  flavor of the same contract.

Building adapters, shells, or integrations against this contract is
**unrestricted and royalty-free** — see the
[license split](https://github.com/SaberMage/spt-releases#license).