/grill-with-docs this is a new project. need to create a PRD for spt-core. spt-core will be a rebuild of sister project claude_skill_owl. broad goals: -spt-core will be a framework wholly independent…


Bash

ls -la "C:/Users/decid/Documents/projects/spt-core/" 2>&1

List spt-core verbose


total 8
drwxr-xr-x 1 decid 197609 0 May 27 23:19 .
drwxr-xr-x 1 decid 197609 0 May 27 23:14 ..
drwxr-xr-x 1 decid 197609 0 May 27 23:35 .claude


ls "C:/Users/decid/Documents/projects/claude_skill_owl/src/" 2>&1 | head -40

List sister project src


cli.rs
common
lib.rs
live
main.rs
owl


1	# SPT
2
3	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.
4
5	## Language
6
7	**SPT**:
8	The system. Canonical name. Acronym is poetic ("Sentience Pocket Transacter") and need not be widely known.
9	_Avoid_: Spacetime (retired codename), Sentience Pocket Transacter (poetic aside only)
10
11	**owl**:
12	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.
13	_Avoid_: messenger, courier (when discussing the primitive itself)
14
15	**`$OWL` / `$LIVE`**:
16	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.
17
18	### Subsystems
19
20	**owl** (messaging substrate):
21	The lower layer. Provides perches, message delivery, registry, spool. A standalone agent can use owl with no live involvement. Lives in `src/owl/`.
22
23	**live** (live-agent lifecycle):
24	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.
25
26	### Perch and listener
27
28	**perch**:
29	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.
30
31	**poll listener**:
32	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.
33
34	**stale**:
35	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.
36
37	**ephemeral perch**:
38	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.
39
40	**working perch**:
41	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.
42	_Avoid_: worker, worker perch (informal; "working perch" is canonical)
43
44	### Perch layout
45
46	Two layouts have existed:
47
48	- **flat** (legacy): every perch — Selves, Psyches, working perches — lived side-by-side directly under `$SPT_HOME/owlery/`.
49	- **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).
50
51	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.
52
53	**agent**:
54	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.
55	_Avoid_: process (an agent is not a single process — Self, Psyche, and listener are distinct processes)
56
57	### Agent kinds
58
59	**ready agent**:
60	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.
61	_Avoid_: plain owl listener, plain listener, bare listener, lone owl (older code/docs still use these — rename pending)
62
63	**live agent**:
64	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.
65
66	**Self**:
67	The user-facing Claude Code session inside a live agent. Has its own perch and ID (e.g. `ling`).
68
69	**Psyche wrapper**:
70	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.
71
72	**Psyche**:
73	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.
74
75	**Spine** (dormant):
76	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.
77
78	**Touch** (dormant):
79	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.
80
81	**generation**:
82	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.
83
84	### Live-agent vocabulary
85
86	**commune**:
87	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.
88
89	**echo commune**:
90	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.
91
92	**split echo commune**:
93	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`.
94
95	**brief** (informal):
96	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".
97
98	**signoff**:
99	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.
100
101	**orphan**:
102	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.
103
104	**orphan grace period**:
105	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.
106
107	**pulse**:
108	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.
109
110	**alarm**:
111	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.
112	_Avoid_: TIMED PULSE (wrapper-side jargon for the same thing; not user-facing)
113
114	### Networking (planned — research stage, see `docs/research/SPT Networked Messaging Research Brief.md`)
115
116	**node**:
117	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.
118
119	**spt-node** (planned standalone daemon):
120	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.
121
122	**spt subnet**:
123	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.
124
125	**cross-subnet contract** (very early concept):
126	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.
127
128	**agent name resolution** (planned):
129	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.
130
131	### Capsule (planned — v1.9, not yet realized)
132
133	**capsule**:
134	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.
135
136	Three motivations:
137	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.
138	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.
139	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.
140
141	**capsule listener**:
142	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.
143
144	**skeleton perch**:
145	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.
146
147	### Updates
148
149	**binary handoff**:
150	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.
151
152	### Identity binding
153
154	**session binding**:
155	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."
156
157	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.
158
159	Known unaccounted edge case: pid reuse on long-running machines. Not handled today; not observed in practice.
160
161	### Transports
162
163	**TCP**:
164	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.
165
166	**spool**:
167	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.
168
169	**inbox** (legacy):
170	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.
171
172	### Messaging verbs
173
174	**send**:
175	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.
176	_Avoid_: deliver, reply (retired)
177
178	**ring**:
179	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`.
180	_Avoid_: send-and-wait
181
182	## Audience
183
184	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`.
185

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


Read enough. `spt-core/` is empty (.claude only). Sister project well-documented — modern spt already has the "untether from Claude Code" line in its own CONTEXT.md. Networking research brief solid (Iroh+mdns-sd rank 1).

Starting grill. One question at a time. Will update `CONTEXT.md` / open ADRs as terms resolve.

---

**Q1 — Deliverable shape.**

You called spt-core a "framework", "modular core", "SDK", *and* it ships a terminal wrapper binary + supports self-updating + plans a `spt-node` daemon next. That's several artifact shapes. Pick one to be canonical, since it changes everything downstream (API boundaries, FFI, release cadence, who owns the process):

- **(A) Rust workspace of library crates only.** `spt-core`, `spt-term`, `spt-msg`, etc. Consumers are Rust projects that link them. CLI binary is just a thin example/reference consumer. Non-Rust consumers must FFI or shell out.
- **(B) Headless daemon process + wire protocol.** spt-core ships as `sptd` (or similar) speaking a documented protocol over UDS/TCP. Any language client connects. The Rust crates exist but are implementation detail.
- **(C) Both — workspace of crates *and* a reference daemon built from them.** Rust projects link the crates directly (fast path). Other-language clients speak the wire protocol to the daemon (compat path). The terminal wrapper is its own binary that consumes core as a crate.

**Recommend (C).** Reasons: (1) `spt-node` is already named as a *separate* project — implies core ≠ daemon, daemon is a *use* of core; (2) networking-from-day-1 means a wire protocol is unavoidable anyway, so exposing it locally costs nothing extra; (3) lets `spt-node` be a thin daemon built on the same protocol as a future remote control surface; (4) keeps Rust-consumer perf path open while not forcing FFI on JS/Python/etc. Tradeoff: two surfaces to maintain version-compat between.

Which shape do you want canonical?


C. also because agent-based projects which integrate with spt-core's ecosystem will primarily (and, possibly: *solely*) leverage its binary for all necessary actions. for instance, spt *today* is a claude code plugin that *mostly* just fires owl.exe at different surfaces: either the agent running a command through it, it functioning as a poll listener via the Monitor tool, or various hooks tapping into it. many of today's common harnesses would support SPT via a similar shape. so one key deliverable of spt-core will be spt.exe (and/or equivalent unix binaries) which will replace owl.exe. but i.e. the messaging protocol and functions will exist as a library for other projects (and possibly spt-node) to integrate more deeply without just calling various commands of spt.exe.


<owl_reachability_notice>
You have an active owl perch. When spawning subagents or doing work, use run_in_background: true so you stay reachable for incoming messages.
</owl_reachability_notice>


CAVEMAN MODE ACTIVE (full). Drop articles/filler/pleasantries/hedging. Fragments OK. Code/commits/security: write normal.


Design spt-core binary and messaging library architecture


(C) locked. Writing CONTEXT.md with first resolved terms.


Write

# spt-core

Harness-independent core for the SPT ecosystem. Provides inter-agent messaging, live-agent lifecycle, terminal wrapping, self-update, and networking primitives — as both a Rust library workspace and a canonical reference binary. Designed so any agent runtime (Claude Code, Codex, Cursor, headless, future harnesses) can interface with the SPT ecosystem either by shelling out to the binary or by linking the crates directly.

Successor to `claude_skill_owl` (today's "modern SPT"), which is being rebuilt as `spt-core` to untether the system from Claude Code and lift it to a general-purpose agent-ecosystem core.

## Language

**spt-core**:
The system. Canonical name. The Rust workspace and the umbrella project.

**spt.exe / spt** (canonical binary):
The reference binary built from the workspace. Replaces today's `owl.exe`. Most external integrations (plugins, hooks, scripts in other harnesses) interact with spt-core *only* through this binary — fire-and-forget subcommands, long-running listeners under a parent harness's process supervisor, etc. Unix builds use the same name without `.exe`.

**library workspace**:
The set of Rust crates that compose spt-core. Consumers that want a deeper integration than shelling out to `spt.exe` link these crates directly. The reference binary is itself a consumer of the workspace. The expected first non-binary consumer is `spt-node` (planned standalone networking daemon, follow-on project).

**spt plugin** (planned follow-on):
A rebuilt version of today's Claude Code `spt` plugin, the first sub-project to be built atop spt-core. Reaches feature parity with today's modern SPT while delegating all core functionality to spt-core (primarily via `spt.exe`, with deeper hooks where useful).

**spt-node** (planned follow-on):
The second sub-project. A standalone local daemon owning a node's identity and networking stack, built directly on spt-core's library crates rather than shelling out. Speaks SPT's wire protocol so any client can reach across machines through it.

### Deliverable shape

spt-core ships **both** a library workspace and a canonical binary:

- **Library crates** — the deeper integration path. Used by sub-projects that link Rust directly (`spt-node`, future first-party services).
- **`spt.exe` / `spt`** — the canonical binary, built from the workspace. The primary integration path for harness plugins and external tooling, which mostly fire it as a subprocess at various surfaces (one-shot commands, poll listeners under a Monitor-tool-equivalent, hook tap-ins).

Both surfaces are first-class. Wire-protocol parity between them is a versioning concern from day one (a non-Rust client speaking to `spt.exe` and a Rust client linking the crates must see the same observable behavior).


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

# spt-core

Harness-independent core for the SPT ecosystem. Provides inter-agent messaging, live-agent lifecycle, terminal wrapping, self-update, and networking primitives — as both a Rust library workspace and a canonical reference binary. Designed so any agent runtime (Claude Code, Codex, Cursor, headless, future harnesses) can interface with the SPT ecosystem either by shelling out to the binary or by linking the crates directly.

Successor to `claude_skill_owl` (today's "modern SPT"), which is being rebuilt as `spt-core` to untether the system from Claude Code and lift it to a general-purpose agent-ecosystem core.

## Language

**spt-core**:
The system. Canonical name. The Rust workspace and the umbrella project.

**spt.exe / spt** (canonical binary):
The reference binary built from the workspace. Replaces today's `owl.exe`. Most external integrations (plugins, hooks, scripts in other harnesses) interact with spt-core *only* through this binary — fire-and-forget subcommands, long-running listeners under a parent harness's process supervisor, etc. Unix builds use the same name without `.exe`.

**library workspace**:
The set of Rust crates that compose spt-core. Consumers that want a deeper integration than shelling out to `spt.exe` link these crates directly. The reference binary is itself a consumer of the workspace. The expected first non-binary consumer is `spt-node` (planned standalone networking daemon, follow-on project).

**spt plugin** (planned follow-on):
A rebuilt version of today's Claude Code `spt` plugin, the first sub-project to be built atop spt-core. Reaches feature parity with today's modern SPT while delegating all core functionality to spt-core (primarily via `spt.exe`, with deeper hooks where useful).

**spt-node** (planned follow-on):
The second sub-project. A standalone local daemon owning a node's identity and networking stack, built directly on spt-core's library crates rather than shelling out. Speaks SPT's wire protocol so any client can reach across machines through it.

### Deliverable shape

spt-core ships **both** a library workspace and a canonical binary:

- **Library crates** — the deeper integration path. Used by sub-projects that link Rust directly (`spt-node`, future first-party services).
- **`spt.exe` / `spt`** — the canonical binary, built from the workspace. The primary integration path for harness plugins and external tooling, which mostly fire it as a subprocess at various surfaces (one-shot commands, poll listeners under a Monitor-tool-equivalent, hook tap-ins).

Both surfaces are first-class. Wire-protocol parity between them is a versioning concern from day one (a non-Rust client speaking to `spt.exe` and a Rust client linking the crates must see the same observable behavior).


CONTEXT.md seeded. On to Q2.

---

**Q2 — Build approach: hard fork vs rename-evolve.**

`spt-core/` dir empty. Sister `claude_skill_owl/` has working Rust workspace (`src/owl/`, `src/live/`, etc.) at v1.11.18 with active phases. Two paths:

- **(A) Hard fork / clean-room rewrite.** Start empty crate. Cherry-pick designs + lift specific modules manually as you reach them. claude_skill_owl keeps shipping until spt-core hits parity. Migration is a flag-day for users (or never — old plugin retires when new spt-plugin built atop spt-core hits parity).
- **(B) Rename + refactor-in-place.** Move `claude_skill_owl` source into `spt-core`, then iteratively restructure: extract crates, untether CC bits, layer in new features. Same git history. claude_skill_owl repo becomes spt-core or gets archived.
- **(C) Hybrid: fresh workspace structure, but bulk-port working code into it.** Empty `spt-core/Cargo.toml` workspace, set up crate boundaries first (`spt-core`, `spt-msg`, `spt-live`, `spt-term`, etc.), then transplant existing modules into their new homes adapter-by-adapter. New history, but no clean-room work where the old code already does the job.

**Recommend (C).** Reasons: (1) the crate-boundary design IS the rebuild's main value — doing it greenfield forces honest decoupling that a rename-in-place would let you skip; (2) untethering from CC isn't a refactor, it's an inversion (hooks/skills become *consumers* of spt-core's adapter API, not embedded callers) — easier in a fresh workspace than retrofitting; (3) most of owl/live's *logic* (TCP registry, spool SQL, perch layout, Psyche pulse loop) is sound and worth porting verbatim — clean-room would burn weeks reinventing it. Tradeoff: dual maintenance window — claude_skill_owl keeps shipping fixes until the new spt-plugin reaches parity. Need a freeze date for owl-side feature work.

Which approach?


C. also because agent-based projects which integrate with spt-core's ecosystem will primarily (and, possibly: *solely*) leverage its binary for all necessary actions. for instance, spt *today* is a cl…


i'm leaning A, but feel free to challenge me. my core reasoning is that, although spt works well today, it's extremely fragile. each new update introduces 2-5 new bugs that need handled in post, often affecting the functionality in unprecedented ways. it was also built on a much different starting point, and evolved rapidly. a lot of its concepts are firm now, but there's tech debt from that evolution. the systems can be built out much more cleanly if done from scratch, and in a safer way where things aren't so volatile.


Challenge follows. Three things to push back on, then refined A.

**Pushback 1: don't conflate "clean architecture" with "rewrite every line."**

Fragility lives in foundations (cross-cutting state, hook interference, race conditions between poll-listener / Psyche / SubagentStart). Not in *every* artifact. Some pieces are commodity and stable:

- SQLite spool schema
- TCP registry wire protocol
- `<EVENT>` envelope spec
- `info.json` perch metadata shape
- Magic constants like orphan grace period, poll cadence math

Clean-room rewriting these reintroduces bugs that took months to find. Recommend: full clean-room on **architecture** (crate boundaries, lifecycle invariants, hook surfaces, endpoint type system, terminal wrapper) but **copy-verbatim** the boring commodity layer (wire formats, SQL DDL, envelope grammar). Same shape, fresh implementations atop it.

**Pushback 2: rediscovery tax.**

You personally hit dozens of edge cases over the sister project's lifetime — pid reuse, ephemeral perch lifetimes in `ring`, binary handoff state migration, orphan false-positives during `/clear`, working perch teardown ordering. Clean-room re-derives all of these from scratch. Mitigation: before clean-room work begins, harvest those edge cases into a **`KNOWN-HAZARDS.md`** doc — invariant by invariant, with the failure mode that motivated each. That doc becomes a test checklist for spt-core. Otherwise A burns weeks re-finding bugs the sister project already paid for.

**Pushback 3: dual-maintenance is the silent cost.**

A means claude_skill_owl keeps shipping until spt-plugin (built atop spt-core) reaches parity. That's months of dual fixes. No mitigation makes that free — you need a *freeze policy* up front:

- **Hard freeze**: only security/breakage fixes on claude_skill_owl from today.
- **Soft freeze**: features continue but documented as "won't port."
- **No freeze**: full parallel maintenance.

Soft and no-freeze both expand the parity moving target. Hard freeze means you live with claude_skill_owl's current bugs until spt-plugin lands.

---

**Refined Q2.** Confirm A with these three baked in:
1. Clean-room architecture, copy-verbatim the commodity wire/schema layer.
2. Write `KNOWN-HAZARDS.md` from sister project before any core code.
3. Pick a freeze policy now.

A still, with the above? And which freeze (recommend **hard freeze** — every fix in claude_skill_owl during the rebuild is wasted work you also have to re-think for spt-core's cleaner shape)?


confirm direction. hard freeze on claude_skill_owl barring breakage fixes.


A confirmed with mitigations + hard freeze. This is ADR-worthy: hard to reverse, surprising to future readers (fresh repo shipping alongside live sister project), real trade-off just walked. Writing it.


1	# ADR Format
2
3	ADRs live in `docs/adr/` and use sequential numbering: `0001-slug.md`, `0002-slug.md`, etc.
4
5	Create the `docs/adr/` directory lazily — only when the first ADR is needed.
6
7	## Template
8
9	```md
10	# {Short title of the decision}
11
12	{1-3 sentences: what's the context, what did we decide, and why.}
13	```
14
15	That's it. An ADR can be a single paragraph. The value is in recording *that* a decision was made and *why* — not in filling out sections.
16
17	## Optional sections
18
19	Only include these when they add genuine value. Most ADRs won't need them.
20
21	- **Status** frontmatter (`proposed | accepted | deprecated | superseded by ADR-NNNN`) — useful when decisions are revisited
22	- **Considered Options** — only when the rejected alternatives are worth remembering
23	- **Consequences** — only when non-obvious downstream effects need to be called out
24
25	## Numbering
26
27	Scan `docs/adr/` for the highest existing number and increment by one.
28
29	## When to offer an ADR
30
31	All three of these must be true:
32
33	1. **Hard to reverse** — the cost of changing your mind later is meaningful
34	2. **Surprising without context** — a future reader will look at the code and wonder "why on earth did they do it this way?"
35	3. **The result of a real trade-off** — there were genuine alternatives and you picked one for specific reasons
36
37	If a decision is easy to reverse, skip it — you'll just reverse it. If it's not surprising, nobody will wonder why. If there was no real alternative, there's nothing to record beyond "we did the obvious thing."
38
39	### What qualifies
40
41	- **Architectural shape.** "We're using a monorepo." "The write model is event-sourced, the read model is projected into Postgres."
42	- **Integration patterns between contexts.** "Ordering and Billing communicate via domain events, not synchronous HTTP."
43	- **Technology choices that carry lock-in.** Database, message bus, auth provider, deployment target. Not every library — just the ones that would take a quarter to swap out.
44	- **Boundary and scope decisions.** "Customer data is owned by the Customer context; other contexts reference it by ID only." The explicit no-s are as valuable as the yes-s.
45	- **Deliberate deviations from the obvious path.** "We're using manual SQL instead of an ORM because X." Anything where a reasonable reader would assume the opposite. These stop the next engineer from "fixing" something that was deliberate.
46	- **Constraints not visible in the code.** "We can't use AWS because of compliance requirements." "Response times must be under 200ms because of the partner API contract."
47	- **Rejected alternatives when the rejection is non-obvious.** If you considered GraphQL and picked REST for subtle reasons, record it — otherwise someone will suggest GraphQL again in six months.
48

# ADR Format

ADRs live in `docs/adr/` and use sequential numbering: `0001-slug.md`, `0002-slug.md`, etc.

Create the `docs/adr/` directory lazily — only when the first ADR is needed.

## Template

```md
# {Short title of the decision}

{1-3 sentences: what's the context, what did we decide, and why.}
```

That's it. An ADR can be a single paragraph. The value is in recording *that* a decision was made and *why* — not in filling out sections.

## Optional sections

Only include these when they add genuine value. Most ADRs won't need them.

- **Status** frontmatter (`proposed | accepted | deprecated | superseded by ADR-NNNN`) — useful when decisions are revisited
- **Considered Options** — only when the rejected alternatives are worth remembering
- **Consequences** — only when non-obvious downstream effects need to be called out

## Numbering

Scan `docs/adr/` for the highest existing number and increment by one.

## When to offer an ADR

All three of these must be true:

1. **Hard to reverse** — the cost of changing your mind later is meaningful
2. **Surprising without context** — a future reader will look at the code and wonder "why on earth did they do it this way?"
3. **The result of a real trade-off** — there were genuine alternatives and you picked one for specific reasons

If a decision is easy to reverse, skip it — you'll just reverse it. If it's not surprising, nobody will wonder why. If there was no real alternative, there's nothing to record beyond "we did the obvious thing."

### What qualifies

- **Architectural shape.** "We're using a monorepo." "The write model is event-sourced, the read model is projected into Postgres."
- **Integration patterns between contexts.** "Ordering and Billing communicate via domain events, not synchronous HTTP."
- **Technology choices that carry lock-in.** Database, message bus, auth provider, deployment target. Not every library — just the ones that would take a quarter to swap out.
- **Boundary and scope decisions.** "Customer data is owned by the Customer context; other contexts reference it by ID only." The explicit no-s are as valuable as the yes-s.
- **Deliberate deviations from the obvious path.** "We're using manual SQL instead of an ORM because X." Anything where a reasonable reader would assume the opposite. These stop the next engineer from "fixing" something that was deliberate.
- **Constraints not visible in the code.** "We can't use AWS because of compliance requirements." "Response times must be under 200ms because of the partner API contract."
- **Rejected alternatives when the rejection is non-obvious.** If you considered GraphQL and picked REST for subtle reasons, record it — otherwise someone will suggest GraphQL again in six months.


# Clean-room fork from claude_skill_owl with hard freeze on the sister project

## Status

accepted (2026-05-28)

## Context

spt-core is the rebuild of `claude_skill_owl` (today's modern SPT) into a harness-independent core. The sister project works but is fragile: each feature release introduces 2–5 regressions, often in unprecedented ways, because the substrate grew rapidly from a much narrower starting point and carries the resulting tech debt. spt-core also commits day-one to several architectural shapes (endpoint type system, networking, terminal wrapper as backbone, untethering from Claude Code) that change foundational invariants the sister project assumes.

## Decision

Build spt-core as a clean-room fork. Empty workspace, new crate boundaries, fresh lifecycle invariants. Rejected alternatives: rename-and-evolve in-place (preserves the fragility) and a hybrid crate-restructure-with-bulk-port (still drags fragile foundations into the new shape).

**Mitigations baked in:**

1. **Copy-verbatim the commodity layer.** Wire formats (`<EVENT>` envelope grammar, TCP registry RPC), the SQLite spool schema/DDL, `info.json` perch metadata shape, and self-contained utility modules are stable and well-tested. Reimplementing them clean-room reintroduces bugs that took months to find in the sister project. Clean-room applies to architecture and lifecycle, not to every artifact.

2. **Harvest hazards before coding.** Author `KNOWN-HAZARDS.md` from the sister project's invariants and historical bug surface (pid reuse, ephemeral perch lifetimes in `ring`, binary handoff state migration, orphan false-positives during `/clear`, working perch teardown ordering, etc.). It becomes a test checklist for spt-core so rediscovery cost is paid once.

3. **Hard freeze on claude_skill_owl from 2026-05-28.** Only security and breakage fixes during the rebuild — no new features, no refactors. Soft and no-freeze policies expand the parity moving target indefinitely; hard freeze accepts living with the sister project's current bugs in exchange for finishing.

## Consequences

- Dual-maintenance burden is bounded by the freeze, not eliminated. Any breakage fix landed on claude_skill_owl must be considered for porting into spt-core's design (often shape-different, sometimes obsolete).
- The sister project keeps shipping until the rebuilt spt plugin (built atop spt-core) reaches feature parity. Until then, the user runs claude_skill_owl in production.
- The "feature parity" bar is whatever the sister project does on the freeze date — not a moving target.


File created successfully at: C:\Users\decid\Documents\projects\spt-core\docs\adr\0001-clean-room-fork-from-claude-skill-owl.md (file state is current in your context — no need to Read it back)

# Clean-room fork from claude_skill_owl with hard freeze on the sister project

## Status

accepted (2026-05-28)

## Context

spt-core is the rebuild of `claude_skill_owl` (today's modern SPT) into a harness-independent core. The sister project works but is fragile: each feature release introduces 2–5 regressions, often in unprecedented ways, because the substrate grew rapidly from a much narrower starting point and carries the resulting tech debt. spt-core also commits day-one to several architectural shapes (endpoint type system, networking, terminal wrapper as backbone, untethering from Claude Code) that change foundational invariants the sister project assumes.

## Decision

Build spt-core as a clean-room fork. Empty workspace, new crate boundaries, fresh lifecycle invariants. Rejected alternatives: rename-and-evolve in-place (preserves the fragility) and a hybrid crate-restructure-with-bulk-port (still drags fragile foundations into the new shape).

**Mitigations baked in:**

1. **Copy-verbatim the commodity layer.** Wire formats (`<EVENT>` envelope grammar, TCP registry RPC), the SQLite spool schema/DDL, `info.json` perch metadata shape, and self-contained utility modules are stable and well-tested. Reimplementing them clean-room reintroduces bugs that took months to find in the sister project. Clean-room applies to architecture and lifecycle, not to every artifact.

2. **Harvest hazards before coding.** Author `KNOWN-HAZARDS.md` from the sister project's invariants and historical bug surface (pid reuse, ephemeral perch lifetimes in `ring`, binary handoff state migration, orphan false-positives during `/clear`, working perch teardown ordering, etc.). It becomes a test checklist for spt-core so rediscovery cost is paid once.

3. **Hard freeze on claude_skill_owl from 2026-05-28.** Only security and breakage fixes during the rebuild — no new features, no refactors. Soft and no-freeze policies expand the parity moving target indefinitely; hard freeze accepts living with the sister project's current bugs in exchange for finishing.

## Consequences

- Dual-maintenance burden is bounded by the freeze, not eliminated. Any breakage fix landed on claude_skill_owl must be considered for porting into spt-core's design (often shape-different, sometimes obsolete).
- The sister project keeps shipping until the rebuilt spt plugin (built atop spt-core) reaches feature parity. Until then, the user runs claude_skill_owl in production.
- The "feature parity" bar is whatever the sister project does on the freeze date — not a moving target.


ADR-0001 written. On to Q3.

---

**Q3 — How much does spt-core know about agents?**

"Untether from Claude Code" decides API shape. Three abstractions:

- **(A) Pure messaging substrate.** spt-core knows perches, delivery, spool, networking, terminal-wrap. **Knows nothing about agents.** Live-agent / Psyche / pulse / commune / alarm / SubagentStart-style hooks all become *consumer-side* concerns. Each harness layer (spt-plugin for CC, future Codex plugin, etc.) builds the live-agent lifecycle on top.
- **(B) Agent abstraction in core.** spt-core defines an `AgentRuntime` trait (or equivalent — "what does it mean to invoke an agent session, drive it, receive its output, send a delta-update"). Psyche/live/commune/pulse concepts exist *in core*, parameterized over the runtime. Harness implements one trait, gets full live-agent stack for free.
- **(C) Bundled runtimes + open trait.** Same trait as (B), but spt-core ships first-party adapters for known runtimes (`spt-runtime-cc`, `spt-runtime-headless`, etc.) as separate crates. Third-party harnesses implement the trait themselves.

**Recommend (B).** Reasons:
- You explicitly named `LiveAgent` as a day-one endpoint type. That commits live-agent lifecycle to *core*, not to a plugin. (A) is inconsistent with your goal.
- (C) bundles harness-specific code in spt-core's repo. That's the exact coupling you said you want gone. CC adapter belongs in spt-plugin's repo, Codex adapter in a hypothetical spt-codex repo, etc.
- (B) lets harness-specific crates live *outside* spt-core while still consuming the full lifecycle. Symmetric for any future harness. The trait IS the untethering.

Tradeoff: trait surface design is the hardest API work in the project. Get it wrong and adapters re-implement workarounds for missing affordances. Mitigation: design the trait *after* the second adapter starts (CC-plugin first, then a headless or Codex spike) — two implementations is the minimum to find the right abstraction.

(B) confirm? Or different shape?


B, yes. note that `Psyche` would also be a day-one endpoint type. `Worker` would as well. (to reach psyche and subagent perches, respectively) --- for B to work, the consumer projects (spt-plugin) need to do something like define configs or manifest files. those files would be fed to i.e. `spt.exe --manifest live-manifest.file live start` and define how SPT should invoke agents, look up conversation history (echo communes), etc. where the framework needs to. one example is using the claude code binary for psyche and echo communes. in that example, (spt-plugin) could wrap `spt.exe --manifest <file> live` all into the `$LIVE` env var. --- that's just an example of how some dependent project might implement the binary into its hooks and calls.


Confirmed (B) + manifests + Psyche/Worker as day-one types. Updating CONTEXT.md with new terms.


Edit

Both surfaces are first-class. Wire-protocol parity between them is a versioning concern from day one (a non-Rust client speaking to `spt.exe` and a Rust client linking the crates must see the same observable behavior).

Both surfaces are first-class. Wire-protocol parity between them is a versioning concern from day one (a non-Rust client speaking to `spt.exe` and a Rust client linking the crates must see the same observable behavior).

## Runtime model

spt-core is harness-independent: it does not know about Claude Code, Codex, Cursor, or any other agent runtime. All harness-specific surfaces (how to invoke an agent session, fetch conversation history for an echo commune, detect activity/idleness, etc.) are abstracted behind a runtime layer that consumers supply.

**AgentRuntime** (Rust trait, implementation detail):
The internal Rust abstraction over a harness. Anything spt-core needs to do *to* or *with* an agent goes through this trait. Most consumers never see it directly — they configure spt-core via a manifest, and spt-core's default `ManifestRuntime` implementation executes against the manifest.

**runtime manifest**:
A declarative configuration file (TOML/YAML — schema TBD) that tells spt-core how to drive a specific harness. Declares: how to invoke an agent session, how to look up conversation history for an echo commune, how to detect/inject input, which binary or command implements each harness-side operation, and which endpoint types this harness supports. The primary integration path for consumer projects (e.g. spt-plugin).

Example shape (illustrative): `spt.exe --manifest spt-plugin.toml live start <id>`. A harness like the planned spt plugin wraps this invocation into the `$LIVE` / `$OWL` environment variables it injects into its sessions, so harness-internal callers continue to invoke `$LIVE` / `$OWL` unchanged.

### Endpoint types

Each perch advertises an **endpoint type** — a tag that says what shape of entity lives at that perch and what operations it accepts. The set of day-one types:

**ReadyAgent**:
Minimal SPT participant — a perch + a poll listener, no Psyche, no live-agent wrapper. Direct heir to the sister project's "ready agent".

**LiveAgent**:
A Self with a Psyche companion. Composite logical actor; addressable as one ID, but its component perches (the Self's, the Psyche's) live independently. Direct heir to the sister project's "live agent".

**Psyche**:
The Psyche companion's own perch, distinct from its paired LiveAgent's perch. First-class endpoint type so messages addressed to a LiveAgent's Psyche route directly without ambiguity.

**Worker**:
A subagent's perch under a parent LiveAgent. Created on subagent start, torn down on subagent stop. Replaces today's "working perch" concept; first-class type so cross-communication between a Self and its workers (and worker↔worker) is addressable.

**SptNode**:
A machine's participation in an SPT subnet. Owned by the planned `spt-node` daemon; identified by an Ed25519 public key generated on first run. First-class so networking primitives can address nodes directly as message targets, not only as transport peers.

**PresenceChannel** (novel concept):
An endpoint type designed for "find the user." A PresenceChannel collects presence signals across the user's nodes and lets an agent on any node figure out *which* machine the user most recently interacted with, then deliver a message via a presence-blueprint-specific delivery surface (notification, terminal, etc.). Distinguishes "deliver to the user" from "deliver to a specific machine."

The endpoint type system is **open**: harnesses and downstream projects may register additional types beyond the day-one set. Closed-vs-open semantics for capability advertisement (what operations each type accepts, how routing decides eligibility) are deferred to the design phase.


The file C:\Users\decid\Documents\projects\spt-core\CONTEXT.md has been updated successfully. (file state is current in your context — no need to Read it back)

Both surfaces are first-class. Wire-protocol parity between them is a versioning concern from day one (a non-Rust client speaking to `spt.exe` and a Rust client linking the crates must see the same observable behavior).

## Runtime model

spt-core is harness-independent: it does not know about Claude Code, Codex, Cursor, or any other agent runtime. All harness-specific surfaces (how to invoke an agent session, fetch conversation history for an echo commune, detect activity/idleness, etc.) are abstracted behind a runtime layer that consumers supply.

**AgentRuntime** (Rust trait, implementation detail):
The internal Rust abstraction over a harness. Anything spt-core needs to do *to* or *with* an agent goes through this trait. Most consumers never see it directly — they configure spt-core via a manifest, and spt-core's default `ManifestRuntime` implementation executes against the manifest.

**runtime manifest**:
A declarative configuration file (TOML/YAML — schema TBD) that tells spt-core how to drive a specific harness. Declares: how to invoke an agent session, how to look up conversation history for an echo commune, how to detect/inject input, which binary or command implements each harness-side operation, and which endpoint types this harness supports. The primary integration path for consumer projects (e.g. spt-plugin).

Example shape (illustrative): `spt.exe --manifest spt-plugin.toml live start <id>`. A harness like the planned spt plugin wraps this invocation into the `$LIVE` / `$OWL` environment variables it injects into its sessions, so harness-internal callers continue to invoke `$LIVE` / `$OWL` unchanged.

### Endpoint types

Each perch advertises an **endpoint type** — a tag that says what shape of entity lives at that perch and what operations it accepts. The set of day-one types:

**ReadyAgent**:
Minimal SPT participant — a perch + a poll listener, no Psyche, no live-agent wrapper. Direct heir to the sister project's "ready agent".

**LiveAgent**:
A Self with a Psyche companion. Composite logical actor; addressable as one ID, but its component perches (the Self's, the Psyche's) live independently. Direct heir to the sister project's "live agent".

**Psyche**:
The Psyche companion's own perch, distinct from its paired LiveAgent's perch. First-class endpoint type so messages addressed to a LiveAgent's Psyche route directly without ambiguity.

**Worker**:
A subagent's perch under a parent LiveAgent. Created on subagent start, torn down on subagent stop. Replaces today's "working perch" concept; first-class type so cross-communication between a Self and its workers (and worker↔worker) is addressable.

**SptNode**:
A machine's participation in an SPT subnet. Owned by the planned `spt-node` daemon; identified by an Ed25519 public key generated on first run. First-class so networking primitives can address nodes directly as message targets, not only as transport peers.

**PresenceChannel** (novel concept):
An endpoint type designed for "find the user." A PresenceChannel collects presence signals across the user's nodes and lets an agent on any node figure out *which* machine the user most recently interacted with, then deliver a message via a presence-blueprint-specific delivery surface (notification, terminal, etc.). Distinguishes "deliver to the user" from "deliver to a specific machine."

The endpoint type system is **open**: harnesses and downstream projects may register additional types beyond the day-one set. Closed-vs-open semantics for capability advertisement (what operations each type accepts, how routing decides eligibility) are deferred to the design phase.

# spt-core

Harness-independent core for the SPT ecosystem. Provides inter-agent messaging, live-agent lifecycle, terminal wrapping, self-update, and networking primitives — as both a Rust library workspace and a canonical reference binary. Designed so any agent runtime (Claude Code, Codex, Cursor, headless, future harnesses) can interface with the SPT ecosystem either by shelling out to the binary or by linking the crates directly.

Successor to `claude_skill_owl` (today's "modern SPT"), which is being rebuilt as `spt-core` to untether the system from Claude Code and lift it to a general-purpose agent-ecosystem core.

## Language

**spt-core**:
The system. Canonical name. The Rust workspace and the umbrella project.

**spt.exe / spt** (canonical binary):
The reference binary built from the workspace. Replaces today's `owl.exe`. Most external integrations (plugins, hooks, scripts in other harnesses) interact with spt-core *only* through this binary — fire-and-forget subcommands, long-running listeners under a parent harness's process supervisor, etc. Unix builds use the same name without `.exe`.

**library workspace**:
The set of Rust crates that compose spt-core. Consumers that want a deeper integration than shelling out to `spt.exe` link these crates directly. The reference binary is itself a consumer of the workspace. The expected first non-binary consumer is `spt-node` (planned standalone networking daemon, follow-on project).

**spt plugin** (planned follow-on):
A rebuilt version of today's Claude Code `spt` plugin, the first sub-project to be built atop spt-core. Reaches feature parity with today's modern SPT while delegating all core functionality to spt-core (primarily via `spt.exe`, with deeper hooks where useful).

**spt-node** (planned follow-on):
The second sub-project. A standalone local daemon owning a node's identity and networking stack, built directly on spt-core's library crates rather than shelling out. Speaks SPT's wire protocol so any client can reach across machines through it.

### Deliverable shape

spt-core ships **both** a library workspace and a canonical binary:

- **Library crates** — the deeper integration path. Used by sub-projects that link Rust directly (`spt-node`, future first-party services).
- **`spt.exe` / `spt`** — the canonical binary, built from the workspace. The primary integration path for harness plugins and external tooling, which mostly fire it as a subprocess at various surfaces (one-shot commands, poll listeners under a Monitor-tool-equivalent, hook tap-ins).

Both surfaces are first-class. Wire-protocol parity between them is a versioning concern from day one (a non-Rust client speaking to `spt.exe` and a Rust client linking the crates must see the same observable behavior).

+spt-core is harness-independent: it does not know about Claude Code, Codex, Cursor, or any other agent runtime. All harness-specific surfaces (how to invoke an agent session, fetch conversation history for an echo commune, detect activity/idleness, etc.) are abstracted behind a runtime layer that consumers supply.

+**AgentRuntime** (Rust trait, implementation detail):

+The internal Rust abstraction over a harness. Anything spt-core needs to do *to* or *with* an agent goes through this trait. Most consumers never see it directly — they configure spt-core via a manifest, and spt-core's default `ManifestRuntime` implementation executes against the manifest.

+A declarative configuration file (TOML/YAML — schema TBD) that tells spt-core how to drive a specific harness. Declares: how to invoke an agent session, how to look up conversation history for an echo commune, how to detect/inject input, which binary or command implements each harness-side operation, and which endpoint types this harness supports. The primary integration path for consumer projects (e.g. spt-plugin).

+Example shape (illustrative): `spt.exe --manifest spt-plugin.toml live start <id>`. A harness like the planned spt plugin wraps this invocation into the `$LIVE` / `$OWL` environment variables it injects into its sessions, so harness-internal callers continue to invoke `$LIVE` / `$OWL` unchanged.

+Each perch advertises an **endpoint type** — a tag that says what shape of entity lives at that perch and what operations it accepts. The set of day-one types:

+Minimal SPT participant — a perch + a poll listener, no Psyche, no live-agent wrapper. Direct heir to the sister project's "ready agent".

+A Self with a Psyche companion. Composite logical actor; addressable as one ID, but its component perches (the Self's, the Psyche's) live independently. Direct heir to the sister project's "live agent".

+The Psyche companion's own perch, distinct from its paired LiveAgent's perch. First-class endpoint type so messages addressed to a LiveAgent's Psyche route directly without ambiguity.

+A subagent's perch under a parent LiveAgent. Created on subagent start, torn down on subagent stop. Replaces today's "working perch" concept; first-class type so cross-communication between a Self and its workers (and worker↔worker) is addressable.

+A machine's participation in an SPT subnet. Owned by the planned `spt-node` daemon; identified by an Ed25519 public key generated on first run. First-class so networking primitives can address nodes directly as message targets, not only as transport peers.

+An endpoint type designed for "find the user." A PresenceChannel collects presence signals across the user's nodes and lets an agent on any node figure out *which* machine the user most recently interacted with, then deliver a message via a presence-blueprint-specific delivery surface (notification, terminal, etc.). Distinguishes "deliver to the user" from "deliver to a specific machine."

+The endpoint type system is **open**: harnesses and downstream projects may register additional types beyond the day-one set. Closed-vs-open semantics for capability advertisement (what operations each type accepts, how routing decides eligibility) are deferred to the design phase.


B, yes. note that `Psyche` would also be a day-one endpoint type. `Worker` would as well. (to reach psyche and subagent perches, respectively) --- for B to work, the consumer projects (spt-plugin) nee…


Endpoint types + manifest baseline in CONTEXT.md. Next.

---

**Q4 — Manifest scope: one or many?**

Your example was `spt.exe --manifest live-manifest.file live start`. The filename suggests the manifest is scoped to *live operations*. But spt-core has many surfaces (live, network, terminal-wrap, self-update, ReadyAgent perches, Worker spawn-on-subagent-hook, PresenceChannel delivery). Two structural options:

- **(A) Single unified manifest per harness.** One file (e.g. `spt-plugin.toml`) declares everything spt-plugin knows: how to spawn a CC Self, how to spawn a Psyche, how to look up history, what endpoint types it supports, how its terminal wrapper attaches, etc. `spt.exe --manifest spt-plugin.toml <subcommand>` — same file passed to every invocation; subcommand picks which section is consulted.
- **(B) Multiple narrow manifests per concern.** `live.toml`, `network.toml`, `terminal.toml`, etc. Each manifest declares one slice. `spt.exe --manifest live.toml live start`, `spt.exe --manifest network.toml node up`. Consumers can mix-and-match (e.g. use spt-core's default network manifest but a custom live manifest).
- **(C) Single manifest, but composable.** One canonical manifest file per harness, but it supports `include` directives so a harness can pull in spt-core-shipped defaults for sections it doesn't customize. `spt.exe --manifest spt-plugin.toml` finds one file, which may transitively include others.

**Recommend (C).** Reasons:
- (A) is operationally cleanest: harness sets `$SPT_MANIFEST=spt-plugin.toml` once, every `spt.exe` invocation Just Works.
- (B) creates a manifest-juggling burden — the harness has to remember which file goes with which subcommand, env vars proliferate, and shared concepts (endpoint types declared in one section, referenced in another) end up duplicated across files.
- (C) gets (A)'s ergonomics with (B)'s composability. A harness with no opinions on networking inherits spt-core's `network.default.toml` via `include`; a harness that wants custom presence-blueprint behavior overrides just that section. Bonus: spt-core can ship sane default sub-manifests as crate-embedded resources, included by URL-like scheme (`spt-core://defaults/network`).

Tradeoff: include resolution adds complexity to the manifest loader (cycle detection, override precedence). Mitigation: start with (A) shape — one flat file, no includes — and add include support when first sub-project (spt-plugin) needs to override only part of a section. YAGNI on includes until proven.

So actually the staged recommendation: **ship (A) in v1.0, evolve to (C) lazily.** Confirm or pick differently?


<local-command-caveat>Caveat: The messages below were generated by the user while running local commands. DO NOT respond to these messages or otherwise consider them in your response unless the user explicitly asks you to.</local-command-caveat>


<command-name>/reload-plugins</command-name>
            <command-message>reload-plugins</command-message>
            <command-args></command-args>


<local-command-stdout>Reloaded: 6 plugins · 0 skills · 39 agents · 9 hooks · 1 plugin MCP server · 1 plugin LSP server</local-command-stdout>


agreed with A first, C if we need it. to zoom out for a sec: are manifests the right direction? is there a better way?