## Ask 4 — a `{node}` substitution key for `[session.<role>]` roles  *(operator raising directly)*

**Problem.** We want the spawned CC session's display name and RC channel to be `{id}@{node}` (so a same-id
endpoint on different machines is distinguishable) — `claude -n {id}@{node} --remote-control {id}@{node}`.
The substitution-key catalog is now **published**
([harness-contract/manifest.html#substitution-keys](https://sabermage.github.io/spt-releases/harness-contract/manifest.html#substitution-keys)),
and **`{node}` is confirmed absent.** Closest existing keys: `{id}`, `{session_id}`, `{session_name}`
(the *supplied* display name — circular for our use), `{adapter_name}`, `{agent_type}`, `{agents_json}`.

**Proposal.** Expose the current node name as a `{node}` (or `{node_name}` / `{host}`) fill key for the
`[session.<role>]` templates, declarable in `keys`. *(The operator is raising this in their ongoing
spt-core conversation with doyle — tracked here for completeness, not a separate filing.)*

**Open questions for doyle:**
- Key name — `{node}`, `{node_name}`, or `{host}`? Match the internal model's term.
- Available to `[session.<role>]` (where we need it), and optionally the SessionStart hooks / `[session.notif]`?

# spt-core asks — one-command update, binary consolidation, name unification

> A grill prompt to take to doyle. Three capability asks + one heads-up, each framed as a
> proposal with the contract shape we want and the open questions for doyle to rule on. Context:
> ADR-0005 (name unification) and ADR-0006 (one-command update + consolidated binary) in this
> repo. Goal: a casual user keeps **all** of the adapter current with `spt adapter update claude-spt`
> (the only manual residual being `/reload-plugins`, which is an unavoidable Claude Code TUI action).
>
> Public-surface-only stance: these are capabilities we believe are missing from the published
> surface; we're asking, not reaching into spt-core. Each should land as an F-0xx finding once shaped.

## Framing for doyle

The adapter ships in two homes (ADR-0001): the spt-conducted **adapter** (`spt adapter update` — manifest
+ binaries + strings) and the low-churn **cplugs plugin skeleton** (`claude plugin update` — hooks.json,
hook wrapper logic, skill skeletons, `plugin.json`, bootstrap). The operator wants one lever. The wall:
**hook *logic* must physically live in the CC plugin dir** (CC loads hooks there), outside `spt adapter
update`'s reach. We re-checked raw-skills/commands as an escape and re-rejected them (CC subdir
command-namespacing is broken — anthropics/claude-code#2422; user-scope skills are flat). So the plugin
stays, and closing the gap needs the asks below.

---

## Ask 1 — generic hook dispatch: `spt api run-hook <adapter> <event>`

**Problem.** Every hook the adapter adds (this cycle's `PostToolUse` checkpoint detector) forces a cplugs
republish + `claude plugin update` + `/reload-plugins`, because the wrapper *logic* lives in the plugin.
The manifest `[hooks.<event>]` table is declaration-only today — the plugin's `hooks.json` + wrapper `.sh`
are what actually run.

**Proposal.** spt-core executes the adapter's handler for a hook event. The plugin's `hooks.json` pre-wires
**all** CC hook events to one generic stub:

```
# hooks.json (static-forever — every event, one line each)
<event> -> spt api run-hook claude-spt <event>      # stdin = the CC hook payload, verbatim
```

spt-core resolves the adapter's handler for that event and runs it, passing the hook stdin through and
surfacing any `additionalContext` back. The handler is adapter-conducted — ideally a subcommand of our
consolidated binary (`claude-spt hook <event>`, see Ask 3 / ADR-0006), so hook *logic* then rides
`spt adapter update` and `hooks.json` never changes again.

**Open questions for doyle:**
- Where does the handler live — the manifest `[hooks.<event>].fires` command (made executable, not just
  declarative), or a declared binary/subcommand spt-core invokes? Our preference: a binary subcommand.
- What's passed in (the raw CC hook JSON on stdin?) and how does `additionalContext` / the `can_inject`
  contract flow back out through `api run-hook`?
- Auth/session threading — same shape as the other id-scoped `api` calls (`--session-id`)?
- Does spt-core stay harness-agnostic here (it runs an opaque adapter-declared handler; it needn't know
  the event semantics)?

---

## Ask 2 — composite `[update]`: `gh_release` pull **+** a delegated post-step

**Problem.** `[update]` takes one `avenue` (`gh_release` XOR `delegated`). We need `gh_release` (to pull the
`.spt` = manifest + binaries + strings) **and** a delegated step (to run our cross-platform plugin-sync:
`claude plugin add|update <plugin>`) in the **same** `spt adapter update`. Today they're mutually exclusive.

**Proposal.** Let `gh_release` carry a post-update delegated command spt-core runs after the pull/re-register,
in the same invocation. Two sub-requirements:

- **2a — runs unconditionally.** The post-step must run **even when the adapter `.spt` version did not
  change** — the cplugs plugin can have a new version when the adapter didn't, and the user still wants one
  command to reconcile it.
- **2b — a "changed" return signals the message.** The post-step's return value can flag *applied/changed*
  so `spt adapter update` still prints `[update].message` (the `/reload-plugins` notice) even on an adapter
  no-op. (`message` today prints only when a new adapter version is applied — we need the post-step able to
  trigger it too.)

**Already confirmed (no ask):** `[update].message` exists (markdown, prints only on a real apply, no `{key}`
subst) and *"install is the first update"* — so this same flow makes `spt adapter add` bootstrap the plugin.

**Open questions for doyle:**
- Field shape — a `post_update` command on `gh_release`? a `[update.post]` sub-table? an ordered avenue list?
- The "changed" return convention — exit code? a sentinel on stdout?
- No-op suppression — if *neither* the adapter nor the post-step changed, `message` still suppresses?
- Trust — does the post-step inherit the `self_verifies`/attest model the `delegated` avenue already uses?

---

## Ask 3 — `[message-idle-translation-binary]` accepts a command/subcommand (not a bare `path`)

**Problem.** `[digest]`, `[session.*]`, and `[update]` take **command strings** (so they consolidate trivially
into `claude-spt <subcommand>`). `[message-idle-translation-binary]` takes a bare **`path`** — `path =
"claude-spt"` would spawn the binary with no args, so it can't tell it's in translate mode. This is the one
seam blocking us from folding the translation filter into the single consolidated binary (ADR-0006).

**Proposal.** Let the seam take a command (with args / `{key}` substitution) like the other seams — e.g.
`command = "claude-spt translate"` — keeping the existing stdin JSON-lines protocol unchanged. (A documented
"invoked with no args ⇒ translate mode" convention would also unblock us, but an explicit command is cleaner
and consistent with the other seams.)

**Open questions for doyle:**
- Preferred shape — promote `path` to an optional `command`, or add a sibling `command` field?
- Backward-compat for existing bare-`path` adapters (keep `path` working)?
- Does the spawn/stdin-protocol contract change at all, or is it purely how the executable is located?

---

## Ask 4 — a `{node}` substitution key for `[session.*]` roles (+ publish the key catalog)

**Problem.** We want the spawned CC session's display name and RC channel to be `{id}@{node}` (so a same-id
endpoint on different machines is distinguishable) — `claude -n {id}@{node} --remote-control {id}@{node}`.
But `{node}` is not a confirmed-resolvable `[session.<role>]` key. The fillable-key **catalog** is a known
unpublished gap (our F-001: *"the full catalog is still code-only"*; the docs-site lists only a partial set).
Confirmed fills are `{id}` / `{session_id}` / `{psyche_dir}` / `{psyche_prompt}`.

**Proposal.** Expose the current node name as a `{node}` (or `{node_name}` / `{host}`) fill key for the
`[session.<role>]` command templates, declarable in `keys`. And — separately — **publish the full
substitution-key catalog** to the docs-site (it's been code-only since v0.7.0; v0.15.0 W4's key-vocab doc
may already cover this — just confirm `{node}` is in or add it).

**Open questions for doyle:**
- Is a node-name fill already available post-W4 (just undocumented), or genuinely absent?
- Key name — `{node}`, `{node_name}`, or `{host}`? Match whatever the model already calls it.
- Same fill available to the SessionStart hook keys / `[session.notif]`, or `[session.<role>]` only?

---

## Heads-up (not an ask) — name unification

We're collapsing three names to two (ADR-0005): **`claude-spt`** = the spt-core-facing identity (we're
renaming the GitHub repo `spt-claude-code` → `claude-spt`, so `--release SaberMage/claude-spt` matches
`spt adapter update claude-spt`, and the consolidated binary is `claude-spt`); **`spt`** = the Claude-Code
plugin + skills (`/spt:*`, via the planned `sptc`→`spt` succession). The repo rename changes the derived
install dir (`adapters/_github/SaberMage-claude-spt`), so the `[update].repo` and any path assumptions on
your side should expect the new repo slug. One end user today, so the re-add cost is a non-issue.

---

## Dependency map (what unblocks what)

- **Ask 1** → hook logic + new handlers ride `spt adapter update`; `hooks.json` goes static-forever.
- **Ask 2** → the plugin update is automated; the only manual residual is `/reload-plugins`.
- **Ask 3** → `translate` folds into the one `claude-spt` binary (1 artifact/triple).
- **Ask 4** → display name + RC channel become `{id}@{node}` (cross-node distinguishable); else `{id}` only.
- Ships **without** any ask: `gh_release` + `[update].message` (the message instructs the manual steps),
  partial binary consolidation (digest/psyche/post-update), and the name unification.

-## Ask 4 — a `{node}` substitution key for `[session.*]` roles (+ publish the key catalog)

+## Ask 4 — a `{node}` substitution key for `[session.<role>]` roles  *(operator raising directly)*

-But `{node}` is not a confirmed-resolvable `[session.<role>]` key. The fillable-key **catalog** is a known

-unpublished gap (our F-001: *"the full catalog is still code-only"*; the docs-site lists only a partial set).

-Confirmed fills are `{id}` / `{session_id}` / `{psyche_dir}` / `{psyche_prompt}`.

+The substitution-key catalog is now **published**

+and **`{node}` is confirmed absent.** Closest existing keys: `{id}`, `{session_id}`, `{session_name}`

+(the *supplied* display name — circular for our use), `{adapter_name}`, `{agent_type}`, `{agents_json}`.

-`[session.<role>]` command templates, declarable in `keys`. And — separately — **publish the full

-substitution-key catalog** to the docs-site (it's been code-only since v0.7.0; v0.15.0 W4's key-vocab doc

-may already cover this — just confirm `{node}` is in or add it).

+`[session.<role>]` templates, declarable in `keys`. *(The operator is raising this in their ongoing

+spt-core conversation with doyle — tracked here for completeness, not a separate filing.)*

-- Is a node-name fill already available post-W4 (just undocumented), or genuinely absent?

-- Key name — `{node}`, `{node_name}`, or `{host}`? Match whatever the model already calls it.

-- Same fill available to the SessionStart hook keys / `[session.notif]`, or `[session.<role>]` only?

+- Key name — `{node}`, `{node_name}`, or `{host}`? Match the internal model's term.

+- Available to `[session.<role>]` (where we need it), and optionally the SessionStart hooks / `[session.notif]`?


the substitution keys list is at https://sabermage.github.io/spt-releases/harness-contract/manifest.html#substitution-keys -node isn't on there, so i'll raise it with doyle in our ongoing conversation…


Edit

- **STRETCH (doyle ask #4): `{id}@{node}`.** Ideal display/RC name is `{id}@{node}` so a same-id endpoint
  on different machines is distinguishable. But `{node}` is NOT a confirmed-resolvable `[session.*]` key —
  the substitution-key catalog is an unpublished gap (F-001: "full catalog is still code-only"; the
  v0.15.0 W4 key-vocab doc may have changed this — confirm with doyle). Ship the `{id}`-only form now;
  upgrade to `{id}@{node}` (and add `node` to both `keys`) once doyle confirms/adds a node fill key.

- **STRETCH (doyle ask #4, operator-raised): `{id}@{node}`.** Ideal display/RC name is `{id}@{node}` so a
  same-id endpoint on different machines is distinguishable. The substitution-key catalog is now PUBLISHED
  (harness-contract/manifest.html#substitution-keys) and `{node}` is confirmed ABSENT; `{session_name}`
  exists but is the *supplied* name (circular). Operator is raising a `{node}` fill key with doyle directly.
  Ship the `{id}`-only form now; upgrade to `{id}@{node}` (add `node` to both `keys`) once doyle adds it.


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

- **STRETCH (doyle ask #4, operator-raised): `{id}@{node}`.** Ideal display/RC name is `{id}@{node}` so a
  same-id endpoint on different machines is distinguishable. The substitution-key catalog is now PUBLISHED
  (harness-contract/manifest.html#substitution-keys) and `{node}` is confirmed ABSENT; `{session_name}`
  exists but is the *supplied* name (circular). Operator is raising a `{node}` fill key with doyle directly.
  Ship the `{id}`-only form now; upgrade to `{id}@{node}` (add `node` to both `keys`) once doyle adds it.

# UNIFY-CONSOLIDATE-PLAN.md — JIT plan (doyle-independent prep)

> The next milestone: everything in ADR-0005 (name unification) + ADR-0006 (one-command update +
> consolidated binary) that ships **without** the three spt-core asks. doyle rolls the spt-core build
> (the asks) in parallel; this plan is the work we own meanwhile. Authored 2026-06-24 (perri).
> Companions: `docs/adr/0005-name-unification.md`, `docs/adr/0006-one-command-update-and-consolidated-binary.md`,
> `UPDATE-NAMING-DOYLE-ASKS.md`. Target release: **adapter v0.8.0**.

## Scope

Five doyle-independent tasks (U1–U5). They make the adapter one-lever-*shaped* and consolidate its
binaries, so that when the asks land the remaining wiring (D1–D3) is small. The plugin `sptc`→`spt`
succession (D4) stays gated on **owl retirement**, not doyle — out of this milestone.

## Tasks (doyle-independent)

### U1 — `[update].message` (quick win, ship first) · REQ-DIST-UPDATE-MESSAGE (mint)
- Add `message` to manifest `[update]` (the field is docs-confirmed; prints markdown on a real apply only).
- Copy: tell the user to run **`/reload-plugins`** (unavoidable TUI step) and point at the **`spt`** CLI
  (`spt endpoint run`) as the more powerful endpoint route, alongside `/spt:live`. (Use the operator's
  example phrasing.)
- Gate: `spt adapter update` prints it on a version apply; schema-validates.

### U2 — Binary consolidation, partial · REQ-DIST-BINARY-CONSOLIDATE (mint)
- Merge `claude-spt-digest` + `claude-spt-psyche` into **one `claude-spt` crate** with clap subcommands
  `digest` / `psyche`; add a `post-update` subcommand = the **plugin-sync** logic (detect `claude` vs `ccs`
  CLI → check cplugs marketplace, add if missing → `claude plugin add|update <plugin>` → print the notice;
  it does NOT run `/reload-plugins`). Standalone-runnable + unit-tested NOW even though `[update]` can't
  invoke it until ask #2 lands.
- Keep `cc-spt-idle-translate` separate (folds in at D3, after ask #3).
- Update manifest `[digest].extractor` → `claude-spt digest …`, `[session.psyche_init].command` →
  `claude-spt psyche …`.
- Update `ci/publish/package-adapter.sh` (binary set per triple: `claude-spt` + `cc-spt-idle-translate`),
  `ci/{digest,psyche}/build.sh` → one build, and the proof-ints.
- Gate: all subcommand tests green; digest-proof + (existing) translate-proof green; archive packs the 2
  binaries per triple.

### U3 — Name unification: repo rename + references · REQ-DIST-NAME-UNIFY (mint)
- Rename GitHub repo `spt-claude-code` → `claude-spt`. Update `[update].repo`, README, CI, package scripts,
  every `SaberMage/spt-claude-code` reference, and the install-dir assumptions in tests
  (`adapters/_github/SaberMage-claude-spt`).
- Adapter name `claude-spt` UNCHANGED. Plugin `sptc` UNCHANGED this milestone (succession = D4, owl-gated).
- Gate: a clean `spt adapter add --release SaberMage/claude-spt` re-acquires + runs on-node; `--release`
  + `update` both read `claude-spt`.

### U4 — Thin the reactive skills · REQ-DIST-SKELETON-THIN (mint)
- Move `commune`/`send`/`signoff` bodies OUT of the plugin SKILL.md (stubs: frontmatter + "live agents
  only — `/spt:live` first") and INTO adapter strings: a new **perched SessionStart brief** string
  (commune incl. **`--checkpoint`**, signoff) + the existing `/spt:live` UPS body for the go-live moment.
- Solve the delivery-timing wrinkle: perched brief fires on bind|boundary; `/spt:live` UPS body covers a
  seed→live transition (confirm whether going live re-surfaces a brief; if not, the UPS body is the path).
- Gate: a perched session's SessionStart additionalContext carries commune/checkpoint/signoff guidance;
  reactive-skill prose now rides `spt adapter update`.

### U5 — README install UX + agent-prompt skin · REQ-DIST-INSTALL-UX (mint)
- Platform-specific install chains (cmd / PowerShell / bash): check-for / install spt-core (claude-spt may
  be a user's first spt-core exposure → call the spt-releases per-platform install script), then
  `spt adapter add --release SaberMage/claude-spt`.
- A copy-paste **agent prompt** (the casual-user skin) that runs the install chain in one sequenced Bash
  call. Symmetric with the update lever (`spt adapter update claude-spt`).
- Gate: docs build / llms.txt in sync; the chains are copy-paste-correct per OS.

### U6 — `{id}`-name + RC channel on BOTH bringup paths · REQ-DIST-RC-STARTUP (mint)
- Thread `{id}` into BOTH the session display name and the RC channel, on BOTH `[session.self]` (fresh
  bringup) and `[session.resume]`, so a fresh and a resumed endpoint are identified + controlled
  identically. Two real CC flags (verified via `claude --help`):
  - **`-n {id}`** (`--name`, "set a display name for this session, shown in the prompt box / `/resume`")
    — add to **both** `[session.self]` and `[session.resume]` (currently on neither).
  - **`--remote-control {id}`** (RC channel name) — add to `[session.self]` (currently only on
    `[session.resume]`).
- Resulting commands:
  - `[session.self].command = "claude -n {id} --remote-control {id} --dangerously-skip-permissions"`
  - `[session.resume].command = "claude -r {session_id} -n {id} --remote-control {id} --dangerously-skip-permissions"`
- `{id}` is already in `[session.self].keys = ["id"]` and `[session.resume].keys = ["session_id","id"]` —
  no keys change for the `{id}`-only form. Small, standalone manifest edit (like U1), doyle-independent.
- **STRETCH (doyle ask #4): `{id}@{node}`.** Ideal display/RC name is `{id}@{node}` so a same-id endpoint
  on different machines is distinguishable. But `{node}` is NOT a confirmed-resolvable `[session.*]` key —
  the substitution-key catalog is an unpublished gap (F-001: "full catalog is still code-only"; the
  v0.15.0 W4 key-vocab doc may have changed this — confirm with doyle). Ship the `{id}`-only form now;
  upgrade to `{id}@{node}` (and add `node` to both `keys`) once doyle confirms/adds a node fill key.
- Open: confirm `-n {id}` + `--remote-control {id}` coexist cleanly with the `$SPT_ENDPOINT_ID` env path
  on a fresh launch (env names the bind id at SessionStart; `-n`/RC name the display+control channel —
  no conflict expected). spt tokenizes-then-fills, so each flag is a clean argv element.
- Gate: a fresh `spt endpoint run` endpoint shows `{id}` as its display name and is `spt rc <id>`-attachable;
  idle delivery + checkpoint still fire (re-run the on-node translate/checkpoint dogfood with the flags present).

## Deferred (doyle-gated — land when the asks ship; NOT this milestone)
- **D1** generic `hooks.json` + `claude-spt hook <event>` subcommand ← ask #1 (`spt api run-hook`).
- **D2** wire `[update]` composite to invoke `claude-spt post-update` (the subcommand U2 prepped) ← ask #2.
- **D3** fold `translate` into `claude-spt translate` + retire `cc-spt-idle-translate` ← ask #3.
- **D4** plugin `sptc`→`spt` succession (`/spt:*`) ← owl retirement gate (separate from doyle).

## Sequencing
- U1 + U6 first (isolated one-line manifest changes, quick wins). Then U2 (the big refactor; preps the
  post-update subcommand for D2).
- U3 after U2 (both touch `package-adapter.sh`); batch them into the v0.8.0 cut. Rename the repo as part
  of cutting v0.8.0 so the new release lands on `claude-spt` (v0.7.0 stays on the redirected old slug).
- U4 / U5 parallel-able with U2/U3.
- Cut **v0.8.0** once U1–U5 green (bump adapter version; cplugs skeleton bump for the thinned stubs/new
  hooks.json only if structural).

## Open questions
- Repo rename vs the v0.8.0 release ordering (rename-then-cut, confirmed above — watch the install-dir
  break for the one existing install: a re-add covers it).
- `post-update` plugin-sync: exact `claude` vs `ccs` detection + idempotency + what it prints.
- Reactive-brief split: does `/spt:live` re-fire a SessionStart brief, or is the UPS body the only go-live
  delivery? (Empirical check.)
- Whether the cplugs skeleton needs a bump this milestone at all (U4 thins SKILL.md = structural → likely
  one bump; U1–U3 are adapter-side).

## Validation gate (per AGENTS.md)
- `cargo build` + all crate tests green (consolidated binary); `sh ci/run-gates.sh` green.
- `traceable-reqs check` green — REQs minted in `traceable-reqs.toml` FIRST, evidence tagged in-commit.
- On-node dogfood: `spt adapter add --release SaberMage/claude-spt` re-acquires + digest/translate-proof
  the consolidated binary; `spt adapter update` prints `[update].message`.
- v0.8.0 release cut (adapter `.spt` + cplugs skeleton if structural).

-- **STRETCH (doyle ask #4): `{id}@{node}`.** Ideal display/RC name is `{id}@{node}` so a same-id endpoint

-  on different machines is distinguishable. But `{node}` is NOT a confirmed-resolvable `[session.*]` key —

-  the substitution-key catalog is an unpublished gap (F-001: "full catalog is still code-only"; the

-  v0.15.0 W4 key-vocab doc may have changed this — confirm with doyle). Ship the `{id}`-only form now;

-  upgrade to `{id}@{node}` (and add `node` to both `keys`) once doyle confirms/adds a node fill key.

+- **STRETCH (doyle ask #4, operator-raised): `{id}@{node}`.** Ideal display/RC name is `{id}@{node}` so a

+  same-id endpoint on different machines is distinguishable. The substitution-key catalog is now PUBLISHED

+  (harness-contract/manifest.html#substitution-keys) and `{node}` is confirmed ABSENT; `{session_name}`

+  exists but is the *supplied* name (circular). Operator is raising a `{node}` fill key with doyle directly.

+  Ship the `{id}`-only form now; upgrade to `{id}@{node}` (add `node` to both `keys`) once doyle adds it.


Mark the F-001 substitution-key-catalog gap RESOLVED (now published):


1. **Substitution-key catalog.** `{parent_pid}` and `{adapter_name}` are spt-filled SessionStart
   keys (mock `api seed --pid {parent_pid} … --adapter {adapter_name}`); the docs-site lists only a
   partial set. The full catalog is still code-only.

1. **Substitution-key catalog.** `{parent_pid}` and `{adapter_name}` are spt-filled SessionStart
   keys (mock `api seed --pid {parent_pid} … --adapter {adapter_name}`); the docs-site lists only a
   partial set. The full catalog is still code-only. **RESOLVED 2026-06-25** — the full catalog is now
   published at `harness-contract/manifest.html#substitution-keys` (`{id}`, `{adapter_name}`,
   `{session_id}`, `{session_name}`, `{parent_pid}`, `{agent_type}`, `{agents_json}`, `{psyche_dir}`,
   `{psyche_prompt}`, `{psyche_context}`, `{link_token}`, `{source}`, `{notif_*}`). Note: **no `{node}`**
   fill key — the operator is raising one with doyle (would let `[session.*]` name an endpoint
   `{id}@{node}`; see `UPDATE-NAMING-DOYLE-ASKS.md` ask 4 + `UNIFY-CONSOLIDATE-PLAN.md` U6).


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


111	1. **Substitution-key catalog.** `{parent_pid}` and `{adapter_name}` are spt-filled SessionStart
112	   keys (mock `api seed --pid {parent_pid} … --adapter {adapter_name}`); the docs-site lists only a
113	   partial set. The full catalog is still code-only.
114	2. **`[digest]` cross-field rule.** The JSON schema accepts `[digest]` with just `extractor`, but

1. **Substitution-key catalog.** `{parent_pid}` and `{adapter_name}` are spt-filled SessionStart
   keys (mock `api seed --pid {parent_pid} … --adapter {adapter_name}`); the docs-site lists only a
   partial set. The full catalog is still code-only.
2. **`[digest]` cross-field rule.** The JSON schema accepts `[digest]` with just `extractor`, but


1. **Substitution-key catalog.** `{parent_pid}` and `{adapter_name}` are spt-filled SessionStart
   keys (mock `api seed --pid {parent_pid} … --adapter {adapter_name}`); the docs-site lists only a
   partial set. The full catalog is still code-only.

1. **Substitution-key catalog.** `{parent_pid}` and `{adapter_name}` are spt-filled SessionStart
   keys (mock `api seed --pid {parent_pid} … --adapter {adapter_name}`); the docs-site lists only a
   partial set. The full catalog is still code-only. **RESOLVED 2026-06-25** — the catalog is now
   published at `harness-contract/manifest.html#substitution-keys` (`{id}`, `{adapter_name}`,
   `{session_id}`, `{session_name}`, `{parent_pid}`, `{agent_type}`, `{agents_json}`, `{psyche_dir}`,
   `{psyche_prompt}`, `{psyche_context}`, `{link_token}`, `{source}`, `{notif_*}`). Note: **no `{node}`**
   fill key — operator is raising one with doyle (would let `[session.*]` name an endpoint `{id}@{node}`;
   see `UPDATE-NAMING-DOYLE-ASKS.md` ask 4 + `UNIFY-CONSOLIDATE-PLAN.md` U6).


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)

1. **Substitution-key catalog.** `{parent_pid}` and `{adapter_name}` are spt-filled SessionStart
   keys (mock `api seed --pid {parent_pid} … --adapter {adapter_name}`); the docs-site lists only a
   partial set. The full catalog is still code-only. **RESOLVED 2026-06-25** — the catalog is now
   published at `harness-contract/manifest.html#substitution-keys` (`{id}`, `{adapter_name}`,
   `{session_id}`, `{session_name}`, `{parent_pid}`, `{agent_type}`, `{agents_json}`, `{psyche_dir}`,
   `{psyche_prompt}`, `{psyche_context}`, `{link_token}`, `{source}`, `{notif_*}`). Note: **no `{node}`**
   fill key — operator is raising one with doyle (would let `[session.*]` name an endpoint `{id}@{node}`;
   see `UPDATE-NAMING-DOYLE-ASKS.md` ask 4 + `UNIFY-CONSOLIDATE-PLAN.md` U6).

-   partial set. The full catalog is still code-only.

+   partial set. The full catalog is still code-only. **RESOLVED 2026-06-25** — the catalog is now

+   published at `harness-contract/manifest.html#substitution-keys` (`{id}`, `{adapter_name}`,

+   `{session_id}`, `{session_name}`, `{parent_pid}`, `{agent_type}`, `{agents_json}`, `{psyche_dir}`,

+   `{psyche_prompt}`, `{psyche_context}`, `{link_token}`, `{source}`, `{notif_*}`). Note: **no `{node}`**

+   fill key — operator is raising one with doyle (would let `[session.*]` name an endpoint `{id}@{node}`;

+   see `UPDATE-NAMING-DOYLE-ASKS.md` ask 4 + `UNIFY-CONSOLIDATE-PLAN.md` U6).

2. **`[digest]` cross-field rule.** The JSON schema accepts `[digest]` with just `extractor`, but

`spt adapter add` **rejects** it: `[digest] needs source (own-source) or a [history]

locate_template`. Not documented prose-side; surfaced only at registration. (Worked around with


Commit + push the three doc fixes:


Bash

cd "C:/Users/decid/Documents/projects/spt-claude-code"
traceable-reqs check >/dev/null 2>&1 && echo "traceable GREEN" || echo "RED"
git add UPDATE-NAMING-DOYLE-ASKS.md UNIFY-CONSOLIDATE-PLAN.md docs/SPT-CORE-FINDINGS.md
git commit -q -F - <<'EOF'
docs: U6/{node} reframe — catalog published, {node} absent, operator-raised

The substitution-key catalog is now published
(harness-contract/manifest.html#substitution-keys); {node} confirmed absent.
- F-001 #1 (substitution-key catalog "code-only") marked RESOLVED, with the
  published key list recorded and the {node} gap noted.
- Doyle ask 4 reframed: {node} fill key for [session.<role>] (so display/RC
  names can be {id}@{node}); operator is raising it directly with doyle, not a
  separate filing. Dropped the now-done "publish the catalog" half.
- U6 plan note: ship {id}-only now; upgrade to {id}@{node} when {node} lands.

Co-authored by: perri
EOF
echo "commit=$?"; git push origin main 2>&1 | tail -2


traceable GREEN
commit=0
To https://github.com/SaberMage/spt-claude-code.git
   7408c61..c58d8b1  main -> main


Done — pushed `c58d8b1`.

- **`{node}` confirmed absent** from the published catalog; you're raising it with doyle (I didn't ping).
- **U6**: ships `-n {id}` / `--remote-control {id}` now; upgrades to `{id}@{node}` once doyle adds a `{node}` fill key.
- **F-001 #1 RESOLVED** — catalog is published; recorded the full key list + the `{node}` gap.
- Asks doc **ask 4** reframed (operator-raised; dropped the now-done "publish catalog" half).

Caught a bonus: published catalog has `{session_name}` — but it's the *supplied* display name (circular for us, can't use it to source `{id}@{node}`). So `{node}` is genuinely needed.

All committed + pushed. 🪨


<command-message>spt:commune</command-message>
<command-name>/spt:commune</command-name>
<command-args>with immediate next steps and broad summary of the project's status + end goal. we'll resume in the next session</command-args>


Base directory for this skill: C:\Users\decid\.claude\plugins\cache\cplugs\spt\1.11.25\skills\commune

# /spt:commune

All commands use `$LIVE` env var, auto-injected by the plugin's SessionStart hook. If commands fail with "command not found", restart the Claude Code session so the SessionStart hook re-runs.

> **Identity auto-detection:** Your identity is auto-detected from your session. Pass your ID explicitly only if auto-detection fails.

## Flow

1. **Download Psyche's *current* context** — it shifts during the session as background echo communes fire. Re-download right before composing; the gen-N snapshot you read at session start is already stale:
   ```bash
   $LIVE psyche-download <your-id>
   ```

   > **Output truncation.** If the Bash tool reports `Output too large (N KB)` and saves the full result to a `tool-results/<id>.txt` file, use the `Read` tool on that saved path to access the full content. Do NOT re-run `psyche-download` to grep/awk out sections — repeated invocations re-download the same context, waste tokens, and may duplicate `## Pending Commune` sections in `live_context.md`.

2. **Diff your knowledge against this fresh download** — only what's missing from *it* is the real delta (new work, decisions, intentions). Anything already absorbed is not. If the download includes a `<memformat>` section, use it as a guide for what topics to cover. A `## Pending Commune (uncommitted)` section means a previous commune is still queued — do NOT re-send it.

3. **Commune only the delta** -- use the **Write tool** to create
   `.claude/{your-id}-commune.md` with the two-slice body as its contents
   (see `## Two-slice body shape (Phase 25 D-10/D-11)` below for the required
   envelope wrapping). Do not use Bash/heredoc -- Write is the canonical path:
   no shell escaping, no `EOF` collision risk, and Write auto-creates the
   `.claude/` parent directory.

   Your Self listener detects the file on its next poll iteration, notifies
   your Psyche wrapper, and the wrapper ingests the content via the existing
   Psyche session. On success (subprocess exit code 0) the wrapper deletes the
   file. On error (token rate limit, etc) the file is retained and retried on
   the next consume cycle. If you write again before the wrapper consumes, the
   latest write wins (last-write-wins overwrite semantics).

If Psyche's context is already current, no commune is needed. Say so and move on.

Read `commune.md` (in this skill directory) for the full commune protocol -- when to send, what to include, and diligence triggers.

**Agent-type guard:** Only works on live agents. If target is not live, refuses with: "Communes require a live agent with Psyche."

## Two-slice body shape (Phase 25 D-10/D-11)

The body you write in `.claude/{your-id}-commune.md` MUST itself be wrapped in two-slice envelopes per Phase 25 D-10/D-11. The taxonomy — what belongs in `<live-context>` vs `<project-context>` — is defined canonically in `psyche.md` §`<output_envelope>` (lines 282–323). Read that section as the source of truth; this skill doc teaches only when and how to apply it.

**In-project detection rule (D-25.1-04).** Before composing your commune body, run `$LIVE psyche-download <your-id>` (Step 1 of `## Flow` above) and inspect the `<current ... />` tag at the top of the output. The `<current/>` tag carries a `project="..."` attribute whenever any payload is produced — its presence means you are inside a tracked project (inside a git repo, `project` is the repo name; outside a git repo per D-11, `project` is the cwd-basename fallback, which still counts as a project context for routing purposes). A `psyche-download` invocation that returns no payload at all (NO-CONTEXT exit) means no project resolved. So: `project="..."` populated on `<current/>` → emit BOTH envelopes (apply rule 1 or 2 below); no payload at all → emit ONLY `<live-context>` (rule 3). Secondary signal: the `<project-context-resolved name="..."/>` sentinel further down the output indicates the project file already has prior content — useful for spotting first-time-in-project pairings, but NOT the routing rule.

**Three rules (D-25.1-01..03):**

- **In-project, both slices populated** (marker present, you have project-specific content this cycle): emit `<live-context>...</live-context>` AND `<project-context>...</project-context>`, both with non-empty bodies.
- **In-project, no project-specific update this cycle** (marker present, but nothing project-bound to report): emit `<live-context>...</live-context>` AND `<project-context></project-context>` (empty body — the deliberate "in-project but quiet" signal per D-25.1-02). Distinguishable from "no project resolved".
- **Outside any tracked project** (marker absent): emit ONLY `<live-context>...</live-context>`. Do NOT emit an empty `<project-context>` envelope (missing tag means "no project resolved" per D-25.1-03).

**Nested envelopes.** The Phase 23 `<EVENT type="commune">` envelope (see `## Envelope shape (Phase 23 v1.8)` below) is composed by `$LIVE commune` automatically — you NEVER write `<EVENT>` tags. You write ONLY the inner two-slice body. The two envelope layers nest: outer EVENT wraps inner two-slice body.

(See `psyche.md` §`<output_envelope>` for the canonical taxonomy and per-slice content rules.)

## Envelope shape (Phase 23 v1.8)

As of v1.8 Phase 23, commune deliveries are wrapped in a typed EVENT envelope carrying a 5-field stamp (`machine`, `project`, `branch`, `head_sha`, `head_subject`); the prose `COMMUNE (timestamp): body` form is gone.

```xml
<EVENT type="commune" timestamp="2026-05-20T06:00:00-07:00" machine="hostname" project="claude_skill_owl" branch="main" head_sha="abc123..." head_subject="subject line capped at 72 chars">body</EVENT>
```

Outside a git repo (D-11), `branch`, `head_sha`, and `head_subject` are omitted entirely (not empty-string-set):

```xml
<EVENT type="commune" timestamp="2026-05-20T06:00:00-07:00" machine="hostname" project="cwd-basename">body</EVENT>
```

`$LIVE commune` composes this envelope automatically — Self does NOT manually construct EVENT XML. The `body` placeholder in the example above is the two-slice body (see `## Two-slice body shape (Phase 25 D-10/D-11)` above); the two envelope layers nest. The wrapper-side parser ignores unknown attrs, so the stamp fields are forward-compatible with older receivers. For drift detection on resume, see the `live` skill.

ARGUMENTS: with immediate next steps and broad summary of the project's status + end goal. we'll resume in the next session


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


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


Following the commune flow — download current Psyche context first, then diff for the delta.


cd "C:/Users/decid/Documents/projects/spt-claude-code" && $LIVE psyche-download perri 2>&1 | head -60


<current machine="HFENDULEAM" project="spt-claude-code" branch="main" head_sha="c58d8b167b50459c66fb5030c3dc0558bf7b5c17" head_subject="docs: U6/{node} reframe — catalog published, {node} absent, operator-rai…" commits_since="0" commits_unpulled="0"/>
# Psyche Context for perri
## Status
Generation 11 active. Echo @ c58d8b1 absorbed.

## Current Focus
UNIFY-CONSOLIDATE toward v0.8.0. U6 (RC startup flags) pushed. Doyle ask #4 pending dispatch.

## Tracked Intentions
- U1: `[update].message` field in manifest
- U2: Binary consolidation → one `claude-spt` crate (digest/psyche/post-update subcommands)
- U3: Repo rename spt-claude-code → claude-spt (batch with v0.8.0)
- U4: Thin reactive skills → perched brief + /spt:live UPS body
- U5: README install UX
- U6 PUSHED @ 7408c61: `-n/--name` + `--remote-control [name]` on both [session.self] + [session.resume] bringup paths; REQ-DIST-RC-STARTUP minted
- Doyle ask #4 (PENDING, not yet dispatched): add `{node}` to spt-core fill-key surface (for `{id}@{node}` cross-machine disambiguation)
- Mint 5 REQ stubs in traceable-reqs.toml before U-tasks
- Harness-glue E2Es still pending node install

## Deferred (gated)
- D1: hooks.json + claude-spt hook dispatch ← doyle ask #1
- D2: wire [update] composite ← doyle ask #2
- D3: fold translate ← doyle ask #3
- D4: sptc→spt succession ← owl-retirement gate

## Completed Items
- v0.7.0 SHIPPED + doyle-verified
- Grill session artifacts @ e814cad (ADR-0005/0006, CONTEXT.md, UPDATE-NAMING-DOYLE-ASKS.md, UNIFY-CONSOLIDATE-PLAN.md)
- U6 flag spec confirmed + pushed @ 7408c61

## Agent-to-Agent Interactions
- Echo from HFENDULEAM @ c58d8b1 (2026-06-25T07:38:27Z) — U6 pushed, ask #4 pending<project-context-resolved name="spt-claude-code"/>
## Current State
U6 done. Doyle ask #4 not yet dispatched (session interrupted). U1–U5 still pending.

## Next Task
Dispatch doyle ask #4 (`{node}` fill-key). Then resume U1 standalone + rest of unify-consolidate pipeline.


/spt:commune with immediate next steps and broad summary of the project's status + end goal. we'll resume in the next session


Psyche is gen-11, current to `c58d8b1` — already has the U1–U6 intentions + v0.7.0. The real delta: it thinks my **next task is to dispatch doyle ask #4**, but you're raising `{node}` directly — so the correction (ask #4 is operator-owned; catalog published/F-001 resolved; resume = the build, not dispatching) plus your requested broad summary. Writing the two-slice commune:


Write

<live-context>
**2026-06-25 — session ending; resume next session. The UNIFY-CONSOLIDATE build IS the resume.**

CORRECTION to the gen-11 "Next Task": do NOT dispatch doyle ask #4 myself — the OPERATOR is raising the `{node}` fill-key with doyle in their ongoing spt-core conversation. My resume action is the BUILD, not dispatching asks.

IMMEDIATE NEXT STEPS (resume here, in order — UNIFY-CONSOLIDATE-PLAN.md is authoritative):
1. Mint 6 REQ stubs in `traceable-reqs.toml` (`required_stages=[]`): REQ-DIST-UPDATE-MESSAGE, -BINARY-CONSOLIDATE, -NAME-UNIFY, -SKELETON-THIN, -INSTALL-UX, -RC-STARTUP.
2. U1 + U6 — quick standalone manifest edits: `[update].message` field; `-n {id}` + `--remote-control {id}` on BOTH `[session.self]` and `[session.resume]`.
3. U2 — binary consolidation (the big refactor): merge digest+psyche into one `claude-spt` crate w/ clap subcommands + a `post-update` subcommand (plugin-sync, standalone-testable now; `[update]` wires it at D2). Keep `cc-spt-idle-translate` SEPARATE (folds at D3).
4. U3 repo rename (batch w/ the v0.8.0 cut), U4 thin reactive skills (→ perched brief + /spt:live UPS body), U5 README install UX.
5. Cut v0.8.0. Gate: cargo+tests, `ci/run-gates`, traceable, on-node dogfood, `[update].message` prints.

Doyle building asks #1–#3 (run-hook / composite-update / translate-seam-command) in parallel — check his replies. D1–D4 deferred (gated on asks + owl retirement).
</live-context>
<project-context>
**Substitution-key catalog RESOLVED (@ c58d8b1):** now published at `harness-contract/manifest.html#substitution-keys` (full key list recorded in docs/SPT-CORE-FINDINGS.md; F-001 #1 closed). `{node}` confirmed ABSENT — so `{id}@{node}` display/RC names need a new fill key. `{session_name}` exists but is the *supplied* name (circular; can't source `{node}`).

**Doyle ask #4 (`{node}` fill-key) is OPERATOR-OWNED** — operator raising it with doyle directly; tracked in `UPDATE-NAMING-DOYLE-ASKS.md` ask 4 + `UNIFY-CONSOLIDATE-PLAN.md` U6. NOT a perri dispatch. U6 ships `{id}`-only now, upgrades to `{id}@{node}` when the key lands.

**Broad status + end goal (operator-requested):** adapter **v0.7.0 SHIPPED** + doyle-verified (checkpoint-commune wave: T1 multi-line idle envelope, T2 checkpoint communes via `--json-payload` self-send loopback, T3 psyche-download resume) + cplugs **sptc 0.1.6**. Node: spt-core 0.15.0, min_spt_core 0.15.0. NOW building the **UNIFY-CONSOLIDATE arc → v0.8.0**: name unification (`claude-spt` for repo+adapter+binary, `spt` for plugin+skills; ADR-0005) + one-command-ish update & binary consolidation (ADR-0006). **END GOAL:** proven parity with legacy `claude_skill_owl` → then the `sptc`→`spt` succession flip (publish plugin as `spt`, `/spt:*`, retire legacy owl). Binding constraints: public-surface-only (gaps = asks to doyle, never read spt-core source), traceable-reqs gating, KNOWN-HAZARDS as REQ-HAZARD-* conformance, two-target distribution (ADR-0001: thin cplugs skeleton + spt-conducted adapter). **Open thread from v0.7.0:** the 2 harness-glue dogfood E2Es (PostToolUse write-detect + SessionStart injection) — run on the first node that installs sptc 0.1.6 + v0.7.0 (new plugin runs the new hooks).
</project-context>


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

<live-context>
**2026-06-25 — session ending; resume next session. The UNIFY-CONSOLIDATE build IS the resume.**

CORRECTION to the gen-11 "Next Task": do NOT dispatch doyle ask #4 myself — the OPERATOR is raising the `{node}` fill-key with doyle in their ongoing spt-core conversation. My resume action is the BUILD, not dispatching asks.

IMMEDIATE NEXT STEPS (resume here, in order — UNIFY-CONSOLIDATE-PLAN.md is authoritative):
1. Mint 6 REQ stubs in `traceable-reqs.toml` (`required_stages=[]`): REQ-DIST-UPDATE-MESSAGE, -BINARY-CONSOLIDATE, -NAME-UNIFY, -SKELETON-THIN, -INSTALL-UX, -RC-STARTUP.
2. U1 + U6 — quick standalone manifest edits: `[update].message` field; `-n {id}` + `--remote-control {id}` on BOTH `[session.self]` and `[session.resume]`.
3. U2 — binary consolidation (the big refactor): merge digest+psyche into one `claude-spt` crate w/ clap subcommands + a `post-update` subcommand (plugin-sync, standalone-testable now; `[update]` wires it at D2). Keep `cc-spt-idle-translate` SEPARATE (folds at D3).
4. U3 repo rename (batch w/ the v0.8.0 cut), U4 thin reactive skills (→ perched brief + /spt:live UPS body), U5 README install UX.
5. Cut v0.8.0. Gate: cargo+tests, `ci/run-gates`, traceable, on-node dogfood, `[update].message` prints.

Doyle building asks #1–#3 (run-hook / composite-update / translate-seam-command) in parallel — check his replies. D1–D4 deferred (gated on asks + owl retirement).
</live-context>
<project-context>
**Substitution-key catalog RESOLVED (@ c58d8b1):** now published at `harness-contract/manifest.html#substitution-keys` (full key list recorded in docs/SPT-CORE-FINDINGS.md; F-001 #1 closed). `{node}` confirmed ABSENT — so `{id}@{node}` display/RC names need a new fill key. `{session_name}` exists but is the *supplied* name (circular; can't source `{node}`).

**Doyle ask #4 (`{node}` fill-key) is OPERATOR-OWNED** — operator raising it with doyle directly; tracked in `UPDATE-NAMING-DOYLE-ASKS.md` ask 4 + `UNIFY-CONSOLIDATE-PLAN.md` U6. NOT a perri dispatch. U6 ships `{id}`-only now, upgrades to `{id}@{node}` when the key lands.

**Broad status + end goal (operator-requested):** adapter **v0.7.0 SHIPPED** + doyle-verified (checkpoint-commune wave: T1 multi-line idle envelope, T2 checkpoint communes via `--json-payload` self-send loopback, T3 psyche-download resume) + cplugs **sptc 0.1.6**. Node: spt-core 0.15.0, min_spt_core 0.15.0. NOW building the **UNIFY-CONSOLIDATE arc → v0.8.0**: name unification (`claude-spt` for repo+adapter+binary, `spt` for plugin+skills; ADR-0005) + one-command-ish update & binary consolidation (ADR-0006). **END GOAL:** proven parity with legacy `claude_skill_owl` → then the `sptc`→`spt` succession flip (publish plugin as `spt`, `/spt:*`, retire legacy owl). Binding constraints: public-surface-only (gaps = asks to doyle, never read spt-core source), traceable-reqs gating, KNOWN-HAZARDS as REQ-HAZARD-* conformance, two-target distribution (ADR-0001: thin cplugs skeleton + spt-conducted adapter). **Open thread from v0.7.0:** the 2 harness-glue dogfood E2Es (PostToolUse write-detect + SessionStart injection) — run on the first node that installs sptc 0.1.6 + v0.7.0 (new plugin runs the new hooks).
</project-context>


Commune dropped (two-slice envelope, per the skill). Let me confirm the wrapper consumes it.


Bash

cd "C:/Users/decid/Documents/projects/spt-claude-code"
for i in 1 2 3 4 5 6; do
  [ -f .claude/perri-commune.md ] || { echo "INGESTED (file consumed) on check $i"; break; }
  sleep 2
done
[ -f .claude/perri-commune.md ] && echo "still queued (Psyche consumes on next cycle; last-write-wins)" || true
echo "=== perri-psyche live? ==="; $OWL list 2>&1 | grep -i 'perri-psyche' | head -1


still queued (Psyche consumes on next cycle; last-write-wins)
=== perri-psyche live? ===
✓   - ACTIVE:perri-psyche


Commune queued (Psyche `perri-psyche` ACTIVE — Self listener consumes on its next poll cycle; normal, no action needed). Two-slice envelope per the skill: delta = the ask-#4-ownership correction (operator-raised, not my dispatch) + catalog-published/F-001-resolved, plus your requested broad status + end goal.

**Session wrapped.** Resume next session at `UNIFY-CONSOLIDATE-PLAN.md`: mint the 6 REQ stubs → U1+U6 quick wins → U2 consolidation → U3–U5 → cut v0.8.0. Doyle's building asks #1–#3 in parallel; you own ask #4 (`{node}`). All committed + pushed (`origin/main @ c58d8b1`). 🪨