# SPT

Inter-agent messaging and live-agent lifecycle for Claude Code. Single Rust binary shipped as the `spt` plugin via the `cplugs` marketplace. Eventual goal: untether from Claude Code and run against arbitrary agent runtimes.

## Language

**SPT**:
The system. Canonical name. Acronym is poetic ("Sentience Pocket Transacter") and need not be widely known.
_Avoid_: Spacetime (retired codename), Sentience Pocket Transacter (poetic aside only)

**owl**:
Historical primitive name for the inter-agent messenger, inspired by carrier-messenger owls from Hogwarts. The Rust binary still ships as `owl.exe`, and the messaging subsystem lives in `src/owl/`. Today "owl" denotes the messaging primitive, not the system.
_Avoid_: messenger, courier (when discussing the primitive itself)

**`$OWL` / `$LIVE`**:
Auto-injected environment variables pointing at the binary. `$OWL` = `owl.exe`, `$LIVE` = `owl.exe live` (subcommand prefix). Agents and the user invoke the binary through these.

### Subsystems

**owl** (messaging substrate):
The lower layer. Provides perches, message delivery, registry, spool. A standalone agent can use owl with no live involvement. Lives in `src/owl/`.

**live** (live-agent lifecycle):
The upper layer, built on owl primitives. Adds the Psyche wrapper, communes, pulses, sign-off, orphan detection. Lives in `src/live/`. Cannot function without owl — uses owl messaging to wire Self ↔ Psyche.

### Perch and listener

**perch**:
An agent's registered identity and mailbox on disk. A directory under the owlery root containing a `ready` sentinel, `info.json`, a SQLite spool, and a TCP registry entry. Persistent: survives process death (then "stale"). Created by registration, destroyed by cleanup.

**poll listener**:
The running process that serves a Self's or ready agent's perch — a background task that drains the spool and emits `<EVENT>` lines to its parent session's stdout. Ephemeral: dies with its parent session.

**stale**:
The state of a perch whose owning background process is missing. Applies broadly: a Self or ready-agent perch with no live poll listener, a Psyche perch with no live wrapper, or a working perch whose subagent has ended its turn. Messages can still be delivered (spool fallback) and will be picked up if/when an owner re-arms.

**ephemeral perch**:
A short-lived perch created by `ring` for a perchless caller so the caller can receive the reply. Cleaned up when the ring call completes.

**working perch**:
A perch automatically created for each subagent of a live agent. SubagentStart hook creates it; PreToolUse hook injects inbound messages into the subagent's context; SubagentStop hook tears it down. Enables cross-communication between a Self and its subagents during subagent work.
_Avoid_: worker, worker perch (informal; "working perch" is canonical)

### Perch layout

Two layouts have existed:

- **flat** (legacy): every perch — Selves, Psyches, working perches — lived side-by-side directly under `$SPT_HOME/owlery/`.
- **nested** (current, since Phase 25): each Self's perch directory contains a `nested/` folder housing the perches of its Psyche and any active working perches. Top-level `owlery/` holds only Selves (and ready agents).

The migration from flat → nested ran through Phase 25 and 25.4. Code resolves both layouts during the migration window via the `perch_path` resolver and `ParentHint` enum.

**agent**:
The logical actor identified by an ID (e.g. `ling`). Has a perch; may or may not have a live poll listener at any moment.
_Avoid_: process (an agent is not a single process — Self, Psyche, and listener are distinct processes)

### Agent kinds

**ready agent**:
An agent that has a perch and a poll listener but no Psyche wrapper. The minimal SPT agent. Named for the `ready` sentinel in its perch.
_Avoid_: plain owl listener, plain listener, bare listener, lone owl (older code/docs still use these — rename pending)

**live agent**:
A Self + Psyche pair. The Self is a Claude Code session that also operates an owl listener; the Psyche is a wrapper process running periodic `claude -p --resume` sessions to give the Self memory, communes, and pulses.

**Self**:
The user-facing Claude Code session inside a live agent. Has its own perch and ID (e.g. `ling`).

**Psyche wrapper**:
The detached Rust supervisor process paired with a Self. Owns the `<self_id>-psyche` perch. Runs the pulse loop. Periodically invokes the Psyche via `claude -p --resume`. Reads `wrapper-state.json` for binary-handoff rehydration. Fails/recovers independently of the Psyche-as-agent.

**Psyche**:
The Claude agent that lives inside the wrapper. Identity = `{self_id}-psyche`. Memory persists across resumes via the embedded `psyche.md` prompt + the Claude `--resume` session id. Ephemeral *during each pulse invocation*; persistent in *identity*. Conceptually paired with a Self.

**Spine** (dormant):
A would-be supervisor agent responsible for monitoring all live agents and Psyche wrappers and taking action when they go down. Implemented (`live boot-spine <id>`, hidden CLI, spine wrapper + perch), but **not invoked in the standard live-agent flow** and has never been run in earnest. Psyche-wrapper development has prioritized self-sufficiency instead.

**Touch** (dormant):
A would-be sub-agent under a Spine, each Touch responsible for checking on one specific live agent. Implemented alongside Spine; also never run in earnest.

**generation**:
A monotonic counter on a live agent. Increments every time the live agent starts (`/spt:live`) or is revived (`/spt:revive`). Lets the Psyche's resumed session know it's a fresh continuation versus the previous instance.

### Live-agent vocabulary

**commune**:
A Self → Psyche delta update. Self writes `.claude/<self_id>-commune.md`; the wrapper detects the file, the Psyche ingests it during its next resume, and the file is deleted on success.

**echo commune**:
A commune authored by a background ephemeral Haiku agent (token-light) instead of the Self. The Haiku reads the latest discussion logs and synthesizes the delta. Still sent **to** the Psyche — same flow as a normal commune, just a different (automated) author.

**split echo commune**:
A newer variant of echo commune that fires when context is cleared (e.g. user runs `/clear`). Sends the delta to **both** the Psyche and the Self, so the Self's freshly-cleared context catches up without the user needing `/compact`.

**brief** (informal):
The Self-bound half of a split echo commune as the Self perceives it on arrival. Not a wire type — same `type="echo_commune"` envelope; "brief" is the situated meaning. Live agents may surface it to the user as "Catch-up Brief".

**signoff**:
Clean live-agent shutdown. Self writes `.claude/<self_id>-signoff.md` (empty body = plain signoff; non-empty body = prepended as FINAL COMMUNE). Wrapper tears down via the internal `INIT_SIGNOFF` protocol token.

**orphan**:
A live-agent state where the Psyche wrapper is still running but its Self is gone (Self's perch went stale and the underlying CC session is dead). The wrapper detects orphan condition and tears itself down.

**orphan grace period**:
A time buffer the wrapper waits before declaring orphan. Prevents false-positive teardown when the Self perch is briefly between poll cycles, or during a binary handoff (seamless transition from one SPT version to a newer one), or otherwise momentarily stale-looking.

**pulse**:
An event **inside the Psyche wrapper** that runs routine periodic tasks. Historically also "poked" the Psyche awake to think about ongoing work, but those pokes rarely produced useful output and now default off.

**alarm**:
A scheduled one-shot message stirring a Self to action at a given timestamp. Created via `$OWL new-alarm` / `/spt:new-alarm`. Delivered as `<EVENT type="alarm">`. Rides on the wrapper's pulse loop, so **alarms require a Psyche wrapper** — they do not work on ready agents.
_Avoid_: TIMED PULSE (wrapper-side jargon for the same thing; not user-facing)

### Networking (planned — research stage, see `docs/research/SPT Networked Messaging Research Brief.md`)

**node**:
A machine's participation in an spt subnet, identified by an Ed25519 public key generated on first run and persisted at `{SPT_HOME}/owlery/node.key`. Each node hosts some number of perches. Today SPT runs as if it were a single-node system; networking promotes "node" to a first-class concept.

**spt-node** (planned standalone daemon):
A small local daemon that runs the network stack (P2P transport, NAT traversal, pairing) on behalf of agents on the machine. Owns the node identity (Ed25519 keypair). Speaks SPT's wire protocol on a local socket so any client — SPT agents today, non-SPT clients eventually — can reach across the subnet by going through it. Chosen over baking the network stack into the SPT binary because it (a) decouples networking from SPT's CC-tied core, (b) creates SPT-protocol-as-product for non-SPT clients, (c) lets the network stack evolve on its own release cycle.

**spt subnet**:
The set of perches one user can address across one or more of their own nodes. No central server, no account, no shared infrastructure beyond bootstrap. Today's `$SPT_HOME/owlery/` is the local slice of a one-user, one-machine subnet; the networking feature lets that subnet span machines.

**cross-subnet contract** (very early concept):
A mechanism (not yet designed) by which two users can authorize a specific set of their agents to communicate across subnets — without merging their subnets. Future direction; only mentioned here so the term has a fixed meaning if it surfaces.

**agent name resolution** (planned):
Across an spt subnet, agents should still be addressable by their short ID (e.g. `ling`) — not by node pubkey. A lookup layer maps `<agent_id>` → `<node_id>:<perch_path>` within the subnet. Design open: registry shape, name-collision rules, and how local addressing stays node-implicit are all unsettled.

### Capsule (planned — v1.9, not yet realized)

**capsule**:
A hosting mode (planned for v1.9) where SPT launches Claude Code inside a psmux terminal session and drives it via keystroke injection. Moves the poll listener *out* of the Claude Code process tree and *alongside* the psmux session, letting SPT inject input directly into the CC prompt instead of waiting for the agent to ingest messages from a Monitor stream or PreToolUse hook. See `.planning/ROADMAP.md` Phases 19–22.

Three motivations:
1. Pre-Monitor-tool, the poll listener had to die after each Bash-tool message and be manually revived; capsule would have given a clean external listener. Monitor tool has since obviated this urgency.
2. Networked messaging — SPT aims to become a remote protocol so agents can be controlled from novel surfaces (VR, handhelds). Capsule is the substrate needed for full remote control (e.g. issuing `/clear`, injecting input). Networked messaging may ship first.
3. Agents driving their own and each other's sessions — e.g. clearing one's own context while seeding the next generation with new instructions.

**capsule listener**:
A variant of poll listener (Phase 19 plans it as a `poll.rs` fork) that runs *outside* the CC process tree alongside a specific psmux session. Drains the spool, delivers to the agent via psmux sendkeys when idle, respools to the inbox path when busy, and watches the claude PID for teardown. Effectively SPT's "user's hands at the keyboard" for that session.

**skeleton perch**:
A perch created by the capsule launcher *before* its agent (the CC session) has started — records the agent_id and launcher PID up front. The "registered but not yet alive" intermediate state. Recognized by SessionStart so the agent learns its identity on boot.

### Updates

**binary handoff**:
A mid-session, in-process upgrade of any long-running SPT process to the latest binary. When a new SPT version is deployed, each running poll listener and Psyche wrapper *self-migrates*: the original process spawns the new binary as a child, relays stdio so the parent CC session sees no interruption, and the new process takes over polling. Same flow for Psyche wrappers (rehydrating their state from `wrapper-state.json`). Goal: users never have to restart SPT to pick up new features.

### Identity binding

**session binding**:
The mechanism that ties an agent's identity (e.g. `ling`) to a Claude Code session's process tree. When an agent registers a perch, `info.json` records `parent_pid` — the pid of the CC session that launched the agent. On a subsequent SessionStart, `session-resume` walks perch `info.json` files and matches its own parent process pid against `info.parent_pid`. A match means "you are this agent; resume that identity."

Why parent_pid and not CC's session_id: CC session_id rotates on `/clear` and across crashes, but the CC process keeps running, so the *process tree* is the more stable handle on "the same logical agent seat" across session boundaries. A background Monitor or Bash task running across a `/clear` outlives the session_id change, enabling seamless SPT continuity.

Known unaccounted edge case: pid reuse on long-running machines. Not handled today; not observed in practice.

### Transports

**TCP**:
The primary transport. A registry maps perch IDs to TCP addresses; senders look up the target and push the body over the socket. Fast, push-based; requires the target to be live and listening.

**spool**:
A SQLite-backed durable queue per perch. The fallback transport when the target's listener isn't reachable. Pull-based: the target's poll listener drains the spool on each poll cycle. Slower than TCP but survives stale perches and listener restarts.

**inbox** (legacy):
A per-perch directory of message files from the original file-based messaging system. Largely a relic; current code still writes to it for compatibility, but the durable layer is the spool.

### Messaging verbs

**send**:
Fire-and-forget message delivery. The common operation: push a body at a target perch and return. Use `--reply-to <perch>` to reply to a prior incoming message. Formerly named `deliver`; the standalone `reply` command was folded into this flag.
_Avoid_: deliver, reply (retired)

**ring**:
Blocking request/reply. Send a message, then wait on the caller's perch for the target's reply. Builds on `send` plus a wait. If the caller has no perch, ring creates an ephemeral perch for the duration of the call. Alias: `ask`.
_Avoid_: send-and-wait

## Audience

Single user today (the author). No external users. The user almost never types the binary directly — agents do. User interaction is mostly in-session Bash via `$OWL` / `$LIVE`.
