Introduction
claude-spt is the Spacetime (spt) adapter for
Claude Code. You install one Claude Code plugin — sptc — and get three things at once:
- Agent messaging and live agents inside Claude Code: send messages between agents, run a
reachable “perch,” and drive long-lived live-agent sessions, all delegated to the
sptbinary. - An invisible
spt-coreinstaller. The first time the plugin loads, it installsspt-corefor you if it is missing. No separate setup step. - A casual on-ramp to spt-core. The plugin is the friendly front door to the wider Spacetime agent ecosystem — subnets, terminal hosting, seamless self-update — without leaving your editor.
Mental model
Claude Code provides the harness (hooks, skills, your prompt). The spt binary provides the
core (messaging, lifecycle, networking). claude-spt is the thin adapter between them: it
maps Claude Code’s hook events to the spt binary’s harness-contract entry points and surfaces
delivered messages back into your session.
Claude Code ──hook events──▶ sptc adapter ──spt api──▶ spt-core
(the harness) (this project) (messaging,
▲ thin glue live agents,
└────── additionalContext / skills ◀── renders ◀──── networking)
Two ideas follow from that picture, and they shape everything else in these docs:
- The adapter is thin by design. Logic and skill instructions live in the
sptbinary and its adapter manifest (conducted by spt-core), not in the plugin. The plugin ships skeletons; the operative content is delivered at run time. See Harness contract. - It is built against
spt-core’s public surface only — the publishedspt-releasesbinary, install scripts, and docs. That constraint is the point: it proves the adapter contract is buildable by anyone, from the published surface alone.
Status
This is an early, skeleton-honest build. Skills surface under the /sptc:* namespace; surfaces
that are not yet operative say so in place. Start with the Quickstart.
Quickstart
Goal: install the sptc plugin, watch it bring up spt-core for you, and confirm your session is
reachable — in under ten minutes. You need Claude Code and a shell (bash on macOS/Linux, Git Bash
on Windows).
1. Add the marketplace and install the plugin
From Claude Code:
/plugin marketplace add SaberMage/cplugs
/plugin install sptc@cplugs
Then restart the Claude Code session so the plugin’s SessionStart hook runs and populates the
session environment.
2. Let it install spt-core for you
On that first start, the plugin’s bootstrap installs spt-core if it is not already present —
this is the invisible-installer step, nothing for you to run. Confirm it landed:
spt --version
If spt is not yet on your PATH in this shell, it was installed to ~/.local/bin/spt (macOS /
Linux) or %LOCALAPPDATA%\spt-core\bin\spt.exe (Windows); open a fresh shell and try again.
3. Confirm your session is reachable
Each Claude Code session resolves its own agent identity. Check it:
spt whoami
That prints this session’s perch id (resolved from $OWL_SESSION_ID / $SPT_AGENT_ID). A printed
id means the adapter wired your session into Spacetime’s messaging fabric.
What works today, and what is coming
This is an early build. The plumbing above — install, invisible spt-core bootstrap, identity —
is operative now. The /sptc:* skills (send, ready, live, commune, and the rest) ship as
skeletons: their operative instructions are delivered by the adapter at invocation time rather
than baked into the plugin, and some are still being wired. Each skill says in place whether it is
operative yet.
Next, read Harness contract to see exactly how Claude Code hook
events map onto the spt binary.
Harness contract
claude-spt is glue: it maps Claude Code hook events to the spt binary’s harness-contract
inbound surface (spt api --adapter claude-spt <verb>). The binary is harness-agnostic; this
adapter is the Claude-Code-shaped edge of it.
The authoritative contract lives on spt-core’s published surface — the
harness-contract + CLI reference. This page documents
the adapter’s wiring: which Claude Code hook drives which spt api verb.
Hook → spt api mapping
The plugin ships a static hooks.json that routes every Claude Code hook event through one thin
wrapper, hooks/dispatch.sh <EventName>, which resolves the claude-spt program (from the adapter’s
[strings].hook_cmd = "{adapter_dir}/claude-spt hook", looked up once per session) and runs
claude-spt hook <EventName> with the Claude Code hook payload on stdin. The hook logic lives in
the program — so it updates with spt adapter update, and the plugin’s hook wiring stays fixed.
| Claude Code hook | handler | spt api verb (representative) | Purpose |
|---|---|---|---|
SessionStart | claude-spt hook SessionStart | seed / bind / boundary | Bootstrap spt-core (via dispatch), register the perch (bind spt-hosted · seed harness-hosted · boundary on clear/compact), then relay an agent-facing brief (see below); non-blocking — never listen. |
UserPromptSubmit | claude-spt hook UserPromptSubmit | state busy + poll | Mark the turn busy, drain delivered messages (incl. deferred) to the prompt as additionalContext, and inject a /sptc:… skill body when present. |
PreToolUse | claude-spt hook PreToolUse | state busy + poll | Mid-turn delivery: drain messages deferred while busy so a live agent receives them while working. |
Stop | claude-spt hook Stop | state (idle) | Mark the agent idle when a turn ends. |
SessionEnd | claude-spt hook SessionEnd | session-end | Tear down session state cleanly. |
SubagentStart | claude-spt hook SubagentStart | worker-* | Track a spawned subagent. |
SubagentStop | claude-spt hook SubagentStop | worker-* | Track subagent completion. |
PostToolUse (Write) | claude-spt hook PostToolUse | state idle + self-send | Detect a !!checkpoint!! commune Write and self-send the agent-driven checkpoint signal (spt-hosted live agents). |
Two invariants the handler holds
- Payload comes from stdin, never from a
/-leading argument. On Windows under Git Bash / MSYS, any argument beginning with/is silently rewritten to a Windows path. The dispatch wrapper passes the payload straight through on stdin (its only argument is the event name) and the program reads the Claude Code hook payload as JSON from stdin, so a/sptc:…token is never corrupted. - Messages are self-delimiting
<EVENT>envelopes.polloutput is rendered by splitting on the canonical<EVENT type="msg" from="…">body</EVENT>envelope, so a multi-message drain parses cleanly and each message keeps its sender for reply-correlation.
Identity
Every session resolves its own perch id via spt whoami, off $OWL_SESSION_ID / $SPT_AGENT_ID.
A session with no perch (never made reachable) simply delivers nothing — the per-prompt drain
no-ops rather than erroring.
SessionStart briefs
SessionStart also relays an agent-facing brief as additionalContext, composed from the
adapter’s [strings.briefs] (same file-backed/inline machinery as [strings.skills]). The hook
only selects + composes + {id}-substitutes — it never authors the prose.
| session state | trigger | brief |
|---|---|---|
| has a perch | bind ($SPT_ENDPOINT_ID) or boundary (clear/compact) | identity brief — who it is ({id}), that its perch is already live (don’t re-arm → COLLISION), and how to message (spt send + reply + the spt endpoint list roster). |
| no perch, node has subnet peers | seed / fresh startup | ring brief — how to reach other agents without a perch (spt ring <target> --timeout 60) + the roster. |
no perch and no peers · subagent (agent_type set) | — | nothing. |
The brief is liveness-agnostic (no live-vs-ready distinction) pending a published machine-readable
liveness query on the spt surface. The peer gate is a line-count presence check on spt subnet status — it never parses the human-formatted column values.
The operative skill instructions are not in this plugin. They are delivered by the adapter manifest (conducted by spt-core) at invocation time; the
/sptc:*SKILL.mdfiles are deliberately thin skeletons. This split keeps the marketplace artifact low-churn while logic and instructions update through spt-core’s signed adapter-update channel.
Alt accounts
Run a session under a different Anthropic account without logging out of the first — so when one account runs out of tokens, an endpoint can keep going on another.
Claude Code holds exactly one account per config root: /login replaces the credentials in the
active root, and claude auth offers only login/logout/status. So a second account means a second
config root, selected by CLAUDE_CONFIG_DIR. claude-spt owns those roots and calls them account
roots; an alt profile is an adapter profile whose only job is to point a session at one.
The layout
~/.claude-spt/
shared/ one node-wide continuity tree
projects/ todos/ file-history/ shell-snapshots/ session-env/
accounts/<account>/ = CLAUDE_CONFIG_DIR for one alt profile
.credentials.json real file — one login, shared by every endpoint on this account
.claude.json real file — identity + trust
projects/ … junctions into ../../shared/…
skills/ agents/ commands/ plugins/
junctions into your primary root's
settings.json a copy of your primary root's, taken at init
Two properties are load-bearing:
- Continuity is shared.
projects/and friends are directory junctions into one node-wide tree, so a session started on one account can be resumed on another — spt’s resume passes--resume <session_id>, and Claude Code reads that transcript out of whichever root is active. - Nothing account-bound is shared. Credentials and
.claude.jsonare real files in each root..claude.jsonis rewritten with a new file identity every 15-25 seconds while a session is live, so a link there would stop tracking on the first rewrite and the two stores would diverge silently — a refreshed token landing in one while the other rots.
history.jsonl is a file, so it cannot join the shared tree: prompt history does not follow an
account switch. That is the one accepted loss.
Your plugins ride along too: the plugin store — marketplaces, installs, caches — is junctioned
from your primary root, so a plugin installed once is present under every account. Which plugins are
enabled stays per-account, because settings.json is a copy. Without the shared store, the copied
settings would enable plugins the account root does not hold, and the session would come up with its
hooks and skills silently missing.
One account, many endpoints
An account root holds a login, not a session — every endpoint on that account shares the one login. Per-endpoint memory and identity are spt’s job at the mind layer, so endpoints do not need a root apiece (and a root apiece would cost a login apiece, which defeats the failover).
An endpoint keeps its id across profiles. The profile selects which account pays, never who is speaking.
Setup
There is none. The first launch of an endpoint on an alt profile notices the account root does
not exist yet and builds it right there: it creates the root and the shared tree, junctions the
continuity set and the authoring surface, copies settings.json, and seeds the trust map from your
current root (trust is per-config-root, and an unseeded root does not fail — it hangs on the
trust dialog). Every launch after the first finds the root in place and pays nothing: per-launch
maintenance is exactly the cost this design removes.
So the whole flow is: launch an endpoint on the profile and log in at Claude Code’s own prompt. A launch against a root with no credentials proceeds deliberately — Claude Code does the login, and the credentials land in that root for every later endpoint on the account.
The same one-shot setup also exists as a command, for building a root ahead of its first launch or repairing one (it is idempotent — re-running it fixes the missing piece and touches nothing else):
claude-spt alt init [account] # account defaults to "alt", the shipped profile's
Using it
The adapter ships one example profile, claude-spt:alt, bound to an account literally named alt:
spt endpoint run --adapter claude-spt:alt --id <id>
Every spawned role follows the profile — the session, its resume, the Psyche, and the end-of-session summarizer — because a failover that leaves the Psyche drawing on the exhausted account is not a failover.
Your own accounts
Add your own without waiting for an adapter release, using spt-core’s own profile machinery:
spt adapter create-profile claude-spt work # then point its commands at --account work
The first launch on the new profile builds its account root, exactly as with the shipped one. A
created profile is node-local and survives re-registration and adapter updates. Profiles are
static: spt has no runtime profile parameterization and no {account} fill key, so each profile
names its account literally — one profile per account.
What this is not
It does not swap providers or backends. The claude-spt:ccs profile (model/billing multiplexing
through ccs) is untouched and keeps its own roles.