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.
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:
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:
[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:
[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:
[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 covers them all.
3. Validate and register
Two layers of validation, both mechanical:
- Schema — your manifest must validate against
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 addparses, validates (including cross-field rules the schema can’t express), and registers in one step:
$ 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:
$ 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):
$ 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.)
5. Make it yours
- Copy
manifest.toml, setname,version, and your realhostable_types. - Point
[hooks.*]at the events your harness actually fires, with honestcan_injectvalues. - Replace each
[session.*].commandwith your harness’s real command line. - Pick the
[history]strategy your harness permits (binary that emits history →fetcher; transcript file on disk →locate_normalize; you push viaapi history-log→native). - Validate against the schema,
spt adapter addit, and fire thecapability/seedcalls above against your own manifest.
Building adapters against this contract is unrestricted and royalty-free — see the license split.
Next
- Checklist: the harness integration checklist — every contract surface grouped by necessity, mapped to the interaction lifecycle, plus the beyond-the-API integrations that make an adapter feel native. Work it top to bottom when building a real harness.
- Reference: the complete manifest reference
and
spt apireference. - How-to: ship spt-core with your adapter — the install-on-demand bootstrap pattern.
- Concept: where adapters sit in the mental model.