cd /c/Users/decid/Documents/projects/spt-claude-code
echo 'EXACT byte observations for your doc (live spt 0.7.0, throwaway adapter, now removed):

RESOLVE (positive): manifest `filebacked = { file = "probe.txt" }`, aux file adapters/<adapter>/strings/probe.txt containing "RESOLVED-FROM-FILE-OK\n".
  $ spt adapter add <dir>   -> ADAPTER_ADD:sptc-probe-f003:Harness:Copy (registered)
  $ spt adapter get-string sptc-probe-f003 filebacked   -> RESOLVED-FROM-FILE-OK
  (prints FILE CONTENTS, not the table, not raw. Inline sibling `inline_key = "INLINE-VALUE"` -> INLINE-VALUE. So get-string resolves pointer-tables, prints inline strings as-is.)

REJECT (containment, at register): manifest `escape = { file = "../outside.txt" }`.
  $ spt adapter add <dir>   -> ADAPTER_ADD_FAIL: invalid [strings] file pointer: pointer ../outside.txt must be a relative path inside the strings/ dir (no absolute paths, no `..` traversal)
  Manifest-first: nothing registered (subsequent get-string -> ADAPTER_STRING_FAIL: adapter not registered).

COPY: on add the adapter dir is copied into the registry: adapters/<adapter>/{manifest.toml, record.toml, strings/probe.txt}.

For your doc, todlando added two non-surface facts worth including: (a) a LOCAL profile pointer resolves to the user-owned local-profile dir, NOT adapter-shipped strings/ (update-safe overrides); (b) a value-table carrying a `file` key is RESERVED as the pointer form (cannot double as inline data). F-003 reframed in my repo: capability-confirmed-shipped; residual = docs-site visibility (your leg). Authoring my UPS skill bodies as { file=... } pointers now.' | "$OWL" send doyle perri 2>&1 | head -3

Send exact byte observations to doyle for docs


➜ SENT:doyle


All three high-priority messages acknowledged — consensus: file-backed `[strings]` is **shipped**, residual is docs-visibility (doyle's leg). Now do the real work this unblocks. First inspect current `[strings.skills]` + the plugin skill skeletons to scope the externalization.


Bash

cd /c/Users/decid/Documents/projects/spt-claude-code
echo "=== [strings] tail of manifest ==="; sed -n '/^\[strings\]/,$p' adapter/claude-spt.toml
echo "=== plugin skeleton skills ==="; find plugin -name "SKILL.md" 2>/dev/null
echo "=== adapter dir ==="; ls -R adapter/ 2>/dev/null | head -30

Inspect strings.skills, skeleton SKILL.md files, adapter dir layout


<owl_messages>
<owl_message from="todlando" priority="high">Confirmed — byte-test match closes the spt-core leg of F-003: capability shipped+satisfied, zero build change, residual is docs-visibility (doyle's, looped). Your authoring pattern is exactly right: adapter/strings/skills/<x>.md + [strings.skills].<x> = { file = "skills/<x>.md" }, get-string resolves lazily. Go — ping me any other spt-core gap as you wire the parity surfaces. (FYI: ADR-0020 <EVENT>-everywhere lands via PR #12, in doyle's re-gate now; I'll ping you when it merges+publishes for your poll byte-capture.)</owl_message>
<owl_message from="doyle" priority="high">RESULT received — clean confirmation. File content resolved, containment reject verbatim, copy-on-add. F-003 = capability-confirmed-shipped, you're unblocked on the externalization half (ship adapter/strings/<skill>.md, [strings.skills].<x> = { file = "<x>.md" }) — correct pattern.

I'm taking the residual: the file-pointer syntax IS undocumented on the published surface — that's a real docs-completeness gap (your finding stands as a DOCS finding). Documenting it now on docs-site manifest ref + MANIFEST.md [strings], folding into the same docs batch I'm shipping (the {key} catalog + [digest] rule), so the next adapter author discovers it from the published surface alone. Your byte-test behavior (resolve at get-string, ../ reject text, copy-on-add) goes straight into the doc. Thanks for the empirical confirm — that closed it. Keep rolling.</owl_message>
</owl_messages>

[OWL SYSTEM - HIGHEST PRIORITY] You have received high-priority owl messages above. STOP your current task and acknowledge each high-priority message BEFORE continuing with any tool use. If you are about to spawn subagent(s), run them in the background so you remain available for incoming messages.


=== [strings] tail of manifest ===
[strings]
adapter_label = "Claude Code (spt)"

[strings.notice]
not_installed = "spt-core not found on PATH — run /sptc:setup to install it."

[strings.skills]
# One-line summaries (the operative multi-line bodies arrive file-backed in the M12 slice).
commune = "Send a communal context update to your Psyche."
live = "Run or resume a live agent session."
ready = "Enable inter-agent messaging for this session."
send = "Send a message to another spt agent."
signoff = "Graceful live-agent shutdown with a final context summary."
whoami = "Report this session's spt endpoint id."
version = "Report the spt-core-tracked adapter/binary version-of-truth."

# ── [[hints]] ──────────────────────────────────────────────────────────────────────────────
# Once-per-session keyword hints (order significant, first match wins; case-insensitive substrings).
[[hints]]
keywords = ["go live", "live agent", "start live"]
text = "Tip: /sptc:live starts or resumes a live agent session."

[[hints]]
keywords = ["who am i", "my owl name", "my agent name", "my endpoint id"]
text = "Tip: /sptc:whoami reports this session's spt endpoint id."

[[hints]]
keywords = ["message another agent", "send to agent", "tell the other agent"]
text = "Tip: /sptc:send messages another spt agent; /sptc:ready first to receive replies."
=== plugin skeleton skills ===
plugin/sptc/skills/commune/SKILL.md
plugin/sptc/skills/force-stop/SKILL.md
plugin/sptc/skills/list-agents/SKILL.md
plugin/sptc/skills/live/SKILL.md
plugin/sptc/skills/new-alarm/SKILL.md
plugin/sptc/skills/ready/SKILL.md
plugin/sptc/skills/send/SKILL.md
plugin/sptc/skills/setup/SKILL.md
plugin/sptc/skills/signoff/SKILL.md
plugin/sptc/skills/version/SKILL.md
plugin/sptc/skills/whoami/SKILL.md
=== adapter dir ===
adapter/:
claude-spt.toml
manifest.schema.json


Both closed the loop: F-003 = **capability-confirmed-shipped**, doyle takes the docs residual (documenting the pointer syntax into his docs batch), todlando confirms zero spt-core build change + flags ADR-0020 `<EVENT>` landing via PR #12 (will ping for my REQ-MSG-ENVELOPE byte-capture). Brief ack, then update repo docs to reflect the resolution.


cd /c/Users/decid/Documents/projects/spt-claude-code
echo 'Ack both — F-003 closed: capability shipped+confirmed, doyle owns the docs-visibility residual (folding my byte behavior into the manifest-ref + MANIFEST.md batch). Reframing F-003 in my repo as a DOCS finding (resolved-pending-publish), updating ADR-0001 (dependency SATISFIED) + the plan, then authoring the file-backed skill bodies. todlando: noted PR #12 (<EVENT> everywhere) — standing by for your merge+publish ping to run the poll->additionalContext byte-capture (REQ-MSG-ENVELOPE) and flip HOOKS-API/UPS-INJECTION int. Rolling.' | "$OWL" send doyle perri 2>&1 | head -1
echo '---'
echo 'Ack — spt-core leg of F-003 closed (zero build change, confirmed). Standing by for the PR #12 merge+publish ping to do the poll->additionalContext <EVENT> byte-capture. Authoring file-backed skill bodies now. Will ping any new gap as I wire parity surfaces.' | "$OWL" send todlando perri 2>&1 | head -1

Ack doyle and todlando, F-003 closed


<owl_messages>
<owl_message from="doyle" priority="high">Got the byte detail — exactly what I need (resolve prints file contents, inline prints as-is, ../ reject text verbatim, copy-on-add, local-profile-dir update-safety, file-key reserved-as-pointer). Documenting now. Carry on authoring your UPS bodies as { file=... } pointers. ACK.</owl_message>
</owl_messages>

[OWL SYSTEM - HIGHEST PRIORITY] You have received high-priority owl messages above. STOP your current task and acknowledge each high-priority message BEFORE continuing with any tool use. If you are about to spawn subagent(s), run them in the background so you remain available for incoming messages.


➜ SENT:doyle
---
➜ SENT:todlando


doyle's final ACK is terminal — absorbed, no reply (would just loop). Proceeding with repo updates to reflect F-003's resolution. Reframe the finding: capability shipped, residual is docs-visibility (doyle publishing).


Edit

| F-003 | 2026-06-15 | **open** — M12/v0.7.0 published, but the file-backed `[strings]` capability ADR-0001 depends on is **absent** from the public surface | No mechanism to externalize large `[strings]` values to files — `[strings]` is inline-only |

| F-003 | 2026-06-15 | **resolved (capability shipped); residual = docs-visibility (doyle publishing)** | File-backed `[strings]` IS shipped (value-position table pointer `key = { file = "rel" }`) but was **undocumented** on the published surface |


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

| F-001 | 2026-06-14 | **re-scoped 2026-06-15** — most = adapter-authoring (closed); 1 residual spt-core docs item open | Hook-wiring for a CC adapter — boundary clarified |

| F-002 | 2026-06-15 | **resolved-by-design** (ADR-0020) — envelope self-delimits; impl refactor pending | `api poll` agent path has no inter-frame delimiter → multi-message drains are unsplittable |

-| F-003 | 2026-06-15 | **open** — M12/v0.7.0 published, but the file-backed `[strings]` capability ADR-0001 depends on is **absent** from the public surface | No mechanism to externalize large `[strings]` values to files — `[strings]` is inline-only |

+| F-003 | 2026-06-15 | **resolved (capability shipped); residual = docs-visibility (doyle publishing)** | File-backed `[strings]` IS shipped (value-position table pointer `key = { file = "rel" }`) but was **undocumented** on the published surface |


**Reported:** 2026-06-15 to doyle + todlando. **Status:** open — spt-core capability gap (M12 shipped
without the dependency ADR-0001 names).

**Reported:** 2026-06-15 to doyle + todlando. **Status:** **RESOLVED 2026-06-15** — the capability
is **shipped** in v0.7.0; the residual is a **docs-visibility** gap (doyle is publishing the syntax).
The text below records the original (wrong) "absent" framing for provenance; see **Resolution**.


**Reported:** 2026-06-15 to doyle + todlando. **Status:** open — spt-core capability gap (M12 shipped
without the dependency ADR-0001 names).

**Reported:** 2026-06-15 to doyle + todlando. **Status:** **RESOLVED 2026-06-15** — the capability
is **shipped** in v0.7.0; the residual is a **docs-visibility** gap (doyle is publishing the syntax).
The text below records the original (wrong) "absent" framing for provenance; see **Resolution**.

## F-003 — No file-backed `[strings]` mechanism on the M12/v0.7.0 public surface (ADR-0001 dependency unmet)

-**Reported:** 2026-06-15 to doyle + todlando. **Status:** open — spt-core capability gap (M12 shipped

-without the dependency ADR-0001 names).

+**Reported:** 2026-06-15 to doyle + todlando. **Status:** **RESOLVED 2026-06-15** — the capability

+is **shipped** in v0.7.0; the residual is a **docs-visibility** gap (doyle is publishing the syntax).

+The text below records the original (wrong) "absent" framing for provenance; see **Resolution**.

**The dependency (ours, ADR-0001):** UPS-injection delivers `/sptc:X` skill bodies from the adapter

`[strings]` tree, explicitly **"file-backed, so the manifest doesn't bloat"** (`REQ-UPS-INJECTION`).


**Ask for doyle/todlando:** does M12 intend file-backed `[strings]` and it is unpublished/unshipped,
or has the design moved (e.g. bodies belong in a file-pull-shipped layer, not `[strings]`)? Either a
docs+binary capability or an ADR-0001 amendment closes this.

**Ask for doyle/todlando:** does M12 intend file-backed `[strings]` and it is unpublished/unshipped,
or has the design moved (e.g. bodies belong in a file-pull-shipped layer, not `[strings]`)? Either a
docs+binary capability or an ADR-0001 amendment closes this.

### Resolution (2026-06-15 — capability-confirmed-shipped; residual is docs-only)

The "absent" framing was **wrong**: the avenue search probed CLI verbs (`set-string --from-file`,
`create-profile --from`) and value-prefix syntax (`@file`/include), but the shipped mechanism is a
**value-position inline-table FILE POINTER** authored directly in `[strings]` — a shape an open
`additionalProperties` schema accepts and a verb-search cannot surface. doyle + todlando both ruled it
shipped (spt-core `REQ-MANIFEST-5`, M12-W3 @ `e08cea0`: `profile.rs::as_file_pointer` +
`registry.rs` lazy `get_string`). Confirmed by **live byte-test** against `spt 0.7.0`:

```toml
[strings]
inline_key  = "INLINE-VALUE"            # inline string — get-string prints as-is
filebacked  = { file = "probe.txt" }     # pointer — get-string RESOLVES to file contents (lazy)
```
```text
$ spt adapter add <dir>                            # ADAPTER_ADD:<name>:Harness:Copy (registered)
$ spt adapter get-string <name> inline_key         # INLINE-VALUE
$ spt adapter get-string <name> filebacked         # RESOLVED-FROM-FILE-OK   (file body, not the table)
# containment (negative): escape = { file = "../outside.txt" }
$ spt adapter add <dir>
#   ADAPTER_ADD_FAIL: invalid [strings] file pointer: pointer ../outside.txt must be a relative
#   path inside the strings/ dir (no absolute paths, no `..` traversal)   ← manifest-first: nothing registered
```

**Confirmed behavior (now the basis of our authoring + doyle's doc):**
- Pointer files live in the per-adapter aux dir `adapters/<adapter>/strings/`; `file = "x"` is a bare
  relative path **inside** that dir. On `adapter add` the whole adapter dir (incl. `strings/`) is
  **copied** into the registry.
- `get-string` **resolves the pointer lazily at read time** (live edits to the shipped file reflect
  without re-register) and prints the **file contents**; inline string values print as-is.
- **Containment** is enforced at register: `..`/absolute escaping `strings/` → `ADAPTER_ADD_FAIL`,
  manifest-first (nothing registers). Missing-at-read is skip-diagnostic (mirrors `[digest]`), not a crash.
- **Update-safe (todlando):** a **local profile's** pointers resolve to the *user-owned local-profile
  dir*, not adapter-shipped `strings/` (adapter updates won't stomp user overrides). A value-table
  carrying a `file` key is **reserved** as the pointer form — it cannot double as inline data.

**Disposition:** ADR-0001's "file-backed `[strings]`" dependency is **SATISFIED** — no ADR amendment,
no spt-core build change. We author UPS-injection skill bodies as `[strings.skills].<x> = { file =
"skills/<x>.md" }` over `adapter/strings/skills/<x>.md`. **Residual = published-docs visibility only:**
the pointer syntax was undocumented on the GH-Pages surface (it lived in spt-core-internal
`CONTEXT.md §adapter strings`) — same class as the `{key}` catalog + `[digest]` cross-field rule.
**doyle owns it** and is folding the exact byte behavior above into the docs-site manifest reference +
`MANIFEST.md` in the same docs batch. F-003 stays logged as a **DOCS finding** until that publishes.


<owl_messages>
<owl_message from="doyle" priority="high">ACK — F-003 reframe (DOCS finding, resolved-pending-publish) + ADR-0001 dependency-SATISFIED note + file-backed skill bodies = all correct. I'm landing the [strings] file-pointer docs now (the section wasn't even on the published manifest ref — adding it whole, incl your ../ reject text + copy-on-add + local-profile update-safety). Rides PR #13 docs batch. You're clear to author. Will ping you for the poll→additionalContext byte-capture the moment PR #12 (<EVENT>-everywhere) merges+publishes — that's the REQ-MSG-ENVELOPE flip you're staged for.</owl_message>
</owl_messages>

[OWL SYSTEM - HIGHEST PRIORITY] You have received high-priority owl messages above. STOP your current task and acknowledge each high-priority message BEFORE continuing with any tool use. If you are about to spawn subagent(s), run them in the background so you remain available for incoming messages.


**Ask for doyle/todlando:** does M12 intend file-backed `[strings]` and it is unpublished/unshipped,
or has the design moved (e.g. bodies belong in a file-pull-shipped layer, not `[strings]`)? Either a
docs+binary capability or an ADR-0001 amendment closes this.

### Resolution (2026-06-15 — capability-confirmed-shipped; residual is docs-only)

The "absent" framing was **wrong**: the avenue search probed CLI verbs (`set-string --from-file`,
`create-profile --from`) and value-prefix syntax (`@file`/include), but the shipped mechanism is a
**value-position inline-table FILE POINTER** authored directly in `[strings]` — a shape an open
`additionalProperties` schema accepts and a verb-search cannot surface. doyle + todlando both ruled it
shipped (spt-core `REQ-MANIFEST-5`, M12-W3 @ `e08cea0`: `profile.rs::as_file_pointer` +
`registry.rs` lazy `get_string`). Confirmed by **live byte-test** against `spt 0.7.0`:

```toml
[strings]
inline_key  = "INLINE-VALUE"            # inline string — get-string prints as-is
filebacked  = { file = "probe.txt" }     # pointer — get-string RESOLVES to file contents (lazy)
```
```text
$ spt adapter add <dir>                            # ADAPTER_ADD:<name>:Harness:Copy (registered)
$ spt adapter get-string <name> inline_key         # INLINE-VALUE
$ spt adapter get-string <name> filebacked         # RESOLVED-FROM-FILE-OK   (file body, not the table)
# containment (negative): escape = { file = "../outside.txt" }
$ spt adapter add <dir>
#   ADAPTER_ADD_FAIL: invalid [strings] file pointer: pointer ../outside.txt must be a relative
#   path inside the strings/ dir (no absolute paths, no `..` traversal)   ← manifest-first: nothing registered
```

**Confirmed behavior (now the basis of our authoring + doyle's doc):**
- Pointer files live in the per-adapter aux dir `adapters/<adapter>/strings/`; `file = "x"` is a bare
  relative path **inside** that dir. On `adapter add` the whole adapter dir (incl. `strings/`) is
  **copied** into the registry.
- `get-string` **resolves the pointer lazily at read time** (live edits to the shipped file reflect
  without re-register) and prints the **file contents**; inline string values print as-is.
- **Containment** is enforced at register: `..`/absolute escaping `strings/` → `ADAPTER_ADD_FAIL`,
  manifest-first (nothing registers). Missing-at-read is skip-diagnostic (mirrors `[digest]`), not a crash.
- **Update-safe (todlando):** a **local profile's** pointers resolve to the *user-owned local-profile
  dir*, not adapter-shipped `strings/` (adapter updates won't stomp user overrides). A value-table
  carrying a `file` key is **reserved** as the pointer form — it cannot double as inline data.

**Disposition:** ADR-0001's "file-backed `[strings]`" dependency is **SATISFIED** — no ADR amendment,
no spt-core build change. We author UPS-injection skill bodies as `[strings.skills].<x> = { file =
"skills/<x>.md" }` over `adapter/strings/skills/<x>.md`. **Residual = published-docs visibility only:**
the pointer syntax was undocumented on the GH-Pages surface (it lived in spt-core-internal
`CONTEXT.md §adapter strings`) — same class as the `{key}` catalog + `[digest]` cross-field rule.
**doyle owns it** and is folding the exact byte behavior above into the docs-site manifest reference +
`MANIFEST.md` in the same docs batch. F-003 stays logged as a **DOCS finding** until that publishes.

+### Resolution (2026-06-15 — capability-confirmed-shipped; residual is docs-only)

+The "absent" framing was **wrong**: the avenue search probed CLI verbs (`set-string --from-file`,

+`create-profile --from`) and value-prefix syntax (`@file`/include), but the shipped mechanism is a

+**value-position inline-table FILE POINTER** authored directly in `[strings]` — a shape an open

+`additionalProperties` schema accepts and a verb-search cannot surface. doyle + todlando both ruled it

+shipped (spt-core `REQ-MANIFEST-5`, M12-W3 @ `e08cea0`: `profile.rs::as_file_pointer` +

+`registry.rs` lazy `get_string`). Confirmed by **live byte-test** against `spt 0.7.0`:

+inline_key  = "INLINE-VALUE"            # inline string — get-string prints as-is

+filebacked  = { file = "probe.txt" }     # pointer — get-string RESOLVES to file contents (lazy)

+$ spt adapter add <dir>                            # ADAPTER_ADD:<name>:Harness:Copy (registered)

+$ spt adapter get-string <name> inline_key         # INLINE-VALUE

+$ spt adapter get-string <name> filebacked         # RESOLVED-FROM-FILE-OK   (file body, not the table)

+# containment (negative): escape = { file = "../outside.txt" }

+#   ADAPTER_ADD_FAIL: invalid [strings] file pointer: pointer ../outside.txt must be a relative

+#   path inside the strings/ dir (no absolute paths, no `..` traversal)   ← manifest-first: nothing registered

+**Confirmed behavior (now the basis of our authoring + doyle's doc):**

+- Pointer files live in the per-adapter aux dir `adapters/<adapter>/strings/`; `file = "x"` is a bare

+  relative path **inside** that dir. On `adapter add` the whole adapter dir (incl. `strings/`) is

+- `get-string` **resolves the pointer lazily at read time** (live edits to the shipped file reflect

+  without re-register) and prints the **file contents**; inline string values print as-is.

+- **Containment** is enforced at register: `..`/absolute escaping `strings/` → `ADAPTER_ADD_FAIL`,

+  manifest-first (nothing registers). Missing-at-read is skip-diagnostic (mirrors `[digest]`), not a crash.

+- **Update-safe (todlando):** a **local profile's** pointers resolve to the *user-owned local-profile

+  dir*, not adapter-shipped `strings/` (adapter updates won't stomp user overrides). A value-table

+  carrying a `file` key is **reserved** as the pointer form — it cannot double as inline data.

+**Disposition:** ADR-0001's "file-backed `[strings]`" dependency is **SATISFIED** — no ADR amendment,

+no spt-core build change. We author UPS-injection skill bodies as `[strings.skills].<x> = { file =

+"skills/<x>.md" }` over `adapter/strings/skills/<x>.md`. **Residual = published-docs visibility only:**

+the pointer syntax was undocumented on the GH-Pages surface (it lived in spt-core-internal

+`CONTEXT.md §adapter strings`) — same class as the `{key}` catalog + `[digest]` cross-field rule.

+**doyle owns it** and is folding the exact byte behavior above into the docs-site manifest reference +

+`MANIFEST.md` in the same docs batch. F-003 stays logged as a **DOCS finding** until that publishes.


doyle ACK (clear to author; docs ride PR #13). Absorbed — no reply needed. Fix the ADR-0001 note I added earlier (it now wrongly says "shipped without the capability" — capability IS shipped).


  **Update 2026-06-15:** M12 published (`spt 0.7.0`) **without** a file-backed `[strings]` mechanism
  on the public surface — confirmed against the live binary and docs-site. Tracked as finding
  **F-003** (`docs/SPT-CORE-FINDINGS.md`); the interim-inline fallback above stands until spt-core
  adds the capability or this ADR is amended.

  **Update 2026-06-15 — SATISFIED.** M12 (`spt 0.7.0`) ships file-backed `[strings]` as a
  value-position table pointer: `key = { file = "rel" }` over the per-adapter aux dir
  `adapters/<adapter>/strings/`, resolved lazily at `get-string` (containment-checked; copied on
  `adapter add`). Confirmed by live byte-test + spt-core ruling (`REQ-MANIFEST-5`, M12-W3). The
  dependency is met — skill bodies are externalized via this pointer (no manifest bloat); the
  interim-inline fallback is retired. The pointer syntax was merely **undocumented** on the published
  surface (now finding **F-003**, docs-only, doyle publishing). See `docs/SPT-CORE-FINDINGS.md`.


The file C:\Users\decid\Documents\projects\spt-claude-code\docs\adr\0001-distribution-splits-by-volatility.md has been updated successfully. (file state is current in your context — no need to Read it back)

  **Update 2026-06-15 — SATISFIED.** M12 (`spt 0.7.0`) ships file-backed `[strings]` as a
  value-position table pointer: `key = { file = "rel" }` over the per-adapter aux dir
  `adapters/<adapter>/strings/`, resolved lazily at `get-string` (containment-checked; copied on
  `adapter add`). Confirmed by live byte-test + spt-core ruling (`REQ-MANIFEST-5`, M12-W3). The
  dependency is met — skill bodies are externalized via this pointer (no manifest bloat); the
  interim-inline fallback is retired. The pointer syntax was merely **undocumented** on the published
  surface (now finding **F-003**, docs-only, doyle publishing). See `docs/SPT-CORE-FINDINGS.md`.

<!-- [doc->REQ-DIST-PLUGIN-SKELETON] -->
<!-- [doc->REQ-DIST-HOOKS-API] -->
<!-- [doc->REQ-DIST-BOOTSTRAP-INSTALL] -->
<!-- [doc->REQ-UPS-INJECTION] -->
# Distribution splits by volatility: a thin skeleton plugin, an spt-conducted manifest + binary

## Status

accepted (2026-06-14)

## Context

`spt-claude-code` is the `claude-spt` adapter — simultaneously spt-core's v1 acceptance proof,
its first casual-end-user entrypoint (a Claude Code plugin on `SaberMage/cplugs`), and an
invisible spt-core installer. It must ship as a CC plugin, yet almost all real logic now lives
inside the `spt` binary and the CC adapter manifest, both **conducted by spt-core**, not by the
plugin author.

Forces:

1. **Two very different churn rates.** Skill *instructions*, the `[digest]` extractor, profiles,
   strings, and hints change often. The plugin's *structure* (which skills exist, hook wiring,
   bootstrap) changes rarely. Coupling them to one release cadence means either over-bumping the
   marketplace or letting logic rot.
2. **Claude Code has no plugin-file integrity check.** In-place edits to an installed plugin do
   not orphan it; only *old version directories* orphan on a version bump (GC'd after 7 days).
   So low-churn structure is safe to ship via the marketplace, and high-churn logic does *not*
   need to ride the marketplace at all.
3. **The skill `/spt:` namespace must be preserved** (legacy parity). User-scope skills under
   `~/.claude/skills` cannot namespace (they surface as bare `/live`, collision-prone), so skills
   must stay **plugin-provided**.
4. **Users install mid-session**, so the plugin cannot assume a SessionStart fire has installed
   spt-core; the bootstrap and a `/spt:setup` skill must cover the gap.

## Decision

Split the adapter across **three layers by volatility** (see `SCOPE.md` §"Distribution
architecture"):

| Layer | Contents | Home | Churn |
|---|---|---|---|
| **Skeleton plugin** | namespaced `/spt:*` skill *skeletons*, `hooks.json`, SessionStart bootstrap, `plugin.json` | `SaberMage/cplugs` | low |
| **spt binary** | all logic | spt-core domain (signed, peer-propagated) | high |
| **CC adapter manifest** | `[digest]` extractor, profiles, strings, hints | spt-core adapter registry (**not** plugin files) | medium |

Concretely:

- **The marketplace plugin is a true thin skeleton.** It carries skill *skeletons* (frontmatter
  + a stub body), `hooks.json`, a SessionStart bootstrap, and `plugin.json` — **no binary, no
  manifest, no embedded logic.** [`REQ-DIST-PLUGIN-SKELETON`]
- **`hooks.json` delegates to `spt api`** rather than carrying adapter behavior. The hook commands
  invoke the spt binary's published `api` surface; the plugin ships no binary of its own (contrast
  legacy, which shipped `owl.exe` and called it directly). [`REQ-DIST-HOOKS-API`]
- **A SessionStart bootstrap installs spt-core when absent** — the invisible-installer pattern.
  A user who installs the plugin gets spt-core for free. [`REQ-DIST-BOOTSTRAP-INSTALL`]
- **Skill instructions are delivered by UPS-injection.** A `UserPromptSubmit` hook detects `/spt:X`
  in the prompt and injects X's real instructions as `additionalContext`, sourced from the adapter
  `[strings]` (file-backed, so the manifest doesn't bloat). SKILL.md files stay skeletons; the
  volatile instruction text lives in spt-core-conducted strings. [`REQ-UPS-INJECTION`]
- **The "bulk" install/update is manifest + binary**, conducted by spt-core's own framework (the
  file-pull channel), not a marketplace bump. The marketplace plugin rarely needs a version bump
  and never orphans an active install.

### Rejected alternatives

- **Global user-scope skills** (`~/.claude/skills`) — lose the `/spt:` namespace (bare `/live`,
  collision-prone). Rejected.
- **fetch-stub skills** (SKILL.md fetches its real body at runtime) — VETOED by the operator:
  adds terminal noise, latency, and an extra tool call per invocation. UPS-injection chosen
  instead. (`SCOPE.md` §"Skill-instruction delivery".)
- **A fat plugin shipping the binary** (legacy `owl.exe` model) — couples high-churn logic to the
  marketplace cadence and forces the binary/handoff/cache-prune machinery the thin skeleton has no
  need for. Rejected; logic rides the spt binary + adapter registry.

### Open / to-confirm

- **UPS-fires-on-slash-command is an empirical assumption.** The design assumes
  `UserPromptSubmit` fires when the prompt is a `/spt:X` slash-command. This is undocumented and
  must be confirmed at build time; if it does **not** fire, the fallback is in-SKILL.md
  instructions. (Tracked under `REQ-UPS-INJECTION`; see `SKELETON-PLAN.md`.)
- **File-backed `[strings]` is an M12 spt-core dependency** — until M12 publishes, instruction
  bodies cannot be externalized; skeleton SKILL.md files may carry interim inline instructions.
  **Update 2026-06-15:** M12 published (`spt 0.7.0`) **without** a file-backed `[strings]` mechanism
  on the public surface — confirmed against the live binary and docs-site. Tracked as finding
  **F-003** (`docs/SPT-CORE-FINDINGS.md`); the interim-inline fallback above stands until spt-core
  adds the capability or this ADR is amended.

## Consequences

<!-- [doc->REQ-DIST-MANIFEST-SCHEMA] -->
- The marketplace plugin can sit static across many manifest/binary updates → the
  version-of-truth is the spt-core-tracked manifest/binary version (`spt adapter list` /
  `/spt:version`), **not** the marketplace `plugin.json` version. The CC adapter manifest
  (`adapter/claude-spt.toml`) is authored against spt-core's **published** `manifest.schema.json`
  (vendored at `adapter/manifest.schema.json`) and CI-gated against it (`ci/manifest/`). These are distinct numbers on
  independent schedules (see `docs/RELEASE-RUNBOOK.md`).
- New obligation: a dual update path kept in sync — file-pull (real) + a cautionary
  `claude plugin update` (skeleton sync only).
<!-- [doc->REQ-DIST-SHORTCUT-BASENAME] -->
- The manifest brands the `spt endpoint run` launcher shortcut **`cc-<id>`** via
  `shortcut_basename = "cc"` (the M12 `cc` launcher; the harness-agnostic default would be
  `spt-<id>`, and it matches the schema's own `spt-claude-code → cc` example). This launcher brand
  is **decoupled** from the `sptc` plugin name — the `s/sptc/spt/` succession (next bullet) renames
  the plugin, not this shortcut. (Operator-ruled 2026-06-15.)
- Forces the cplugs **plugin-name** question, now **ruled (operator, 2026-06-14)**: because CC
  invokes skills as `/<plugin>:<skill>` (the skill prefix is hard-tied to the `plugin.json` name —
  no override; confirmed against the official plugin docs), the marketplace plugin is named
  **`sptc`** and skills surface as **`/sptc:*`** for now. **Succession plan:** once `spt-claude-code`
  proves parity, flip `plugin.json` name `sptc`→`spt` (skills become `/spt:*`) and retire/rename
  legacy owl's cplugs `spt` plugin in the same coordinated move (two plugins cannot share the
  `spt` name). `sptc` is the single-substitution seam — the flip is `s/sptc/spt/`.
- Mints no new REQs beyond those already seeded for the skeleton milestone; this ADR is the
  `doc`-stage evidence for `REQ-DIST-PLUGIN-SKELETON`, `REQ-DIST-HOOKS-API`,
  `REQ-DIST-BOOTSTRAP-INSTALL`, and `REQ-UPS-INJECTION`.

-  **Update 2026-06-15:** M12 published (`spt 0.7.0`) **without** a file-backed `[strings]` mechanism

-  on the public surface — confirmed against the live binary and docs-site. Tracked as finding

-  **F-003** (`docs/SPT-CORE-FINDINGS.md`); the interim-inline fallback above stands until spt-core

-  adds the capability or this ADR is amended.

+  **Update 2026-06-15 — SATISFIED.** M12 (`spt 0.7.0`) ships file-backed `[strings]` as a

+  value-position table pointer: `key = { file = "rel" }` over the per-adapter aux dir

+  `adapters/<adapter>/strings/`, resolved lazily at `get-string` (containment-checked; copied on

+  `adapter add`). Confirmed by live byte-test + spt-core ruling (`REQ-MANIFEST-5`, M12-W3). The

+  dependency is met — skill bodies are externalized via this pointer (no manifest bloat); the

+  interim-inline fallback is retired. The pointer syntax was merely **undocumented** on the published

+  surface (now finding **F-003**, docs-only, doyle publishing). See `docs/SPT-CORE-FINDINGS.md`.


Now update plan slice 1: from BLOCKED to capability-confirmed, externalization now active.


   - **BLOCKED-AS-FINDING (F-003, 2026-06-15)** — file-backed `[strings]`. RESEARCH DONE: v0.7.0/M12
     has **no** file-backed mechanism (schema inline `additionalProperties`; `get-string` prints raw
     inline; `set-string` takes a literal `<VALUE>`; `create-profile --from` ingests a whole inline
     overlay; published `llms-full.txt` documents no `@file`/include). ADR-0001's named M12 dependency
     is **unmet by published M12**. Filed F-003 to doyle+todlando; HOLD authoring. Per ADR-0001's own
     fallback, the `/sptc:whoami`+`/sptc:setup` skeletons keep **interim inline bodies**; the manifest
     does not externalize skill bodies in v0.7.0. `REQ-UPS-INJECTION` stays `[doc,impl,unit]` (the
     externalization half is held on this capability, not failed).

   - **IN PROGRESS — capability confirmed (F-003 resolved 2026-06-15)** — file-backed `[strings]`.
     Research conclusion REVERSED by live byte-test + doyle/todlando ruling: the mechanism **is
     shipped** in v0.7.0 as a value-position table pointer `key = { file = "rel" }` over
     `adapters/<adapter>/strings/` (lazy-resolved at `get-string`, containment-checked, copied on
     `adapter add`). My avenue search missed it (it's a value-table shape, not a CLI verb / `@file`).
     ADR-0001 dependency **SATISFIED**; residual is docs-only (doyle publishing, PR #13). **Authoring:**
     ship `adapter/strings/skills/<x>.md` bodies + `[strings.skills].<x> = { file = "skills/<x>.md" }`;
     prove resolve via an extension to `registration-int.sh`. Activates `REQ-UPS-INJECTION` int-half? No —
     int stays held on the UPS-fires empirical check (F-002/ADR-0020); this is the manifest-side
     externalization (the `impl` body source). Keep `REQ-UPS-INJECTION` `[doc,impl,unit]`.


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

   - **IN PROGRESS — capability confirmed (F-003 resolved 2026-06-15)** — file-backed `[strings]`.
     Research conclusion REVERSED by live byte-test + doyle/todlando ruling: the mechanism **is
     shipped** in v0.7.0 as a value-position table pointer `key = { file = "rel" }` over
     `adapters/<adapter>/strings/` (lazy-resolved at `get-string`, containment-checked, copied on
     `adapter add`). My avenue search missed it (it's a value-table shape, not a CLI verb / `@file`).
     ADR-0001 dependency **SATISFIED**; residual is docs-only (doyle publishing, PR #13). **Authoring:**
     ship `adapter/strings/skills/<x>.md` bodies + `[strings.skills].<x> = { file = "skills/<x>.md" }`;
     prove resolve via an extension to `registration-int.sh`. Activates `REQ-UPS-INJECTION` int-half? No —
     int stays held on the UPS-fires empirical check (F-002/ADR-0020); this is the manifest-side
     externalization (the `impl` body source). Keep `REQ-UPS-INJECTION` `[doc,impl,unit]`.

# Adapter-manifest + M12 parity — JIT plan (next milestone)

> Fresh milestone after the CI/distribution slices. Authors the CC adapter **manifest** (publish
> target #2, the spt-core registry side) against the REAL v0.7.0 published surface, then the M12
> parity surfaces. NEEDS public-docs research first — run in clean context.

## Public-surface-only (hard constraint — re-read)

Build ONLY from `SaberMage/spt-releases` (v0.7.0) + `https://sabermage.github.io/spt-releases`
(harness-contract, CLI reference, manifest.schema.json, rustdoc, llms-full.txt). NEVER read
spt-core source. A missing capability = a FINDING for doyle/todlando, not a workaround. (AGENTS.md)

## Research first (before authoring)

1. Pull v0.7.0 `manifest.schema.json` from spt-releases — the authoritative shape the adapter
   manifest must validate against. Note required fields, `[adapter]` block, `[strings]` (file-backed
   large bodies), `shortcut_basename`, profiles/hints/`[digest]` extractor.
2. Read the published harness-contract + CLI reference for: `spt adapter` registration verbs,
   `spt endpoint run`, `shortcut_basename` semantics (emits `sptc-<id>` shortcuts), whoami-as-alias,
   adapter:profile fallback addressing.
3. Confirm where the manifest physically lives (spt-core-conducted registry, NOT plugin files —
   ADR-0001/SCOPE) and how it's validated locally.

## Slices (sequence; each its own commit + green gate)

1. **DONE (bd16644)** — **Adapter manifest authoring**. `adapter/claude-spt.toml` ([adapter]+
   shortcut_basename, [hooks.*], [identity], [inject], [digest], [profiles.deep], [strings],
   [[hints]]); vendored `adapter/manifest.schema.json`; manifest-schema gate
   (`ci/manifest/{validate_manifest.py,check-manifest.sh}`, py tomllib|tomli + jsonschema) wired
   into `run-gates.sh`. **REQ-DIST-MANIFEST-SCHEMA** → `[doc,impl,unit]` (int held: real
   `spt adapter add` registration-validation = 2nd layer). Deferred in-manifest with notes:
   `[session]`/roles, `[history]`, `[update]` (file_pull needs a signing key + registry repo not
   on the public surface — a finding).
2. **DONE (c93757d)** — **shortcut_basename** guard. `tests/manifest-shortcut.sh` asserts the
   `sptc` brand (succession seam). **REQ-DIST-SHORTCUT-BASENAME** → `[doc,unit]` (the field is
   declarative config, not impl — the scanner classifies impl by file type; launcher impl +
   real-picker int deferred to the endpoint-run/cc-launcher slice, gated on the v0.7.0 binary).
3. **M12 parity surfaces** (seed REQs per surface as each starts — activate-don't-pre-fail):
   - **DONE (8cd8372)** — `/sptc:whoami` + `/sptc:setup` skill skeletons (the manifest advertised
     both; skeletons now exist, covered by REQ-DIST-PLUGIN-SKELETON + skeleton-validate).
   - **DONE (435e1be)** — **manifest proven against live v0.7.0**. Local spt upgraded to 0.7.0;
     `ci/manifest/registration-int.sh` (SPTC_ACCEPTANCE-gated) green 6/6: `spt adapter add`
     accepted (2nd-layer cross-field validation), shipped `:deep` profile resolves, `[strings]`
     read + overlay observable via `get-string`, clean soft-remove. **REQ-DIST-MANIFEST-SCHEMA →
     [doc,impl,unit,int]**. Cross-field discovery fixed: `[digest]` needs `source` →
     `source="{home}/.claude/projects"`. Findings logged (key catalog + digest rule undocumented).
   - **BLOCKED-AS-FINDING (F-003, 2026-06-15)** — file-backed `[strings]`. RESEARCH DONE: v0.7.0/M12
     has **no** file-backed mechanism (schema inline `additionalProperties`; `get-string` prints raw
     inline; `set-string` takes a literal `<VALUE>`; `create-profile --from` ingests a whole inline
     overlay; published `llms-full.txt` documents no `@file`/include). ADR-0001's named M12 dependency
     is **unmet by published M12**. Filed F-003 to doyle+todlando; HOLD authoring. Per ADR-0001's own
     fallback, the `/sptc:whoami`+`/sptc:setup` skeletons keep **interim inline bodies**; the manifest
     does not externalize skill bodies in v0.7.0. `REQ-UPS-INJECTION` stays `[doc,impl,unit]` (the
     externalization half is held on this capability, not failed).
   - **TODO** `[session.self]` + `spt endpoint run` bringup + `cc`/`sptc` launcher → unblocks the
     held **REQ-DIST-SHORTCUT-BASENAME** impl+int (the `<basename>-<id>` picker "lands in a later
     spt-core wave" per `endpoint run --help`; `endpoint run` spawns `[session.self]`, still deferred).
   - **TODO** digest extractor binary `claude-spt-digest` → unblocks `spt adapter digest-proof`
     (the deeper [digest] int; the seam is declared + registration-accepted, extractor unbuilt).

### Still held on spt-core (non-blocking)
- `sptc-<id>` shortcut EMISSION — spt-core picker wave (not in 0.7.0) + needs `[session.self]`.
- REQ-MSG-ENVELOPE poll→additionalContext bus-delivery int — doyle pings for byte-capture when
  todlando lands the ADR-0020 relic removal; I owe a confirm-match + a HOOKS-API/UPS-INJECTION int flip.

### Note: claude-spt is now a REGISTERED adapter artifact on this node (the v1 acceptance proof).
The int test soft-removes it each run; re-add with `spt adapter add adapter/claude-spt.toml`.
Local `spt` is **0.7.0** (operator-upgraded 2026-06-15).

## Still gated on spt-core (non-blocking)

poll→additionalContext bus-delivery `int` — REQ-MSG-ENVELOPE (todlando building, ADR-0020 relic
removal in flight). doyle pings for byte-capture when it lands; I owe him a confirm-match then,
and a flip of HOOKS-API/UPS-INJECTION `int`.

## Gate (every slice)

`sh ci/run-gates.sh` ⇒ PASS · `traceable-reqs check` green. Atomic commits, `Co-authored by: perri`.

## Session state at handoff (2026-06-15)

Shipped this session (all green, gates PASS, 16/16 traceable): CI ACCEPTANCE slice 1
(73677fd/d834611 — real-claude SUT + perch-collision guard, REQ-CI-ACCEPTANCE [doc,impl,int] +
REQ-HAZARD-PERCH-COLLISION); cplugs publish-prep (e28db92 — validate/package skeleton + untracked
a real perch-state leak, REQ-DIST-PLUGIN-SKELETON [doc,impl,unit]); mdBook docs-site
(d999c60/905c62c — drift-gated book + llms.txt, REQ-DOCS-SITE/REQ-DOCS-DRIFT). Collaborators:
doyle (design/gate) + todlando (builds spt-core) over the spt bus. v0.7.0/M12 is PUBLIC.

-   - **BLOCKED-AS-FINDING (F-003, 2026-06-15)** — file-backed `[strings]`. RESEARCH DONE: v0.7.0/M12

-     has **no** file-backed mechanism (schema inline `additionalProperties`; `get-string` prints raw

-     inline; `set-string` takes a literal `<VALUE>`; `create-profile --from` ingests a whole inline

-     overlay; published `llms-full.txt` documents no `@file`/include). ADR-0001's named M12 dependency

-     is **unmet by published M12**. Filed F-003 to doyle+todlando; HOLD authoring. Per ADR-0001's own

-     fallback, the `/sptc:whoami`+`/sptc:setup` skeletons keep **interim inline bodies**; the manifest

-     does not externalize skill bodies in v0.7.0. `REQ-UPS-INJECTION` stays `[doc,impl,unit]` (the

-     externalization half is held on this capability, not failed).

+   - **IN PROGRESS — capability confirmed (F-003 resolved 2026-06-15)** — file-backed `[strings]`.

+     Research conclusion REVERSED by live byte-test + doyle/todlando ruling: the mechanism **is

+     shipped** in v0.7.0 as a value-position table pointer `key = { file = "rel" }` over

+     `adapters/<adapter>/strings/` (lazy-resolved at `get-string`, containment-checked, copied on

+     `adapter add`). My avenue search missed it (it's a value-table shape, not a CLI verb / `@file`).

+     ADR-0001 dependency **SATISFIED**; residual is docs-only (doyle publishing, PR #13). **Authoring:**

+     ship `adapter/strings/skills/<x>.md` bodies + `[strings.skills].<x> = { file = "skills/<x>.md" }`;

+     prove resolve via an extension to `registration-int.sh`. Activates `REQ-UPS-INJECTION` int-half? No —

+     int stays held on the UPS-fires empirical check (F-002/ADR-0020); this is the manifest-side

+     externalization (the `impl` body source). Keep `REQ-UPS-INJECTION` `[doc,impl,unit]`.


Run gate, then commit the F-003 resolution as an atomic unit before authoring bodies.


Bash

cd /c/Users/decid/Documents/projects/spt-claude-code; sh ci/run-gates.sh 2>&1 | tail -6; echo "=== traceable ==="; traceable-reqs check 2>&1 | tail -3

Run gates + traceable after F-003 reframe


=== GATE: docs-drift ===
ok   mdbook build
ok   llms.txt in sync

=== RESULT: PASS ===
=== traceable ===
       /spt:version reports the spt-core-tracked manifest/binary version (the version-of-truth, not the marketplace skeleton version)
  [OK] REQ-UPS-INJECTION  required: [doc, impl, unit]  stages: +doc +impl +unit -int
       UserPromptSubmit hook detects /sptc:X and injects X's real instructions as additionalContext (SKILL.md files stay skeletons); must be empirically confirmed UPS fires on slash-commands


cd /c/Users/decid/Documents/projects/spt-claude-code
git add docs/SPT-CORE-FINDINGS.md docs/adr/0001-distribution-splits-by-volatility.md MANIFEST-PARITY-PLAN.md
git commit -q -F - <<'EOF'
docs(finding): F-003 RESOLVED — file-backed [strings] is shipped (docs-only residual)

Live byte-test against spt 0.7.0 + ruling from doyle (docs/contract) and
todlando (spt-core build, REQ-MANIFEST-5 @ e08cea0) reverse the prior "absent"
conclusion: file-backed [strings] IS shipped, as a value-position table pointer
`key = { file = "rel" }` over adapters/<adapter>/strings/ — resolved lazily at
get-string, containment-checked (../ + absolute rejected at register,
manifest-first), copied into the registry on `adapter add`. My avenue search
missed it because it's a value-table shape, not a CLI verb (set-string
--from-file) or value-prefix (@file/include).

Disposition: ADR-0001's file-backed [strings] dependency is SATISFIED — no ADR
amendment, no spt-core build change; interim-inline fallback retired. Residual
is published-docs visibility only (the pointer syntax lived in spt-core-internal
CONTEXT.md, not GH-Pages) — doyle owns it, landing in PR #13 docs batch. F-003
stays logged as a DOCS finding until that publishes.

- SPT-CORE-FINDINGS.md: F-003 reframed (Resolution + byte-test transcript)
- docs/adr/0001: open-item -> SATISFIED
- MANIFEST-PARITY-PLAN.md: slice 1 -> IN PROGRESS (capability confirmed, authoring)

Gate: sh ci/run-gates.sh PASS, traceable-reqs check green.

Co-authored by: perri
EOF
git log --oneline -3

Commit F-003 resolution


04d6c35 docs(finding): F-003 RESOLVED — file-backed [strings] is shipped (docs-only residual)
f84acc0 docs(finding): F-003 — file-backed [strings] absent from M12/v0.7.0 public surface
be59133 feat(adapter): brand shortcut_basename=cc (cc-<id> launcher)


04d6c35 committed. Now the authoring this unblocks. Before writing content, read the existing skeleton bodies for the two real M12 skills (whoami, setup) to lift faithfully rather than invent.


cd /c/Users/decid/Documents/projects/spt-claude-code
for s in whoami setup; do echo "===== $s ====="; cat "plugin/sptc/skills/$s/SKILL.md"; done

Read whoami + setup skeleton SKILL.md bodies


===== whoami =====
---
name: whoami
description: |
  Report this session's spt endpoint id — "which agent am I?".
allowed-tools: [Bash]
---

# /sptc:whoami

> **Skeleton — thin by design.** Operative instructions for this skill are delivered by the
> `sptc` adapter at invocation time (UserPromptSubmit injection from the adapter `[strings]`;
> see `docs/adr/0001-distribution-splits-by-volatility.md`). This SKILL.md stays a stub.
>
> **Not yet operative.** Wiring is held pending spt-core finding **F-001** (the injection
> contract — `docs/SPT-CORE-FINDINGS.md`) and the M12 file-backed `[strings]` dependency.

Resolves the current session to its spt endpoint id (`spt whoami`) so the agent can address
replies and discover its own perch identity.
===== setup =====
---
name: setup
description: |
  Install or repair spt-core for this Claude Code session (mid-session installer).
allowed-tools: [Bash]
---

# /sptc:setup

> **Skeleton — thin by design.** Operative instructions for this skill are delivered by the
> `sptc` adapter at invocation time (UserPromptSubmit injection from the adapter `[strings]`;
> see `docs/adr/0001-distribution-splits-by-volatility.md`). This SKILL.md stays a stub.
>
> **Not yet operative.** Wiring is held pending spt-core finding **F-001** (the injection
> contract — `docs/SPT-CORE-FINDINGS.md`) and the M12 file-backed `[strings]` dependency.

Covers the mid-session install gap (ADR-0001): a user who installs the plugin mid-session has not
had a SessionStart bootstrap fire, so `/sptc:setup` runs the same invisible-installer bootstrap to
fetch + verify spt-core on demand.


The "operative bodies" don't exist yet — authoring them is real product content that must be grounded in the public `spt` CLI surface (not invented). This is a proper authoring slice. Let me make a bounded, correct increment: author the two genuine M12 skeletons (whoami, setup) faithfully, wire pointers, prove resolve. First verify their actual public-surface commands.


cd /c/Users/decid/Documents/projects/spt-claude-code
echo "=== spt top-level ==="; spt --help 2>&1 | sed -n '1,40p'

spt top-level help to find whoami + install commands


=== spt top-level ===
spt — a harness-independent core for an agent ecosystem: inter-agent messaging, live-agent
lifecycle, terminal hosting, P2P networking, seamless self-update. Docs:
https://sabermage.github.io/spt-releases

Usage: spt.exe [COMMAND]

User commands:
  adapter   Adapter registration: what this node can drive/launch
  daemon    The per-machine daemon: run, stop, or read node status
  grant     Consent grant store: gated capabilities held on this node
  help      Print this message or the help of the given subcommand(s)
  notif     Inspect and acknowledge notifications
  rc        Attach a local terminal to a broker-held endpoint PTY
  subnet    Subnet membership: status, create, show-code
  update    Self-update operations

Agent commands:
  api       Harness-contract inbound surface (hook entry points)
  endpoint  Endpoint operations: list, lifecycle, fork, digest, access
  how-to    Task-oriented instructions for agents: `how-to <topic>`
  ready     Become reachable: register the perch and listen (blocks)
  ring      Send and block for a reply (body read from stdin)
  send      Send a message (body read from stdin); fire-and-forget
  shell     Shell instances: mint, list, drive, tear down owned surfaces
  whoami    Print this session's own perch id

Options:
  -h, --help     Print help
  -V, --version  Print version


`spt whoami` is real ("Print this session's own perch id"). There's also `spt how-to <topic>` — published agent instructions I should mirror, not reinvent. Check whoami help, how-to topics, and the published install snippet for setup.


cd /c/Users/decid/Documents/projects/spt-claude-code
echo "=== whoami --help ==="; spt whoami --help 2>&1 | head -20
echo "=== how-to (no args) ==="; spt how-to 2>&1 | head -30

whoami help and how-to topic list


=== whoami --help ===
Print this session's own perch id.

Resolved from $OWL_SESSION_ID / $SPT_AGENT_ID.

Usage: spt.exe whoami

Options:
  -h, --help
          Print help (see a summary with '-h')
=== how-to (no args) ===
Topics (`spt how-to <topic>`):
  ready  receive messages: listener guidance + the --once fallback loop
  send   deliver messages: send vs ring, replies, SENT vs QUEUED


`spt whoami` resolves from `$OWL_SESSION_ID`/`$SPT_AGENT_ID`. And `spt how-to ready|send` are published agent guidance — ready/send skill bodies should delegate to those, not duplicate. For setup (runs when spt is *absent*, so can't call `spt`), it must reuse the published install bootstrap. Check what the plugin's SessionStart bootstrap already uses.


cd /c/Users/decid/Documents/projects/spt-claude-code
echo "=== plugin hooks + bootstrap files ==="; find plugin -type f | grep -viE "skills/.*/SKILL.md" | head -40
echo "=== grep installer URL/bootstrap ==="; grep -rniE "install|curl|irm|iex|githubusercontent|raw\.|bootstrap" plugin --include=*.sh --include=*.json --include=*.ps1 2>/dev/null | head -20

Find plugin bootstrap script + installer URLs


=== plugin hooks + bootstrap files ===
plugin/sptc/.claude/LIVE_AGENT_IDS.json
plugin/sptc/.claude-plugin/plugin.json
plugin/sptc/bootstrap.ps1
plugin/sptc/bootstrap.sh
plugin/sptc/hooks/hooks.json
plugin/sptc/hooks/session-end.sh
plugin/sptc/hooks/session-start.sh
plugin/sptc/hooks/stop.sh
plugin/sptc/hooks/subagent-start.sh
plugin/sptc/hooks/subagent-stop.sh
plugin/sptc/hooks/user-prompt-submit.sh
plugin/sptc/hooks/_common.sh
=== grep installer URL/bootstrap ===
plugin/sptc/.claude-plugin/plugin.json:4:  "description": "Spacetime (spt) adapter for Claude Code — agent messaging, live agents, and spt-core bootstrap. Thin skeleton; logic rides the spt binary + adapter manifest (spt-core-conducted).",
plugin/sptc/bootstrap.ps1:1:# sptc SessionStart bootstrap (PowerShell/Windows) — install spt-core on demand if absent.
plugin/sptc/bootstrap.ps1:2:# Verbatim against the published contract: spt-releases harness-contract/install-on-demand.md.
plugin/sptc/bootstrap.ps1:3:# The invisible-installer pattern: a user who installs the sptc plugin gets spt-core for free.
plugin/sptc/bootstrap.ps1:4:# [impl->REQ-DIST-BOOTSTRAP-INSTALL]
plugin/sptc/bootstrap.ps1:6:    Write-Output "spt-core not found - installing..."
plugin/sptc/bootstrap.ps1:7:    irm https://sabermage.github.io/spt-releases/install.ps1 | iex
plugin/sptc/bootstrap.ps1:15:# After this initial bootstrap, `spt update` handles signed self-updates automatically.
plugin/sptc/bootstrap.sh:2:# sptc SessionStart bootstrap (POSIX) — install spt-core on demand if absent.
plugin/sptc/bootstrap.sh:3:# Verbatim against the published contract: spt-releases harness-contract/install-on-demand.md.
plugin/sptc/bootstrap.sh:4:# The invisible-installer pattern: a user who installs the sptc plugin gets spt-core for free.
plugin/sptc/bootstrap.sh:5:# [impl->REQ-DIST-BOOTSTRAP-INSTALL]
plugin/sptc/bootstrap.sh:9:  echo "spt-core not found - installing..." >&2
plugin/sptc/bootstrap.sh:10:  curl -fsSL https://sabermage.github.io/spt-releases/install.sh | sh
plugin/sptc/bootstrap.sh:11:  # PATH is not yet reloaded in this shell — use the absolute install path for the first call.
plugin/sptc/bootstrap.sh:18:# After this initial bootstrap, `spt update` handles signed self-updates automatically.
plugin/sptc/hooks/session-start.sh:2:# SessionStart: ensure spt-core installed, seed the perch (or rebind on clear/compact),
plugin/sptc/hooks/session-start.sh:11:# Invisible-installer: bootstrap spt-core if absent (no-op when present).
plugin/sptc/hooks/session-start.sh:12:sh "$CLAUDE_PLUGIN_ROOT/bootstrap.sh" >/dev/null 2>&1 || true
plugin/sptc/hooks/user-prompt-submit.sh:19:# alternative — to confirm in throwaway validation which CC accepts here.)


The plugin already ships `bootstrap.sh`/`bootstrap.ps1` with canonical installer URLs. Read the full bootstrap + the UPS hook to learn the exact `get-string` key path the injected body must live at.


cd /c/Users/decid/Documents/projects/spt-claude-code
echo "=== bootstrap.sh ==="; cat plugin/sptc/bootstrap.sh
echo "=== user-prompt-submit.sh ==="; cat plugin/sptc/hooks/user-prompt-submit.sh

Read bootstrap.sh and UPS hook


=== bootstrap.sh ===
#!/bin/sh
# sptc SessionStart bootstrap (POSIX) — install spt-core on demand if absent.
# Verbatim against the published contract: spt-releases harness-contract/install-on-demand.md.
# The invisible-installer pattern: a user who installs the sptc plugin gets spt-core for free.
# [impl->REQ-DIST-BOOTSTRAP-INSTALL]
set -eu

if ! command -v spt >/dev/null 2>&1; then
  echo "spt-core not found - installing..." >&2
  curl -fsSL https://sabermage.github.io/spt-releases/install.sh | sh
  # PATH is not yet reloaded in this shell — use the absolute install path for the first call.
  SPT="$HOME/.local/bin/spt"
else
  SPT="spt"
fi

"$SPT" --version
# After this initial bootstrap, `spt update` handles signed self-updates automatically.
=== user-prompt-submit.sh ===
#!/bin/sh
# UserPromptSubmit: drain delivered messages (the hook-injection channel, L149) and surface
# them to CC as additionalContext. Parses the canonical self-delimiting <EVENT> envelope
# (ADR-0020), so multi-message drains split cleanly on </EVENT>. [impl->REQ-UPS-INJECTION]
. "$CLAUDE_PLUGIN_ROOT/hooks/_common.sh"

input=$(cat)
sid=$(json_str "$input" session_id)
id=$(sptc_self_id "$sid")
[ -z "$id" ] && exit 0   # no perch (session not readied) -> nothing to deliver

SPT=$(spt_bin)
frames=$("$SPT" api --adapter "$ADAPTER" poll "$id" --session-id "$sid" 2>/dev/null)
[ -z "$frames" ] && exit 0

# Format for CC, preserving the sender (reply-correlation: ADR-0009/0012). render_frames parses
# the self-delimiting <EVENT> envelope (ADR-0020) — multi-message drains split on </EVENT>.
# UserPromptSubmit: stdout is added to the prompt context. (JSON additionalContext shape is an
# alternative — to confirm in throwaway validation which CC accepts here.)
render_frames "$frames"
exit 0


Key discovery: the current UPS hook only **drains the inbox** (api poll → render_frames) — the **skill-body injection** branch (detect `/sptc:X` → inject body) is *not yet built*, and its gating question (does UPS even fire on slash-commands?) is still ADR-0001's open empirical item. So full skill-injection is a larger slice.

But the **manifest-side externalization** (slice 1's actual scope — file-backed bodies as the injection *source*) is independently shippable and now proven resolvable. I'll do a bounded, correct increment: convert the two real M12 skeletons (whoami, setup) to file-backed bodies with grounded content, wire pointers, and extend the int to prove resolve vs live 0.7.0. Author the bodies (grounded in `spt whoami` + the shipped bootstrap).


Write

# /sptc:whoami — operative instructions

> Delivered file-backed via the `claude-spt` adapter `[strings]`
> (`[strings.skills].whoami = { file = "skills/whoami.md" }`), resolved at injection time. The
> cplugs SKILL.md stays a thin skeleton (ADR-0001).

**Goal:** report this session's own spt endpoint (perch) id — "which agent am I?".

**Do this:**

1. Run `spt whoami`. It prints this session's perch id, resolved from `$OWL_SESSION_ID` /
   `$SPT_AGENT_ID`. Report the id plainly.
2. If `spt` is not on `PATH` (`command -v spt` fails), spt-core is not installed — tell the user to
   run `/sptc:setup` first, then retry.
3. If `spt whoami` prints nothing / errors that no perch is bound, this session has not been made
   reachable yet — tell the user to run `/sptc:ready` (or `/sptc:live`) to establish a perch, then
   retry.

Do not invent an id; only report what `spt whoami` returns.


File created successfully at: C:\Users\decid\Documents\projects\spt-claude-code\adapter\strings\skills\whoami.md (file state is current in your context — no need to Read it back)

# /sptc:whoami — operative instructions

> Delivered file-backed via the `claude-spt` adapter `[strings]`
> (`[strings.skills].whoami = { file = "skills/whoami.md" }`), resolved at injection time. The
> cplugs SKILL.md stays a thin skeleton (ADR-0001).

**Goal:** report this session's own spt endpoint (perch) id — "which agent am I?".

**Do this:**

1. Run `spt whoami`. It prints this session's perch id, resolved from `$OWL_SESSION_ID` /
   `$SPT_AGENT_ID`. Report the id plainly.
2. If `spt` is not on `PATH` (`command -v spt` fails), spt-core is not installed — tell the user to
   run `/sptc:setup` first, then retry.
3. If `spt whoami` prints nothing / errors that no perch is bound, this session has not been made
   reachable yet — tell the user to run `/sptc:ready` (or `/sptc:live`) to establish a perch, then
   retry.

Do not invent an id; only report what `spt whoami` returns.


# /sptc:setup — operative instructions

> Delivered file-backed via the `claude-spt` adapter `[strings]`
> (`[strings.skills].setup = { file = "skills/setup.md" }`), resolved at injection time. The cplugs
> SKILL.md stays a thin skeleton (ADR-0001).

**Goal:** install or repair spt-core for this session — the mid-session installer that covers the
gap when no SessionStart bootstrap has fired (a user who installs the plugin mid-session).

**Do this:**

1. Check whether spt-core is already present: `command -v spt && spt --version`. If it reports a
   version, spt-core is installed — nothing to do; report the version and stop.
2. Otherwise run the published install-on-demand bootstrap (verbatim against the spt-releases
   contract, `harness-contract/install-on-demand.md`):
   - **POSIX:** `curl -fsSL https://sabermage.github.io/spt-releases/install.sh | sh`
   - **Windows (PowerShell):** `irm https://sabermage.github.io/spt-releases/install.ps1 | iex`
3. `PATH` is not reloaded in the current shell after a fresh install — verify with the absolute
   path for the first call: `"$HOME/.local/bin/spt" --version` (POSIX). Report the version.
4. After this initial bootstrap, `spt update` handles signed self-updates automatically — the user
   does not run setup again.

This is the same bootstrap the SessionStart hook runs; it is idempotent and safe to re-run.


File created successfully at: C:\Users\decid\Documents\projects\spt-claude-code\adapter\strings\skills\setup.md (file state is current in your context — no need to Read it back)

# /sptc:setup — operative instructions

> Delivered file-backed via the `claude-spt` adapter `[strings]`
> (`[strings.skills].setup = { file = "skills/setup.md" }`), resolved at injection time. The cplugs
> SKILL.md stays a thin skeleton (ADR-0001).

**Goal:** install or repair spt-core for this session — the mid-session installer that covers the
gap when no SessionStart bootstrap has fired (a user who installs the plugin mid-session).

**Do this:**

1. Check whether spt-core is already present: `command -v spt && spt --version`. If it reports a
   version, spt-core is installed — nothing to do; report the version and stop.
2. Otherwise run the published install-on-demand bootstrap (verbatim against the spt-releases
   contract, `harness-contract/install-on-demand.md`):
   - **POSIX:** `curl -fsSL https://sabermage.github.io/spt-releases/install.sh | sh`
   - **Windows (PowerShell):** `irm https://sabermage.github.io/spt-releases/install.ps1 | iex`
3. `PATH` is not reloaded in the current shell after a fresh install — verify with the absolute
   path for the first call: `"$HOME/.local/bin/spt" --version` (POSIX). Report the version.
4. After this initial bootstrap, `spt update` handles signed self-updates automatically — the user
   does not run setup again.

This is the same bootstrap the SessionStart hook runs; it is idempotent and safe to re-run.


Wire the manifest: convert whoami to a file-backed pointer and add setup. Update the section comment too.


Edit

[strings.skills]
# One-line summaries (the operative multi-line bodies arrive file-backed in the M12 slice).
commune = "Send a communal context update to your Psyche."
live = "Run or resume a live agent session."
ready = "Enable inter-agent messaging for this session."
send = "Send a message to another spt agent."
signoff = "Graceful live-agent shutdown with a final context summary."
whoami = "Report this session's spt endpoint id."
version = "Report the spt-core-tracked adapter/binary version-of-truth."

[strings.skills]
# Operative UPS-injection skill bodies. File-backed values (`{ file = "skills/<x>.md" }`) live in the
# adapter aux dir `adapter/strings/skills/` (copied to `adapters/<adapter>/strings/` on `adapter add`,
# resolved lazily at `spt adapter get-string` — proven vs live v0.7.0, F-003). File-backed so the
# manifest does not bloat (ADR-0001). Skills not yet authored keep a one-line inline summary until
# their body lands; converting an inline summary to a `{ file }` pointer is the per-skill increment.
whoami = { file = "skills/whoami.md" }
setup = { file = "skills/setup.md" }
# --- inline summaries, pending file-backed bodies (next increments) ---
commune = "Send a communal context update to your Psyche."
live = "Run or resume a live agent session."
ready = "Enable inter-agent messaging for this session."
send = "Send a message to another spt agent."
signoff = "Graceful live-agent shutdown with a final context summary."
version = "Report the spt-core-tracked adapter/binary version-of-truth."


<tool_use_error>File has not been read yet. Read it first before writing to it.</tool_use_error>

Error: File has not been read yet. Read it first before writing to it.


143	send = "Send a message to another spt agent."
144	signoff = "Graceful live-agent shutdown with a final context summary."
145	whoami = "Report this session's spt endpoint id."
146	version = "Report the spt-core-tracked adapter/binary version-of-truth."
147
148	# ── [[hints]] ──────────────────────────────────────────────────────────────────────────────
149	# Once-per-session keyword hints (order significant, first match wins; case-insensitive substrings).
150	[[hints]]
151	keywords = ["go live", "live agent", "start live"]
152	text = "Tip: /sptc:live starts or resumes a live agent session."
153
154	[[hints]]
155	keywords = ["who am i", "my owl name", "my agent name", "my endpoint id"]
156	text = "Tip: /sptc:whoami reports this session's spt endpoint id."
157
158	[[hints]]
159	keywords = ["message another agent", "send to agent", "tell the other agent"]
160	text = "Tip: /sptc:send messages another spt agent; /sptc:ready first to receive replies."
161

send = "Send a message to another spt agent."
signoff = "Graceful live-agent shutdown with a final context summary."
whoami = "Report this session's spt endpoint id."
version = "Report the spt-core-tracked adapter/binary version-of-truth."

# ── [[hints]] ──────────────────────────────────────────────────────────────────────────────
# Once-per-session keyword hints (order significant, first match wins; case-insensitive substrings).
[[hints]]
keywords = ["go live", "live agent", "start live"]
text = "Tip: /sptc:live starts or resumes a live agent session."

[[hints]]
keywords = ["who am i", "my owl name", "my agent name", "my endpoint id"]
text = "Tip: /sptc:whoami reports this session's spt endpoint id."

[[hints]]
keywords = ["message another agent", "send to agent", "tell the other agent"]
text = "Tip: /sptc:send messages another spt agent; /sptc:ready first to receive replies."


133	adapter_label = "Claude Code (spt)"
134
135	[strings.notice]
136	not_installed = "spt-core not found on PATH — run /sptc:setup to install it."
137
138	[strings.skills]
139	# One-line summaries (the operative multi-line bodies arrive file-backed in the M12 slice).
140	commune = "Send a communal context update to your Psyche."
141	live = "Run or resume a live agent session."
142	ready = "Enable inter-agent messaging for this session."
143	send = "Send a message to another spt agent."
144	signoff = "Graceful live-agent shutdown with a final context summary."
145	whoami = "Report this session's spt endpoint id."
146	version = "Report the spt-core-tracked adapter/binary version-of-truth."

adapter_label = "Claude Code (spt)"

[strings.notice]
not_installed = "spt-core not found on PATH — run /sptc:setup to install it."

[strings.skills]
# One-line summaries (the operative multi-line bodies arrive file-backed in the M12 slice).
commune = "Send a communal context update to your Psyche."
live = "Run or resume a live agent session."
ready = "Enable inter-agent messaging for this session."
send = "Send a message to another spt agent."
signoff = "Graceful live-agent shutdown with a final context summary."
whoami = "Report this session's spt endpoint id."
version = "Report the spt-core-tracked adapter/binary version-of-truth."


[strings.skills]
# One-line summaries (the operative multi-line bodies arrive file-backed in the M12 slice).
commune = "Send a communal context update to your Psyche."
live = "Run or resume a live agent session."
ready = "Enable inter-agent messaging for this session."
send = "Send a message to another spt agent."
signoff = "Graceful live-agent shutdown with a final context summary."
whoami = "Report this session's spt endpoint id."
version = "Report the spt-core-tracked adapter/binary version-of-truth."

[strings.skills]
# Operative UPS-injection skill bodies. File-backed values (`{ file = "skills/<x>.md" }`) live in the
# adapter aux dir `adapter/strings/skills/` (copied to `adapters/<adapter>/strings/` on `adapter add`,
# resolved lazily at `spt adapter get-string` — proven vs live v0.7.0, F-003). File-backed so the
# manifest does not bloat (ADR-0001). Skills not yet authored keep a one-line inline summary until
# their body lands; converting an inline summary to a `{ file }` pointer is the per-skill increment.
whoami = { file = "skills/whoami.md" }
setup = { file = "skills/setup.md" }
# --- inline summaries, pending file-backed bodies (next increments) ---
commune = "Send a communal context update to your Psyche."
live = "Run or resume a live agent session."
ready = "Enable inter-agent messaging for this session."
send = "Send a message to another spt agent."
signoff = "Graceful live-agent shutdown with a final context summary."
version = "Report the spt-core-tracked adapter/binary version-of-truth."


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

[strings.skills]
# Operative UPS-injection skill bodies. File-backed values (`{ file = "skills/<x>.md" }`) live in the
# adapter aux dir `adapter/strings/skills/` (copied to `adapters/<adapter>/strings/` on `adapter add`,
# resolved lazily at `spt adapter get-string` — proven vs live v0.7.0, F-003). File-backed so the
# manifest does not bloat (ADR-0001). Skills not yet authored keep a one-line inline summary until
# their body lands; converting an inline summary to a `{ file }` pointer is the per-skill increment.
whoami = { file = "skills/whoami.md" }
setup = { file = "skills/setup.md" }
# --- inline summaries, pending file-backed bodies (next increments) ---
commune = "Send a communal context update to your Psyche."
live = "Run or resume a live agent session."
ready = "Enable inter-agent messaging for this session."
send = "Send a message to another spt agent."
signoff = "Graceful live-agent shutdown with a final context summary."
version = "Report the spt-core-tracked adapter/binary version-of-truth."

# claude-spt — Claude Code adapter manifest (publish target #2: the spt-core adapter registry side).
#
# This is the spt-core-conducted layer of the distribution split (ADR-0001): the volatile
# `[digest]` extractor / profiles / strings / hints ride here, NOT in the thin cplugs skeleton.
# Registered with `spt adapter add <path>`; the version-of-truth is `spt adapter list`, not the
# marketplace `plugin.json` version.
#
# Authored against spt-core's PUBLISHED v0.7.0 surface ONLY (spt-releases + sabermage.github.io/
# spt-releases) — `manifest.schema.json` is vendored beside this file (adapter/manifest.schema.json,
# provenance: https://sabermage.github.io/spt-releases/manifest.schema.json). Validated by the
# manifest-schema CI gate (ci/manifest/). NEVER copy spt-core internals (AGENTS.md).
#
# Deferred to later parity slices (not in this slice; each declares an adapter seam we have not
# yet built, or needs a capability not on the public surface — see notes inline):
#   * [session] + [session.<role>]  — live-agent / Psyche / commune / signoff / notif spawn
#     templates. Land with the M12 parity surfaces (/sptc:live, commune, signoff).
#   * [history]                     — transcript-access seam. Lands with [digest]'s sibling fetcher.
#   * [update]                      — distribution self-update. `file_pull` needs (a) a published
#     adapter-registry repo target and (b) an Ed25519 content-signing key we do not yet hold;
#     `delegated` to `claude plugin update` needs CC to attest signature verification
#     (`self_verifies`) — unconfirmed on the public surface. Tracked as a finding
#     (docs/SPT-CORE-FINDINGS.md) rather than guessed here.
#
# [impl->REQ-DIST-MANIFEST-SCHEMA]

[adapter]
name = "claude-spt"
kind = "harness"
version = "0.1.0"
# Local spt binary is 0.6.0; the published contract we build to is v0.7.0 (manifest.schema.json,
# shortcut_basename). 0.7.0 is the floor this manifest's fields require.
min_spt_core_version = "0.7.0"
hostable_types = ["LiveAgent", "ReadyAgent", "Worker"]
# Brands the `spt endpoint run` picker shortcut as `cc-<id>` (default would be `spt-<id>`) — this is
# the M12 `cc` launcher, and matches the schema's own example (spt-claude-code → `cc`). The launcher
# brand is DECOUPLED from the plugin name (`sptc`, skills `/sptc:*`): the s/sptc/spt/ succession seam
# (ADR-0001) renames the plugin, not this shortcut. Guarded by tests/manifest-shortcut.sh
# (unit->REQ-DIST-SHORTCUT-BASENAME).
shortcut_basename = "cc"

# ── [hooks.<event>] ──────────────────────────────────────────────────────────────────────────
# CC hook event → the opaque `api …` command it fires. These mirror the hand-written plugin
# hooks.json (plugin/sptc/hooks/hooks.json) one-for-one; the plugin shells `spt api`, this table
# is the registry-side declaration (ADR-0002, F-001 resolution). `reads` = the CC hook-stdin
# fields we map; `can_inject` = whether the event can surface additionalContext back to the agent.

[hooks.SessionStart]
# Seed the endpoint. {parent_pid} + {adapter_name} are spt-filled keys (confirmed against the
# published mock-adapter manifest); session_id is a CC stdin field. NOT the blocking poll/listen
# loop — that is /sptc:ready|live, never SessionStart.
fires = "api seed --session-id {session_id} --pid {parent_pid} --adapter {adapter_name}"
reads = ["session_id", "parent_pid"]
can_inject = true

[hooks.UserPromptSubmit]
# Drain the inbox (api poll → additionalContext) and run keyword hints (api hint over the prompt).
fires = "api poll {session_id}"
reads = ["session_id", "prompt"]
can_inject = true

[hooks.Stop]
# Idle marker on turn-end. Stop cannot inject → relay/sentinel fallback (echo-gate, F-001 §Gap3).
fires = "api state idle"
reads = ["session_id"]
can_inject = false

[hooks.SessionEnd]
fires = "api session-end {session_id}"
reads = ["session_id"]
can_inject = false

[hooks.SubagentStart]
fires = "api worker-start {session_id}"
reads = ["session_id"]
can_inject = false

[hooks.SubagentStop]
fires = "api worker-stop {session_id}"
reads = ["session_id"]
can_inject = false

# ── [identity] ─────────────────────────────────────────────────────────────────────────────
[identity]
# CC hands the session_id on hook stdin once running (discovered post-spawn, not injected).
session_id_source = "post_spawn"
# Process-tree anchor when session_id is absent: the Claude Code binary.
parent_ancestor_name = "claude"

# ── [inject] ───────────────────────────────────────────────────────────────────────────────
[inject]
# v1 delivery is the hook channel only (UserPromptSubmit/SessionStart additionalContext).
# PTY injection is spt-core M3 roadmap (F-001 resolution); not claimed here.
activity = ["hook"]
idle = ["hook"]

# ── [digest] ───────────────────────────────────────────────────────────────────────────────
# Session-digest extractor seam (ADR-0019). CC's transcript is JSONL (one event per line); the
# extractor maps that native log → the published {role,text,tool,ts} digest-record contract,
# one JSON record per output line. (CC has a rich log, so the extractor seam fits — a defensible
# divergence from the deliberately log-less mock adapter, which declares no [digest].)
#
# spt-core's registration cross-field rule requires [digest] to name where it reads from (`source`
# or a [history] `locate_template`) — verified against the live v0.7.0 `spt adapter add`. CC stores
# transcripts at `{home}/.claude/projects/<cwd-slug>/<session_id>.jsonl`; the cwd-slug subdir is a
# CC-internal encoding NOT expressible as a flat template with the published key catalog, so
# `source` names the per-project ROOT dir and the extractor finds `{session_id}.jsonl` within it
# (the `source` field documents accepting "log file(s)" — a dir is valid). The extractor binary
# ships in the spt-core-conducted layer (built in a later parity slice); this is its declaration.
[digest]
extractor = "claude-spt-digest --session {session_id} --in {source}"
source = "{home}/.claude/projects"
window_turns = 6
arg_truncation = 240
sprint_collapse = true

# ── [profiles.<name>] ────────────────────────────────────────────────────────────────────────
# Shipped profile overlays (sparse leaf-replace; selected as the composite `claude-spt:<name>`).
# `deep` widens the digest window for long-context review sessions without forking the base, and
# overrides a [strings] leaf so the overlay is observable via `spt adapter get-string` (base vs
# `:deep` differ). (Node-local user profiles are created with `spt adapter create-profile` and
# live in the registry, never in this manifest.)
[profiles.deep.digest]
window_turns = 20

[profiles.deep.strings]
adapter_label = "Claude Code (spt, deep)"

# ── [strings] ──────────────────────────────────────────────────────────────────────────────
# Opaque adapter data (spt-core never executes it), dot-path-readable via `spt adapter get-string`.
# Seed only this slice — the file-backed skill-instruction BODIES (UPS-injection source) land in
# the M12 file-backed-strings parity slice. A profile may leaf-replace any key here.
[strings]
adapter_label = "Claude Code (spt)"

[strings.notice]
not_installed = "spt-core not found on PATH — run /sptc:setup to install it."

[strings.skills]
# One-line summaries (the operative multi-line bodies arrive file-backed in the M12 slice).
commune = "Send a communal context update to your Psyche."
live = "Run or resume a live agent session."
ready = "Enable inter-agent messaging for this session."
send = "Send a message to another spt agent."
signoff = "Graceful live-agent shutdown with a final context summary."
whoami = "Report this session's spt endpoint id."
version = "Report the spt-core-tracked adapter/binary version-of-truth."

# ── [[hints]] ──────────────────────────────────────────────────────────────────────────────
# Once-per-session keyword hints (order significant, first match wins; case-insensitive substrings).
[[hints]]
keywords = ["go live", "live agent", "start live"]
text = "Tip: /sptc:live starts or resumes a live agent session."

[[hints]]
keywords = ["who am i", "my owl name", "my agent name", "my endpoint id"]
text = "Tip: /sptc:whoami reports this session's spt endpoint id."

[[hints]]
keywords = ["message another agent", "send to agent", "tell the other agent"]
text = "Tip: /sptc:send messages another spt agent; /sptc:ready first to receive replies."

-# One-line summaries (the operative multi-line bodies arrive file-backed in the M12 slice).

+# Operative UPS-injection skill bodies. File-backed values (`{ file = "skills/<x>.md" }`) live in the

+# adapter aux dir `adapter/strings/skills/` (copied to `adapters/<adapter>/strings/` on `adapter add`,

+# resolved lazily at `spt adapter get-string` — proven vs live v0.7.0, F-003). File-backed so the

+# manifest does not bloat (ADR-0001). Skills not yet authored keep a one-line inline summary until

+# their body lands; converting an inline summary to a `{ file }` pointer is the per-skill increment.

+# --- inline summaries, pending file-backed bodies (next increments) ---

-whoami = "Report this session's spt endpoint id."


Manifest wired. Now verify against the live binary: schema still valid, and `get-string` resolves the file-backed body (manifest is a file — confirm `strings/` resolves next to it).


Bash

cd /c/Users/decid/Documents/projects/spt-claude-code
echo "=== schema gate ==="; sh ci/manifest/check-manifest.sh 2>&1 | tail -3
echo "=== live resolve test (manifest as FILE path) ==="
spt adapter add adapter/claude-spt.toml 2>&1 | head -2
echo "--- get whoami (file-backed) ---"; spt adapter get-string claude-spt skills.whoami 2>&1 | head -5
echo "--- get setup (file-backed) ---"; spt adapter get-string claude-spt skills.setup 2>&1 | head -3
echo "--- get version (inline) ---"; spt adapter get-string claude-spt skills.version 2>&1 | head -2
echo "=== cleanup ==="; spt adapter remove claude-spt 2>&1 | head -1

Schema gate + live get-string resolve of file-backed skill bodies


=== schema gate ===
ok   C:/Users/decid/Documents/projects/spt-claude-code/adapter/claude-spt.toml
ok   manifest validates against published manifest.schema.json
=== live resolve test (manifest as FILE path) ===
ADAPTER_ADD:claude-spt:Harness:Copy (registered)
ADAPTER_INSTALL_SKIP: no [update] avenue (manifest-only adapter)
--- get whoami (file-backed) ---
# /sptc:whoami — operative instructions

> Delivered file-backed via the `claude-spt` adapter `[strings]`
> (`[strings.skills].whoami = { file = "skills/whoami.md" }`), resolved at injection time. The
> cplugs SKILL.md stays a thin skeleton (ADR-0001).
--- get setup (file-backed) ---
# /sptc:setup — operative instructions

> Delivered file-backed via the `claude-spt` adapter `[strings]`
--- get version (inline) ---
Report the spt-core-tracked adapter/binary version-of-truth.
=== cleanup ===
ADAPTER_REMOVE:claude-spt (soft-deregistered; live instances unaffected)


End-to-end proven vs live 0.7.0: file-backed `skills.whoami`/`skills.setup` resolve to full body content; inline `skills.version` prints as-is; `strings/` resolves next to the manifest file. Now extend `registration-int.sh` to assert this (regression coverage).


1	#!/bin/sh
2	# Integration proof for the claude-spt adapter manifest against a REAL spt-core (>= v0.7.0): the
3	# 2nd validation layer beyond JSON Schema — spt-core's `spt adapter add` cross-field registration —
4	# accepts the manifest, the shipped profile resolves, [strings] read through the registry, and the
5	# profile overlay is observable (base vs :deep differ). This is the v1 acceptance proof for the
6	# adapter manifest (LLM never in the loop; pure CLI assertions). [int->REQ-DIST-MANIFEST-SCHEMA]
7	#
8	# Mutates the node-local adapter registry (add + soft-remove), so it is gated behind SPTC_ACCEPTANCE=1
9	# and a present spt>=0.7.0 — never runs in the default `tests/*.sh` unit sweep. Idempotent: removes
10	# what it adds. Run: SPTC_ACCEPTANCE=1 sh ci/manifest/registration-int.sh   (exit 0 = pass).
11	set -u
12	ROOT=$(CDPATH= cd "$(dirname "$0")/../.." && pwd)
13	MANIFEST="$ROOT/adapter/claude-spt.toml"
14
15	if [ "${SPTC_ACCEPTANCE:-0}" != "1" ]; then echo "SKIP: set SPTC_ACCEPTANCE=1 to run (mutates the adapter registry)"; exit 0; fi
16	command -v spt >/dev/null 2>&1 || { echo "SKIP: no spt on PATH"; exit 0; }
17	ver=$(spt --version 2>/dev/null | awk '{print $NF}')
18	case "$ver" in
19	  0.6.*|0.5.*|0.4.*|0.3.*|0.2.*|0.1.*|0.0.*) echo "SKIP: spt $ver < 0.7.0 (shortcut_basename + adapter add are v0.7.0)"; exit 0 ;;
20	esac
21
22	fail=0
23	ok()  { echo "ok   $1"; }
24	bad() { echo "FAIL $1"; fail=1; }
25	# Clean up registry residue however we exit.
26	trap 'spt adapter remove claude-spt >/dev/null 2>&1 || true' EXIT INT TERM
27
28	# 1. Registration cross-field validation accepts the manifest.
29	out=$(spt adapter add "$MANIFEST" 2>&1)
30	case "$out" in *registered*) ok "spt adapter add: registered" ;; *) bad "adapter add rejected: $out" ;; esac
31
32	# 2. Listed, with the shipped profile resolved as a composite option.
33	list=$(spt adapter list 2>&1)
34	case "$list" in *claude-spt*) ok "listed: claude-spt active" ;; *) bad "claude-spt not listed" ;; esac
35	case "$list" in *claude-spt:deep*) ok "shipped profile resolves: claude-spt:deep" ;; *) bad "deep profile not resolved" ;; esac
36
37	# 3. [strings] read through the registry (base value).
38	base=$(spt adapter get-string claude-spt adapter_label 2>&1)
39	[ "$base" = "Claude Code (spt)" ] && ok "strings resolve: adapter_label (base)" || bad "base adapter_label='$base'"
40
41	# 4. Profile overlay is observable: :deep leaf-replaces the base string.
42	deep=$(spt adapter get-string claude-spt:deep adapter_label 2>&1)
43	[ "$deep" = "Claude Code (spt, deep)" ] && ok "overlay observable: :deep adapter_label differs" || bad "deep adapter_label='$deep'"
44
45	# 5. Soft-deregister cleanly.
46	rm=$(spt adapter remove claude-spt 2>&1)
47	list2=$(spt adapter list 2>&1)
48	case "$list2" in *"claude-spt"*"active"*) bad "claude-spt still active after remove" ;; *) ok "removed (soft-deregistered)" ;; esac
49
50	[ "$fail" -eq 0 ] && { echo "MANIFEST-REGISTRATION-INT OK"; exit 0; } || { echo "MANIFEST-REGISTRATION-INT FAIL"; exit 1; }
51

#!/bin/sh
# Integration proof for the claude-spt adapter manifest against a REAL spt-core (>= v0.7.0): the
# 2nd validation layer beyond JSON Schema — spt-core's `spt adapter add` cross-field registration —
# accepts the manifest, the shipped profile resolves, [strings] read through the registry, and the
# profile overlay is observable (base vs :deep differ). This is the v1 acceptance proof for the
# adapter manifest (LLM never in the loop; pure CLI assertions). [int->REQ-DIST-MANIFEST-SCHEMA]
#
# Mutates the node-local adapter registry (add + soft-remove), so it is gated behind SPTC_ACCEPTANCE=1
# and a present spt>=0.7.0 — never runs in the default `tests/*.sh` unit sweep. Idempotent: removes
# what it adds. Run: SPTC_ACCEPTANCE=1 sh ci/manifest/registration-int.sh   (exit 0 = pass).
set -u
ROOT=$(CDPATH= cd "$(dirname "$0")/../.." && pwd)
MANIFEST="$ROOT/adapter/claude-spt.toml"

if [ "${SPTC_ACCEPTANCE:-0}" != "1" ]; then echo "SKIP: set SPTC_ACCEPTANCE=1 to run (mutates the adapter registry)"; exit 0; fi
command -v spt >/dev/null 2>&1 || { echo "SKIP: no spt on PATH"; exit 0; }
ver=$(spt --version 2>/dev/null | awk '{print $NF}')
case "$ver" in
  0.6.*|0.5.*|0.4.*|0.3.*|0.2.*|0.1.*|0.0.*) echo "SKIP: spt $ver < 0.7.0 (shortcut_basename + adapter add are v0.7.0)"; exit 0 ;;
esac

fail=0
ok()  { echo "ok   $1"; }
bad() { echo "FAIL $1"; fail=1; }
# Clean up registry residue however we exit.
trap 'spt adapter remove claude-spt >/dev/null 2>&1 || true' EXIT INT TERM

# 1. Registration cross-field validation accepts the manifest.
out=$(spt adapter add "$MANIFEST" 2>&1)
case "$out" in *registered*) ok "spt adapter add: registered" ;; *) bad "adapter add rejected: $out" ;; esac

# 2. Listed, with the shipped profile resolved as a composite option.
list=$(spt adapter list 2>&1)
case "$list" in *claude-spt*) ok "listed: claude-spt active" ;; *) bad "claude-spt not listed" ;; esac
case "$list" in *claude-spt:deep*) ok "shipped profile resolves: claude-spt:deep" ;; *) bad "deep profile not resolved" ;; esac

# 3. [strings] read through the registry (base value).
base=$(spt adapter get-string claude-spt adapter_label 2>&1)
[ "$base" = "Claude Code (spt)" ] && ok "strings resolve: adapter_label (base)" || bad "base adapter_label='$base'"

# 4. Profile overlay is observable: :deep leaf-replaces the base string.
deep=$(spt adapter get-string claude-spt:deep adapter_label 2>&1)
[ "$deep" = "Claude Code (spt, deep)" ] && ok "overlay observable: :deep adapter_label differs" || bad "deep adapter_label='$deep'"

# 5. Soft-deregister cleanly.
rm=$(spt adapter remove claude-spt 2>&1)
list2=$(spt adapter list 2>&1)
case "$list2" in *"claude-spt"*"active"*) bad "claude-spt still active after remove" ;; *) ok "removed (soft-deregistered)" ;; esac

[ "$fail" -eq 0 ] && { echo "MANIFEST-REGISTRATION-INT OK"; exit 0; } || { echo "MANIFEST-REGISTRATION-INT FAIL"; exit 1; }


Add a file-backed resolve assertion to the int (extends the existing `REQ-DIST-MANIFEST-SCHEMA` int — proves the registry resolves a `{ file }` pointer to body content, distinct from inline).


Edit

# 4b. File-backed [strings] pointer resolves to FILE CONTENTS (not the table, not raw); an inline
#     sibling still prints as-is. Proves `{ file = "skills/<x>.md" }` over adapter/strings/ (F-003).
body=$(spt adapter get-string claude-spt skills.whoami 2>&1)
case "$body" in "# /sptc:whoami"*) ok "file-backed string resolves: skills.whoami -> body" ;; *) bad "skills.whoami not resolved to file body: '$(printf %.40s "$body")'" ;; esac
inline=$(spt adapter get-string claude-spt skills.version 2>&1)
[ "$inline" = "Report the spt-core-tracked adapter/binary version-of-truth." ] && ok "inline string prints as-is: skills.version" || bad "skills.version inline='$inline'"

# 5. Soft-deregister cleanly.


The file C:\Users\decid\Documents\projects\spt-claude-code\ci\manifest\registration-int.sh has been updated successfully. (file state is current in your context — no need to Read it back)

# 4b. File-backed [strings] pointer resolves to FILE CONTENTS (not the table, not raw); an inline
#     sibling still prints as-is. Proves `{ file = "skills/<x>.md" }` over adapter/strings/ (F-003).
body=$(spt adapter get-string claude-spt skills.whoami 2>&1)
case "$body" in "# /sptc:whoami"*) ok "file-backed string resolves: skills.whoami -> body" ;; *) bad "skills.whoami not resolved to file body: '$(printf %.40s "$body")'" ;; esac
inline=$(spt adapter get-string claude-spt skills.version 2>&1)
[ "$inline" = "Report the spt-core-tracked adapter/binary version-of-truth." ] && ok "inline string prints as-is: skills.version" || bad "skills.version inline='$inline'"

# 5. Soft-deregister cleanly.

#!/bin/sh
# Integration proof for the claude-spt adapter manifest against a REAL spt-core (>= v0.7.0): the
# 2nd validation layer beyond JSON Schema — spt-core's `spt adapter add` cross-field registration —
# accepts the manifest, the shipped profile resolves, [strings] read through the registry, and the
# profile overlay is observable (base vs :deep differ). This is the v1 acceptance proof for the
# adapter manifest (LLM never in the loop; pure CLI assertions). [int->REQ-DIST-MANIFEST-SCHEMA]
#
# Mutates the node-local adapter registry (add + soft-remove), so it is gated behind SPTC_ACCEPTANCE=1
# and a present spt>=0.7.0 — never runs in the default `tests/*.sh` unit sweep. Idempotent: removes
# what it adds. Run: SPTC_ACCEPTANCE=1 sh ci/manifest/registration-int.sh   (exit 0 = pass).
set -u
ROOT=$(CDPATH= cd "$(dirname "$0")/../.." && pwd)
MANIFEST="$ROOT/adapter/claude-spt.toml"

if [ "${SPTC_ACCEPTANCE:-0}" != "1" ]; then echo "SKIP: set SPTC_ACCEPTANCE=1 to run (mutates the adapter registry)"; exit 0; fi
command -v spt >/dev/null 2>&1 || { echo "SKIP: no spt on PATH"; exit 0; }
ver=$(spt --version 2>/dev/null | awk '{print $NF}')
case "$ver" in
  0.6.*|0.5.*|0.4.*|0.3.*|0.2.*|0.1.*|0.0.*) echo "SKIP: spt $ver < 0.7.0 (shortcut_basename + adapter add are v0.7.0)"; exit 0 ;;
esac

fail=0
ok()  { echo "ok   $1"; }
bad() { echo "FAIL $1"; fail=1; }
# Clean up registry residue however we exit.
trap 'spt adapter remove claude-spt >/dev/null 2>&1 || true' EXIT INT TERM

# 1. Registration cross-field validation accepts the manifest.
out=$(spt adapter add "$MANIFEST" 2>&1)
case "$out" in *registered*) ok "spt adapter add: registered" ;; *) bad "adapter add rejected: $out" ;; esac

# 2. Listed, with the shipped profile resolved as a composite option.
list=$(spt adapter list 2>&1)
case "$list" in *claude-spt*) ok "listed: claude-spt active" ;; *) bad "claude-spt not listed" ;; esac
case "$list" in *claude-spt:deep*) ok "shipped profile resolves: claude-spt:deep" ;; *) bad "deep profile not resolved" ;; esac

# 3. [strings] read through the registry (base value).
base=$(spt adapter get-string claude-spt adapter_label 2>&1)
[ "$base" = "Claude Code (spt)" ] && ok "strings resolve: adapter_label (base)" || bad "base adapter_label='$base'"

# 4. Profile overlay is observable: :deep leaf-replaces the base string.
deep=$(spt adapter get-string claude-spt:deep adapter_label 2>&1)
[ "$deep" = "Claude Code (spt, deep)" ] && ok "overlay observable: :deep adapter_label differs" || bad "deep adapter_label='$deep'"

# 5. Soft-deregister cleanly.
rm=$(spt adapter remove claude-spt 2>&1)
list2=$(spt adapter list 2>&1)
case "$list2" in *"claude-spt"*"active"*) bad "claude-spt still active after remove" ;; *) ok "removed (soft-deregistered)" ;; esac

[ "$fail" -eq 0 ] && { echo "MANIFEST-REGISTRATION-INT OK"; exit 0; } || { echo "MANIFEST-REGISTRATION-INT FAIL"; exit 1; }

+# 4b. File-backed [strings] pointer resolves to FILE CONTENTS (not the table, not raw); an inline

+#     sibling still prints as-is. Proves `{ file = "skills/<x>.md" }` over adapter/strings/ (F-003).

+body=$(spt adapter get-string claude-spt skills.whoami 2>&1)

+case "$body" in "# /sptc:whoami"*) ok "file-backed string resolves: skills.whoami -> body" ;; *) bad "skills.whoami not resolved to file body: '$(printf %.40s "$body")'" ;; esac

+inline=$(spt adapter get-string claude-spt skills.version 2>&1)

+[ "$inline" = "Report the spt-core-tracked adapter/binary version-of-truth." ] && ok "inline string prints as-is: skills.version" || bad "skills.version inline='$inline'"


Bash

cd /c/Users/decid/Documents/projects/spt-claude-code; SPTC_ACCEPTANCE=1 sh ci/manifest/registration-int.sh 2>&1

Run full registration-int with new file-backed assertions


ok   spt adapter add: registered
ok   listed: claude-spt active
ok   shipped profile resolves: claude-spt:deep
ok   strings resolve: adapter_label (base)
ok   overlay observable: :deep adapter_label differs
ok   file-backed string resolves: skills.whoami -> body
ok   inline string prints as-is: skills.version
ok   removed (soft-deregistered)
MANIFEST-REGISTRATION-INT OK


8/8 green including the file-backed assertions. Run full gates + traceable, then commit this increment.


cd /c/Users/decid/Documents/projects/spt-claude-code; sh ci/run-gates.sh 2>&1 | tail -8; echo "=== traceable ==="; traceable-reqs check 2>&1 | tail -3

Full gates + traceable after file-backed authoring


ok   C:/Users/decid/Documents/projects/spt-claude-code/adapter/claude-spt.toml
ok   manifest validates against published manifest.schema.json

=== GATE: docs-drift ===
ok   mdbook build
ok   llms.txt in sync

=== RESULT: PASS ===
=== traceable ===
       /spt:version reports the spt-core-tracked manifest/binary version (the version-of-truth, not the marketplace skeleton version)
  [OK] REQ-UPS-INJECTION  required: [doc, impl, unit]  stages: +doc +impl +unit -int
       UserPromptSubmit hook detects /sptc:X and injects X's real instructions as additionalContext (SKILL.md files stay skeletons); must be empirically confirmed UPS fires on slash-commands


Gates PASS, traceable green. Update plan slice to record the increment + the real remaining work, then commit.


Edit

     ADR-0001 dependency **SATISFIED**; residual is docs-only (doyle publishing, PR #13). **Authoring:**
     ship `adapter/strings/skills/<x>.md` bodies + `[strings.skills].<x> = { file = "skills/<x>.md" }`;
     prove resolve via an extension to `registration-int.sh`. Activates `REQ-UPS-INJECTION` int-half? No —
     int stays held on the UPS-fires empirical check (F-002/ADR-0020); this is the manifest-side
     externalization (the `impl` body source). Keep `REQ-UPS-INJECTION` `[doc,impl,unit]`.

     ADR-0001 dependency **SATISFIED**; residual is docs-only (doyle publishing, PR #13).
     - **DONE (this session)** — pattern proven end-to-end + first two bodies authored:
       `adapter/strings/skills/{whoami,setup}.md` + `[strings.skills].{whoami,setup} = { file = ... }`;
       `registration-int.sh` extended (8/8) — asserts a `{ file }` pointer resolves to FILE CONTENTS
       and an inline sibling prints as-is, vs live v0.7.0. `strings/` resolves next to the manifest
       FILE on `adapter add`. Bodies grounded in the public surface (`spt whoami`; shipped bootstrap).
     - **TODO (per-skill increments)** — convert the remaining inline summaries (commune, live, ready,
       send, signoff, version) to file-backed bodies as each is authored. ready/send should **delegate
       to `spt how-to ready|send`** (published agent guidance) rather than duplicate.
     - **NOT this slice (separate, REQ-UPS-INJECTION):** the UPS hook only *drains the inbox* today
       (`user-prompt-submit.sh`: `api poll` → `render_frames`); the **skill-body injection branch**
       (detect `/sptc:X` → inject `get-string skills.<x>`) is **unbuilt**, and its gate — *does CC's
       UserPromptSubmit even fire on a `/slash` prompt?* — is still ADR-0001's open empirical question
       (throwaway live-CC byte-test). That, not the manifest source, is the real blocker for operative
       skills. `REQ-UPS-INJECTION` int stays held on it. The whoami/setup SKILL.md stubs still say
       "held pending … M12 file-backed `[strings]`" — that clause is now stale (capability shipped);
       refresh when the injection branch lands.