Write

**2026-06-24 — checkpoint-commune wave SHIPPED (v0.7.0); next milestone = UNIFY-CONSOLIDATE (doyle-independent prep) PLANNED. Resuming next session.**

## Where we are
- **v0.7.0 SHIPPED end-to-end + doyle-verified.** The checkpoint-commune wave (T1 multi-line idle envelope, T2 checkpoint communes via `--json-payload` self-send loopback, T3 psyche-download resume) is built, validated on real spt-core v0.15.0, gated by doyle, and PUBLISHED to BOTH targets: adapter `.spt` GitHub release v0.7.0 (asset `adapter.spt`, Latest) + cplugs skeleton sptc 0.1.6 (@b14bb19). See memory [[checkpoint-commune-wave-built]] for the full detail. Only OPEN thread there = the 2 harness-glue dogfood E2Es (PostToolUse write-detect + SessionStart injection), to run on the first node that installs sptc 0.1.6 + v0.7.0 (new plugin runs the new hooks).
- **Grill-with-docs session just completed** (operator + me) on TWO topics: (1) one-command update, (2) name unification. Output committed `e814cad`, pushed to origin/main: ADR-0005, ADR-0006, CONTEXT.md (`naming-domains` term), `UPDATE-NAMING-DOYLE-ASKS.md`, `UNIFY-CONSOLIDATE-PLAN.md`.

## IMMEDIATE NEXT STEPS (resume here, in order)
Execute `UNIFY-CONSOLIDATE-PLAN.md` — the doyle-INDEPENDENT prep toward **adapter v0.8.0**. doyle is rolling the spt-core build (the 3 asks) IN PARALLEL — check his replies before/while working.
1. **Mint the 5 REQ stubs FIRST** in `traceable-reqs.toml` (REQ-DIST-UPDATE-MESSAGE, -BINARY-CONSOLIDATE, -NAME-UNIFY, -SKELETON-THIN, -INSTALL-UX), `required_stages=[]` until each task starts (AGENTS.md rule 3).
2. **U1 — `[update].message`** (quick win): add the field to the manifest (docs-confirmed; prints markdown on real apply). Copy = run `/reload-plugins` (unavoidable TUI step) + point at the `spt` CLI as the powerful endpoint route + `/spt:live`.
3. **U2 — Binary consolidation (partial):** merge claude-spt-digest + claude-spt-psyche → ONE `claude-spt` crate w/ clap subcommands `digest`/`psyche`, + a `post-update` subcommand (plugin-sync: detect claude vs ccs CLI → check cplugs marketplace → `claude plugin add|update` → print notice; standalone+unit-tested now, [update] wires it later at D2). Keep `cc-spt-idle-translate` SEPARATE (folds in at D3 after ask #3). Update manifest seam commands + `ci/publish/package-adapter.sh` + build scripts + proof-ints.
4. **U3 — Repo rename** `spt-claude-code`→`claude-spt` (adapter name UNCHANGED; plugin sptc UNCHANGED — succession is D4, owl-gated). Update `[update].repo`, README, CI, package scripts, install-dir test assumptions. Batch with the v0.8.0 cut.
5. **U4 — Thin reactive skills:** commune/send/signoff SKILL.md → stubs; bodies → perched SessionStart brief (commune incl. `--checkpoint`, signoff) + the `/spt:live` UPS body. Solve the delivery-timing wrinkle (brief fires bind|boundary, not the instant a seed goes live — confirm if going-live re-surfaces a brief; else UPS body is the path).
6. **U5 — README install UX:** platform-specific chains (cmd/pwsh/bash: check/install spt-core → adapter add) + copy-paste agent-prompt skin, symmetric with `spt adapter update claude-spt`.
7. **Cut v0.8.0** once U1–U5 green (gate: cargo+all tests, ci/run-gates, traceable, on-node dogfood, `[update].message` prints).

## DEFERRED (NOT this milestone — gated)
- **D1** generic hooks.json + `claude-spt hook <event>` ← doyle ask #1 (`spt api run-hook`).
- **D2** wire `[update]` composite to invoke `claude-spt post-update` ← 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).

## The 3 doyle asks (in UPDATE-NAMING-DOYLE-ASKS.md — sent to doyle, he's building)
1. `spt api run-hook <adapter> <event>` — generic hook dispatch (hook logic rides the adapter; hooks.json static-forever). Handler ideally a binary subcommand.
2. Composite `[update]` — gh_release + delegated post-step in one `spt adapter update`; post-step runs UNCONDITIONALLY + a "changed" return prints `[update].message` on an adapter no-op.
3. `[message-idle-translation-binary]` take a command (not bare `path`) so translate consolidates.

## Project status + end goal (broad)
**spt-claude-code = the rebuilt Claude Code harness adapter (`claude-spt`)**, built against spt-core's PUBLISHED surface ONLY (gaps = findings/asks to doyle; never read spt-core source). It is spt-core's v1 acceptance proof + first casual-end-user entrypoint (a CC plugin on cplugs) + invisible spt-core installer. **Node: adapter v0.7.0, min_spt_core 0.15.0, cplugs sptc 0.1.6, spt-core 0.15.0 on this machine.** End goal = proven parity with legacy `claude_skill_owl` → then the `sptc`→`spt` succession flip (publish the plugin as `spt`, `/spt:*`, retire legacy owl). The checkpoint-commune wave closed a big parity chunk; the unify/consolidate arc + the one-command-update story make it shippable to casual users. Binding constraints: public-surface-only, traceable-reqs gating, KNOWN-HAZARDS as REQ-HAZARD-* conformance, two-target distribution (ADR-0001: thin cplugs skeleton + spt-conducted adapter).

## Naming (decided, ADR-0005 — applies going forward)
TWO tokens: **`claude-spt`** = spt-core-facing (repo after rename + adapter + consolidated binary); **`spt`** = CC-facing (plugin + skills `/spt:*`, via succession). Can't be one token (adapter ≠ `spt` = spt-core's id; skills want `/spt:*`). `cc` launcher decoupled.

## Recipes / comms
- Comms: legacy `$OWL send doyle perri` (new-spt `spt send doyle` = NO_PERCH). Bash tool for sends (PowerShell has no printf).
- Release: build Win native (ci/{digest,psyche,idle-translate}/build.sh) + Linux cross (cargo-zigbuild, zig 0.14.1 at `~/.sptc-zig/zig-x86_64-windows-0.14.1` → PATH); `sh ci/publish/package-adapter.sh --apply`; `gh release create <tag> dist/adapter.spt#adapter.spt`; cplugs skeleton via `sh ci/publish/package-skeleton.sh --apply` → marketplace clone → `git -C` commit+push. gh authed as SaberMage. NO .github/workflows (no docs-publish action).
- Validation: swap installed `cc-spt-idle-translate.exe` (Harness Pointer at AppData/Local/spt-core/adapters/_github/SaberMage-spt-claude-code/), `spt endpoint run --adapter claude-spt --id <id> --create --start --subnet SPT_DEV`, perch session_id in `owlery/<id>/info.json` (CC post_spawn uuid, NOT endpoint-run session), transcript at ~/.claude/projects/<slug>/<cc-uuid>.jsonl. Teardown: `spt endpoint purge <id> --force --yes` + restore binary.


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)

**2026-06-24 — checkpoint-commune wave SHIPPED (v0.7.0); next milestone = UNIFY-CONSOLIDATE (doyle-independent prep) PLANNED. Resuming next session.**

## Where we are
- **v0.7.0 SHIPPED end-to-end + doyle-verified.** The checkpoint-commune wave (T1 multi-line idle envelope, T2 checkpoint communes via `--json-payload` self-send loopback, T3 psyche-download resume) is built, validated on real spt-core v0.15.0, gated by doyle, and PUBLISHED to BOTH targets: adapter `.spt` GitHub release v0.7.0 (asset `adapter.spt`, Latest) + cplugs skeleton sptc 0.1.6 (@b14bb19). See memory [[checkpoint-commune-wave-built]] for the full detail. Only OPEN thread there = the 2 harness-glue dogfood E2Es (PostToolUse write-detect + SessionStart injection), to run on the first node that installs sptc 0.1.6 + v0.7.0 (new plugin runs the new hooks).
- **Grill-with-docs session just completed** (operator + me) on TWO topics: (1) one-command update, (2) name unification. Output committed `e814cad`, pushed to origin/main: ADR-0005, ADR-0006, CONTEXT.md (`naming-domains` term), `UPDATE-NAMING-DOYLE-ASKS.md`, `UNIFY-CONSOLIDATE-PLAN.md`.

## IMMEDIATE NEXT STEPS (resume here, in order)
Execute `UNIFY-CONSOLIDATE-PLAN.md` — the doyle-INDEPENDENT prep toward **adapter v0.8.0**. doyle is rolling the spt-core build (the 3 asks) IN PARALLEL — check his replies before/while working.
1. **Mint the 5 REQ stubs FIRST** in `traceable-reqs.toml` (REQ-DIST-UPDATE-MESSAGE, -BINARY-CONSOLIDATE, -NAME-UNIFY, -SKELETON-THIN, -INSTALL-UX), `required_stages=[]` until each task starts (AGENTS.md rule 3).
2. **U1 — `[update].message`** (quick win): add the field to the manifest (docs-confirmed; prints markdown on real apply). Copy = run `/reload-plugins` (unavoidable TUI step) + point at the `spt` CLI as the powerful endpoint route + `/spt:live`.
3. **U2 — Binary consolidation (partial):** merge claude-spt-digest + claude-spt-psyche → ONE `claude-spt` crate w/ clap subcommands `digest`/`psyche`, + a `post-update` subcommand (plugin-sync: detect claude vs ccs CLI → check cplugs marketplace → `claude plugin add|update` → print notice; standalone+unit-tested now, [update] wires it later at D2). Keep `cc-spt-idle-translate` SEPARATE (folds in at D3 after ask #3). Update manifest seam commands + `ci/publish/package-adapter.sh` + build scripts + proof-ints.
4. **U3 — Repo rename** `spt-claude-code`→`claude-spt` (adapter name UNCHANGED; plugin sptc UNCHANGED — succession is D4, owl-gated). Update `[update].repo`, README, CI, package scripts, install-dir test assumptions. Batch with the v0.8.0 cut.
5. **U4 — Thin reactive skills:** commune/send/signoff SKILL.md → stubs; bodies → perched SessionStart brief (commune incl. `--checkpoint`, signoff) + the `/spt:live` UPS body. Solve the delivery-timing wrinkle (brief fires bind|boundary, not the instant a seed goes live — confirm if going-live re-surfaces a brief; else UPS body is the path).
6. **U5 — README install UX:** platform-specific chains (cmd/pwsh/bash: check/install spt-core → adapter add) + copy-paste agent-prompt skin, symmetric with `spt adapter update claude-spt`.
7. **Cut v0.8.0** once U1–U5 green (gate: cargo+all tests, ci/run-gates, traceable, on-node dogfood, `[update].message` prints).

## DEFERRED (NOT this milestone — gated)
- **D1** generic hooks.json + `claude-spt hook <event>` ← doyle ask #1 (`spt api run-hook`).
- **D2** wire `[update]` composite to invoke `claude-spt post-update` ← 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).

## The 3 doyle asks (in UPDATE-NAMING-DOYLE-ASKS.md — sent to doyle, he's building)
1. `spt api run-hook <adapter> <event>` — generic hook dispatch (hook logic rides the adapter; hooks.json static-forever). Handler ideally a binary subcommand.
2. Composite `[update]` — gh_release + delegated post-step in one `spt adapter update`; post-step runs UNCONDITIONALLY + a "changed" return prints `[update].message` on an adapter no-op.
3. `[message-idle-translation-binary]` take a command (not bare `path`) so translate consolidates.

## Project status + end goal (broad)
**spt-claude-code = the rebuilt Claude Code harness adapter (`claude-spt`)**, built against spt-core's PUBLISHED surface ONLY (gaps = findings/asks to doyle; never read spt-core source). It is spt-core's v1 acceptance proof + first casual-end-user entrypoint (a CC plugin on cplugs) + invisible spt-core installer. **Node: adapter v0.7.0, min_spt_core 0.15.0, cplugs sptc 0.1.6, spt-core 0.15.0 on this machine.** End goal = proven parity with legacy `claude_skill_owl` → then the `sptc`→`spt` succession flip (publish the plugin as `spt`, `/spt:*`, retire legacy owl). The checkpoint-commune wave closed a big parity chunk; the unify/consolidate arc + the one-command-update story make it shippable to casual users. Binding constraints: public-surface-only, traceable-reqs gating, KNOWN-HAZARDS as REQ-HAZARD-* conformance, two-target distribution (ADR-0001: thin cplugs skeleton + spt-conducted adapter).

## Naming (decided, ADR-0005 — applies going forward)
TWO tokens: **`claude-spt`** = spt-core-facing (repo after rename + adapter + consolidated binary); **`spt`** = CC-facing (plugin + skills `/spt:*`, via succession). Can't be one token (adapter ≠ `spt` = spt-core's id; skills want `/spt:*`). `cc` launcher decoupled.

## Recipes / comms
- Comms: legacy `$OWL send doyle perri` (new-spt `spt send doyle` = NO_PERCH). Bash tool for sends (PowerShell has no printf).
- Release: build Win native (ci/{digest,psyche,idle-translate}/build.sh) + Linux cross (cargo-zigbuild, zig 0.14.1 at `~/.sptc-zig/zig-x86_64-windows-0.14.1` → PATH); `sh ci/publish/package-adapter.sh --apply`; `gh release create <tag> dist/adapter.spt#adapter.spt`; cplugs skeleton via `sh ci/publish/package-skeleton.sh --apply` → marketplace clone → `git -C` commit+push. gh authed as SaberMage. NO .github/workflows (no docs-publish action).
- Validation: swap installed `cc-spt-idle-translate.exe` (Harness Pointer at AppData/Local/spt-core/adapters/_github/SaberMage-spt-claude-code/), `spt endpoint run --adapter claude-spt --id <id> --create --start --subnet SPT_DEV`, perch session_id in `owlery/<id>/info.json` (CC post_spawn uuid, NOT endpoint-run session), transcript at ~/.claude/projects/<slug>/<cc-uuid>.jsonl. Teardown: `spt endpoint purge <id> --force --yes` + restore binary.


please write a JIT plan for this next milestone (what can be done before doyle implements). i'll have doyle rolling the spt-core build alongside you. then /commune with immediate next steps and broad…


Commune dropped. Let me confirm my Psyche ingested it (file disappears = success).


Bash

cd "C:/Users/decid/Documents/projects/spt-claude-code"
for i in 1 2 3 4 5; do
  [ -f .claude/perri-commune.md ] || { echo "INGESTED (file gone) on check $i"; break; }
  echo "check $i: still present, waiting..."; sleep 2
done
[ -f .claude/perri-commune.md ] && echo "NOTE: commune still on disk after 10s (Psyche may be mid-ingest; self-heals)" || true
echo "=== git clean + synced ==="; git status --short; git log origin/main..HEAD --oneline | head || echo "in sync"


check 1: still present, waiting...
check 2: still present, waiting...
check 3: still present, waiting...
check 4: still present, waiting...
check 5: still present, waiting...
NOTE: commune still on disk after 10s (Psyche may be mid-ingest; self-heals)
=== git clean + synced ===


cd "C:/Users/decid/Documents/projects/spt-claude-code"
echo "=== perri + psyche liveness ==="; $OWL list 2>&1 | grep -iE 'perri' | head
echo "=== commune file now? ==="; ls -la .claude/perri-commune.md 2>&1 | tail -1 || echo "gone (ingested)"


=== perri + psyche liveness ===
✓ ACTIVE:perri
✓   - ACTIVE:perri-psyche
=== commune file now? ===
-rw-r--r-- 1 decid 197609 6567 Jun 24 23:58 .claude/perri-commune.md


Psyche `perri-psyche` is **ACTIVE** — ingesting the 6.5KB commune via a resume turn (async; file deletes on success, self-heals per the live-agent contract). Memory ([[checkpoint-commune-wave-built]] + index) is the durable backup regardless.

**Session wrapped. Everything captured + pushed (`origin/main @ e814cad`).**

What landed this turn:
- **JIT plan** → `UNIFY-CONSOLIDATE-PLAN.md` — the doyle-**independent** prep toward **v0.8.0**: U1 `[update].message`, U2 binary consolidation (digest/psyche/post-update → one `claude-spt`), U3 repo rename, U4 thin reactive skills, U5 README install UX. D1–D4 deferred (3 doyle asks + owl-gated succession).
- **Commune** dropped → immediate next steps (mint 5 REQ stubs, then U1→U5) + broad status + end-goal, for next-session resume.
- **doyle pinged** the asks doc — he's building the 3 spt-core capabilities in parallel.

**Resume next session:** start at `UNIFY-CONSOLIDATE-PLAN.md` U1, check doyle's replies first. Caveman holds. 🪨


please add to the JIT: in the adapter manifest, set `--remote-control <id>` for endpoint startup, too. right now i think it only sets on resume.


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


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


Right — `[session.self]` (fresh spt-hosted startup) is `claude --dangerously-skip-permissions`, no `--remote-control`; only `[session.resume]` sets `--remote-control {id}`. Adding to the JIT as U6.


Edit

- Gate: docs build / llms.txt in sync; the chains are copy-paste-correct per OS.

## Deferred (doyle-gated — land when the asks ship; NOT this milestone)

- Gate: docs build / llms.txt in sync; the chains are copy-paste-correct per OS.

### U6 — `[session.self]` sets `--remote-control {id}` (RC-channel parity) · REQ-DIST-RC-STARTUP (mint)
- Today only `[session.resume]` names the RC session via `--remote-control {id}`; `[session.self]`
  (fresh spt-hosted bringup) is bare `claude --dangerously-skip-permissions` and threads `{id}` only via
  `$SPT_ENDPOINT_ID`. Add `--remote-control {id}` to `[session.self].command` so BOTH bringup paths name
  the RC channel `{id}` — `spt rc <id>` attach + RC-driven control behave identically for fresh and
  resumed endpoints (not just resume).
- Small, standalone manifest change (like U1) — doyle-independent.
- Open: verify `claude --remote-control {id}` works on a FRESH launch (no `-r`); confirm it doesn't
  collide with the `$SPT_ENDPOINT_ID` env path (both can coexist — env for SessionStart bind, RC for the
  control channel). Add `{id}` to `[session.self].keys`.
- Gate: a fresh `spt endpoint run` endpoint is `spt rc <id>`-attachable; idle delivery + checkpoint still
  fire (re-run the on-node translate/checkpoint dogfood with the RC flag present).

## Deferred (doyle-gated — land when the asks ship; NOT this milestone)


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)

- Gate: docs build / llms.txt in sync; the chains are copy-paste-correct per OS.

### U6 — `[session.self]` sets `--remote-control {id}` (RC-channel parity) · REQ-DIST-RC-STARTUP (mint)
- Today only `[session.resume]` names the RC session via `--remote-control {id}`; `[session.self]`
  (fresh spt-hosted bringup) is bare `claude --dangerously-skip-permissions` and threads `{id}` only via
  `$SPT_ENDPOINT_ID`. Add `--remote-control {id}` to `[session.self].command` so BOTH bringup paths name
  the RC channel `{id}` — `spt rc <id>` attach + RC-driven control behave identically for fresh and
  resumed endpoints (not just resume).
- Small, standalone manifest change (like U1) — doyle-independent.
- Open: verify `claude --remote-control {id}` works on a FRESH launch (no `-r`); confirm it doesn't
  collide with the `$SPT_ENDPOINT_ID` env path (both can coexist — env for SessionStart bind, RC for the
  control channel). Add `{id}` to `[session.self].keys`.
- Gate: a fresh `spt endpoint run` endpoint is `spt rc <id>`-attachable; idle delivery + checkpoint still
  fire (re-run the on-node translate/checkpoint dogfood with the RC flag present).

## Deferred (doyle-gated — land when the asks ship; NOT this milestone)

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

## 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 first (isolated, quick). 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).

+### U6 — `[session.self]` sets `--remote-control {id}` (RC-channel parity) · REQ-DIST-RC-STARTUP (mint)

+- Today only `[session.resume]` names the RC session via `--remote-control {id}`; `[session.self]`

+  (fresh spt-hosted bringup) is bare `claude --dangerously-skip-permissions` and threads `{id}` only via

+  `$SPT_ENDPOINT_ID`. Add `--remote-control {id}` to `[session.self].command` so BOTH bringup paths name

+  the RC channel `{id}` — `spt rc <id>` attach + RC-driven control behave identically for fresh and

+- Small, standalone manifest change (like U1) — doyle-independent.

+- Open: verify `claude --remote-control {id}` works on a FRESH launch (no `-r`); confirm it doesn't

+  collide with the `$SPT_ENDPOINT_ID` env path (both can coexist — env for SessionStart bind, RC for the

+  control channel). Add `{id}` to `[session.self].keys`.

+- Gate: a fresh `spt endpoint run` endpoint is `spt rc <id>`-attachable; idle delivery + checkpoint still

+  fire (re-run the on-node translate/checkpoint dogfood with the RC flag present).


+ add to both the actual session name set. so that's the `-n <id>` flag


Update the plan's sequencing to slot U6 with the other quick standalone (U1):


## Sequencing
- U1 first (isolated, quick). Then U2 (the big refactor; preps the post-update subcommand for D2).

## Sequencing
- U1 + U6 first (isolated one-line manifest changes, quick wins). Then U2 (the big refactor; preps the
  post-update subcommand for D2).


## Sequencing
- U1 + U6 first (isolated one-line manifest changes, quick wins). Then U2 (the big refactor; preps the
  post-update subcommand for D2).

# 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 — `[session.self]` sets `--remote-control {id}` (RC-channel parity) · REQ-DIST-RC-STARTUP (mint)
- Today only `[session.resume]` names the RC session via `--remote-control {id}`; `[session.self]`
  (fresh spt-hosted bringup) is bare `claude --dangerously-skip-permissions` and threads `{id}` only via
  `$SPT_ENDPOINT_ID`. Add `--remote-control {id}` to `[session.self].command` so BOTH bringup paths name
  the RC channel `{id}` — `spt rc <id>` attach + RC-driven control behave identically for fresh and
  resumed endpoints (not just resume).
- Small, standalone manifest change (like U1) — doyle-independent.
- Open: verify `claude --remote-control {id}` works on a FRESH launch (no `-r`); confirm it doesn't
  collide with the `$SPT_ENDPOINT_ID` env path (both can coexist — env for SessionStart bind, RC for the
  control channel). Add `{id}` to `[session.self].keys`.
- Gate: a fresh `spt endpoint run` endpoint is `spt rc <id>`-attachable; idle delivery + checkpoint still
  fire (re-run the on-node translate/checkpoint dogfood with the RC flag 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 first (isolated, quick). 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).

-- U1 first (isolated, quick). Then U2 (the big refactor; preps the post-update subcommand for D2).

+- U1 + U6 first (isolated one-line manifest changes, quick wins). Then U2 (the big refactor; preps the


Commit the plan amendment + push.


Bash

cd "C:/Users/decid/Documents/projects/spt-claude-code"
git add UNIFY-CONSOLIDATE-PLAN.md
git commit -q -F - <<'EOF'
docs(plan): add U6 — [session.self] sets --remote-control {id} (RC parity)

Operator note: --remote-control {id} is only on [session.resume] today; a
fresh spt-hosted bringup ([session.self]) threads {id} only via
$SPT_ENDPOINT_ID. Add it to [session.self] so both bringup paths name the RC
channel {id} (spt rc <id> attach + RC-driven control identical for fresh and
resumed endpoints). Small standalone manifest change, doyle-independent; mint
REQ-DIST-RC-STARTUP.

Co-authored by: perri
EOF
echo "commit=$?"; git push origin main 2>&1 | tail -2
echo "=== commune ingested? ==="; ls .claude/perri-commune.md 2>&1 | tail -1 || echo "gone (ingested)"


commit=0
To https://github.com/SaberMage/spt-claude-code.git
   e814cad..d474e91  main -> main
=== commune ingested? ===
ls: cannot access '.claude/perri-commune.md': No such file or directory


Prompt: + add to both the actual session name set. so that's the `-n <id>` flag


Commune ingested (file gone — Psyche has it). Now your addition: `-n <id>` (session name) on **both** `[session.self]` and `[session.resume]`. Let me ground the flag against `claude --help` before updating U6.


claude --help 2>&1 | grep -iE '(-n|--name|--session|--remote-control|session id|resume)' | head -20 || echo "no match"


                                        /skill-name. Explicitly provide context
  --fork-session                        When resuming, create a new session ID
                                        with --resume or --continue)
  --from-pr [value]                     Resume a session linked to a PR by PR
  -n, --name <name>                     Set a display name for this session
                                        (shown in the prompt box, /resume
  --no-chrome                           Disable Claude in Chrome integration
  --no-session-persistence              Disable session persistence - sessions
                                        resumed (only works with --print)
  --remote-control [name]               Start an interactive session with Remote
  --remote-control-session-name-prefix <prefix>
  -r, --resume [value]                  Resume a conversation by session ID, or
  --session-id <uuid>                   Use a specific session ID for the


Both flags confirmed real: `-n, --name <name>` (session display name) and `--remote-control [name]` (RC channel). Updating U6 to set **both** on **both** paths.


Edit

### U6 — `[session.self]` sets `--remote-control {id}` (RC-channel parity) · REQ-DIST-RC-STARTUP (mint)
- Today only `[session.resume]` names the RC session via `--remote-control {id}`; `[session.self]`
  (fresh spt-hosted bringup) is bare `claude --dangerously-skip-permissions` and threads `{id}` only via
  `$SPT_ENDPOINT_ID`. Add `--remote-control {id}` to `[session.self].command` so BOTH bringup paths name
  the RC channel `{id}` — `spt rc <id>` attach + RC-driven control behave identically for fresh and
  resumed endpoints (not just resume).
- Small, standalone manifest change (like U1) — doyle-independent.
- Open: verify `claude --remote-control {id}` works on a FRESH launch (no `-r`); confirm it doesn't
  collide with the `$SPT_ENDPOINT_ID` env path (both can coexist — env for SessionStart bind, RC for the
  control channel). Add `{id}` to `[session.self].keys`.
- Gate: a fresh `spt endpoint run` endpoint is `spt rc <id>`-attachable; idle delivery + checkpoint still
  fire (re-run the on-node translate/checkpoint dogfood with the RC flag present).

### 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. Small, standalone manifest edit (like U1), doyle-independent.
- 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).


### 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. Small, standalone manifest edit (like U1), doyle-independent.
- 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).

# 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 — `[session.self]` sets `--remote-control {id}` (RC-channel parity) · REQ-DIST-RC-STARTUP (mint)
- Today only `[session.resume]` names the RC session via `--remote-control {id}`; `[session.self]`
  (fresh spt-hosted bringup) is bare `claude --dangerously-skip-permissions` and threads `{id}` only via
  `$SPT_ENDPOINT_ID`. Add `--remote-control {id}` to `[session.self].command` so BOTH bringup paths name
  the RC channel `{id}` — `spt rc <id>` attach + RC-driven control behave identically for fresh and
  resumed endpoints (not just resume).
- Small, standalone manifest change (like U1) — doyle-independent.
- Open: verify `claude --remote-control {id}` works on a FRESH launch (no `-r`); confirm it doesn't
  collide with the `$SPT_ENDPOINT_ID` env path (both can coexist — env for SessionStart bind, RC for the
  control channel). Add `{id}` to `[session.self].keys`.
- Gate: a fresh `spt endpoint run` endpoint is `spt rc <id>`-attachable; idle delivery + checkpoint still
  fire (re-run the on-node translate/checkpoint dogfood with the RC flag 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).

-### U6 — `[session.self]` sets `--remote-control {id}` (RC-channel parity) · REQ-DIST-RC-STARTUP (mint)

-- Today only `[session.resume]` names the RC session via `--remote-control {id}`; `[session.self]`

-  (fresh spt-hosted bringup) is bare `claude --dangerously-skip-permissions` and threads `{id}` only via

-  `$SPT_ENDPOINT_ID`. Add `--remote-control {id}` to `[session.self].command` so BOTH bringup paths name

-  the RC channel `{id}` — `spt rc <id>` attach + RC-driven control behave identically for fresh and

-- Small, standalone manifest change (like U1) — doyle-independent.

-- Open: verify `claude --remote-control {id}` works on a FRESH launch (no `-r`); confirm it doesn't

-  collide with the `$SPT_ENDPOINT_ID` env path (both can coexist — env for SessionStart bind, RC for the

-  control channel). Add `{id}` to `[session.self].keys`.

-- Gate: a fresh `spt endpoint run` endpoint is `spt rc <id>`-attachable; idle delivery + checkpoint still

-  fire (re-run the on-node translate/checkpoint dogfood with the RC flag present).

+### 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.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. Small, standalone manifest edit (like U1), doyle-independent.

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


Bash

cd "C:/Users/decid/Documents/projects/spt-claude-code"
git add UNIFY-CONSOLIDATE-PLAN.md
git commit -q -F - <<'EOF'
docs(plan): U6 — also set -n {id} (session display name) on both bringup paths

Operator add: thread {id} into the session display name via `-n {id}` on BOTH
[session.self] and [session.resume] (currently neither), alongside
--remote-control {id} (add to [session.self]; resume already has it). Both
flags verified against `claude --help` (-n/--name = display name;
--remote-control [name] = RC channel). Fresh and resumed endpoints then
identify + control identically.

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


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


U6 updated + pushed (`7408c61`). Final shape:

```
[session.self]    = claude -n {id} --remote-control {id} --dangerously-skip-permissions
[session.resume]  = claude -r {session_id} -n {id} --remote-control {id} --dangerously-skip-permissions
```

Both flags verified vs `claude --help`: `-n/--name` = session display name, `--remote-control [name]` = RC channel. Both now on both bringup paths → fresh + resumed endpoints identify + control identically. `{id}` already in both `keys` — no keys change. Minted as `REQ-DIST-RC-STARTUP`, U6 in the plan (quick standalone with U1).

Commune already ingested before this edit, but the **plan is authoritative** (next session reads `UNIFY-CONSOLIDATE-PLAN.md`) and it has U6 + the flag spec. Session wrapped, all pushed. 🪨


among the resolvable [session.*] keys, is the current node name available? it would be ideal if both -n and --remote-control used i.e. `{id}@{node}`, to help the user distinguish them more clearly.


Good question — needs grounding in the fillable-key vocabulary. Let me check the schema + docs for whether `{node}` (or host/machine) is a resolvable `[session.*]` key.


cd "C:/Users/decid/Documents/projects/spt-claude-code"
echo "=== schema: key/substitution/fill docs ==="
grep -niE '\{key\}|\{node\}|\{host|\{machine|substitut|placeholder|fills?|node|hostname' adapter/manifest.schema.json | head -20
echo "=== repo docs/findings: documented fillable keys ==="
grep -rniE '\{node\}|\{host|\{machine|key vocab|fillable|spt-filled|spt fills|substitution key' docs/ CONTEXT.md adapter/claude-spt.toml 2>/dev/null | head -20


=== schema: key/substitution/fill docs ===
91:      "description": "`[profiles.<name>]` — **shipped** profile overlays: sparse leaf-replace\noverlays declared by the adapter dev inside the parent manifest, updating\nas one unit with it. Stored raw ([`toml::Value`]); [`crate::profile::resolve`]\nmerges one onto the base and re-validates the complete manifest. A bare\n`adapter_name` ignores these (parent unmodified); the composite\n`<adapter>:<profile>` selects one. **Local** (node-local, user-authored)\nprofiles live beside the adapter in the registry, never here.\n(CONTEXT.md §adapter profile.)",
96:      "description": "`[strings]` — an adapter-authored KV tree of **opaque data** (spt-core\nnever executes a string; command templates live in their own sections\nbehind registration). Dot-path-readable via `spt adapter get-string`, and\nit rides the same leaf-replace profile overlay as the rest of the manifest\n(a shipped or local profile may override base strings). Node-local; no\ncross-node sync. (CONTEXT.md §adapter strings.)",
250:          "description": "`[session.notif]` — the endpoint-native notification render\n(the `notif_command` seam, REQ-NOTIF-2): an OS toast, a\nGameRobot `alert-symbol`, anything the adapter can run. Spawned\ndetached when a notif surfaces at this endpoint, combinable with the\nagent-surface delivery. Keys spt-core fills: `{notif_id}`,\n`{notif_from}`, `{notif_subnet}`, `{notif_body}`.",
267:          "description": "Opaque command line, with `{key}` substitution placeholders.",
295:          "description": "Substitution keys spt-core guarantees to fill for this role.",
314:          "description": "Value to inject (with substitution); required for `inject`.",
396:          "description": "Opaque extractor command: native harness log → the `{role,text,tool,ts}`\ncontract (one JSON record per output line). `{key}` substitution applies\n(e.g. `{session_id}`, and `{source}` for the resolved log path).",
647:          "description": "Per-spawn user approval gate (floor; a node/endpoint setting may tighten).\nAbsent ⇒ `none`. (Modeled now; conducted when shells land.)",
691:        "same-node",
739:      "description": "One entry in `[shell.capabilities]` — a command and its argument names.\n\nA capability may carry its own **act-gate** (M11, REQ-CONSENT-3): the same\n`require_approval` enum the spawn gate uses, riding the individual op so the\n*dangerous act* is gated, not just the spawn. An optional `class_key` scopes\nthe written grant finer than the op id — granted per `(owner × class × node)`\n(the usbip `attach`'s device class), so a remembered HID-class grant never\nauthorizes a storage-class act. Spawn gates govern existence; capability\ngates govern acts (CONTEXT §\"per-capability approval gates\").",
749:          "description": "Per-act approval gate (floor; a node/endpoint setting may tighten).\nAbsent ⇒ `none` (ungated).",
753:          "description": "Optional grant-qualifier class so a grant scopes finer than the op id\n(`(owner × class × node)`). Only meaningful with a gated `require_approval`.",
=== repo docs/findings: documented fillable keys ===
docs/SPT-CORE-FINDINGS.md:111:1. **Substitution-key catalog.** `{parent_pid}` and `{adapter_name}` are spt-filled SessionStart
docs/SPT-CORE-FINDINGS.md:282:→ `DIGEST_PROOF_EXTRACT_FAIL:claude-spt: digest extractor failed: no value for substitution key {session_id}`.
adapter/claude-spt.toml:104:# legacy-parity bare flow). {parent_pid} is a spt-filled key; session_id is a CC stdin field. NOT the


The `keys` field = "keys spt-core guarantees to fill" — and there's a finding about the substitution-key **catalog**. Let me read it.


cd "C:/Users/decid/Documents/projects/spt-claude-code"
sed -n '100,135p' docs/SPT-CORE-FINDINGS.md


`CONTEXT.md`/ADRs then propagates (no new design). We will report the **observed** `api poll` frame
format so the publish can confirm-match. Until published we may rely on observed behavior of the
public binary (observable behavior = public surface).

### Validation note (2026-06-15, against live v0.7.0 `spt adapter add` + the shipped mock-adapter)

Authored + registered `adapter/claude-spt.toml` against the real v0.7.0 binary
(`REQ-DIST-MANIFEST-SCHEMA` int — `ci/manifest/registration-int.sh`, 6/6 green). Confirms the
residual: the **published docs still omit** two things a manifest author needs, both learnable only
from the **shipped mock-adapter source** (which IS public surface) + live `adapter add` errors:

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
   `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
   `source = "{home}/.claude/projects"` — CC's cwd-slug subdir is not expressible as a flat
   `locate_template`, so `source` names the per-project root and the extractor finds
   `{session_id}.jsonl` within. A CC-shaped extractor is the right design vs the mock's log-less
   `[history] native`.) **Non-blocking** (resolved by reading the shipped mock + the error text);
   logged so doyle can publish the key catalog + the digest source-requirement to the docs-site.

Also observed (not a gap, a roadmap fact): `spt endpoint run` notes the **interactive picker that
emits the `<basename>-<id>` shortcut "lands in a later wave"** — so the `sptc-<id>` *emission* int
(`REQ-DIST-SHORTCUT-BASENAME`) stays held on that wave (and on authoring `[session.self]`, which
`endpoint run` spawns). `shortcut_basename = "sptc"` is confirmed to round-trip into the stored
resolved manifest, so the declaration side is proven.

---

## F-002 — `api poll` (agent path) has no inter-frame delimiter — multi-message drains unsplittable

**Reported:** 2026-06-15 to doyle. **Status:** open — **spt-core CODE gap** (not docs); doyle has
raised the framing as a contract decision to the operator.


Grounded. The honest answer: **`{node}` is not a confirmed-resolvable `[session.*]` key on the published surface.** The fillable-key *catalog* is a known unpublished gap (logged in F-001 — *"the full catalog is still code-only"*). Confirmed-fillable keys are `{id}` / `{session_id}` / `{psyche_dir}` / `{psyche_prompt}` for `[session.<role>]`; `{parent_pid}` / `{adapter_name}` for SessionStart; `{notif_*}` for notif. The node name exists in spt-core's model (endpoints list under `HFENDULEAM`), but it's not a documented `{node}` fill — so `{id}@{node}` can't be assumed today.

`{id}@{node}` is a good idea though (disambiguates a same-id endpoint across machines, in both the display name and RC channel). It's a small doyle ask. Updating U6 + the asks doc, and I'll ask doyle now (he's building).


Edit

- `{id}` is already in `[session.self].keys = ["id"]` and `[session.resume].keys = ["session_id","id"]` —
  no keys change. Small, standalone manifest edit (like U1), doyle-independent.

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


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

# 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. Small, standalone manifest edit (like U1), doyle-independent.
- 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).

-  no keys change. Small, standalone manifest edit (like U1), doyle-independent.

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


Add ask #4 to the doyle asks doc:


**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?

---

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

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


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

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

# 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?

---

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

+**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).

+- 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?


Update the dependency map, commit, push, then ask doyle:


- **Ask 3** → `translate` folds into the one `claude-spt` binary (1 artifact/triple).

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


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

# 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).
- 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** → display name + RC channel become `{id}@{node}` (cross-node distinguishable); else `{id}` only.


[Request interrupted by user]


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.


Good — catalog is published (so the F-001 "code-only" gap is resolved), and `{node}` confirmed absent. You're raising it with doyle directly, so I won't ping him. Let me grab the actual key list to record it accurately, then fix the doc framing.


WebFetch

Prompt: List every substitution key documented in the "Substitution keys" section — the {key} placeholders spt-core fills for manifest command templates. For each, give its name and what it resolves to, and note which sections/roles it applies to (e.g. [session.<role>], SessionStart hooks, [digest], [session.notif]). Quote the section verbatim if short.


# SPT Manifest Substitution Keys Reference

Here are all substitution keys documented in the "Substitution keys" section, with their definitions and applicable contexts:

| Key | Resolution | Applicable Contexts |
|-----|-----------|-------------------|
| `{id}` | "The endpoint id being hosted" | All roles, hooks, digest |
| `{adapter_name}` | "The adapter's declared `name`" | All roles, hooks |
| `{session_id}` | "The harness session id (minted at spawn; reported back via `api seed`)" | All session roles, hooks, digest |
| `{session_name}` | "The session's display name, when one is supplied" | `[session.self]` and `[session.resume]` only |
| `{parent_pid}` | "The harness parent process pid — the SessionStart `api seed` anchor" | SessionStart hook |
| `{agent_type}` | "The hosted agent type" | Session roles |
| `{agents_json}` | "The resolved agents roster for the session" | Session roles |
| `{psyche_dir}` | "A live agent's Psyche perch directory (its working dir)" | `[session.psyche_*]` roles only |
| `{psyche_prompt}` | "The composed Psyche turn prompt for this spawn" | `[session.psyche_*]` roles only |
| `{psyche_context}` | "The Psyche's carried context block" | `[session.psyche_*]` roles only |
| `{link_token}` | "A shell-link capability token" | Shell adapters |
| `{source}` | "The transcript/log path spt-core resolves for a `[digest]`/`[history]` extractor" | `[digest]` and `[history]` extractors |
| `{notif_id}` / `{notif_from}` / `{notif_subnet}` / `{notif_body}` | Notification metadata fields | `[session.notif]` role only |


Got the published catalog — `{node}` confirmed absent (and the "publish the catalog" half of my ask #4 is already done — it's live). Note: `{session_name}` exists but is circular for us (it's the *supplied* display name). Fixing the three docs to match reality, no doyle ping (you own it).


Edit

## 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?

## 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]`?