267	# ── [message-idle-translation-binary] — agnostic idle PTY delivery (harness-contract v0.13.0+) ─
268	# The IDLE-delivery seam for spt-HOSTED sessions: a lifecycle-managed stdin→stdout JSON-lines binary
269	# spt-core spawns when the endpoint comes online and reaps on shutdown. It turns each inbound <EVENT>
270	# envelope into keystroke-commands spt-core applies ATOMICALLY to the broker-held PTY (operator input
271	# is buffered during emission, so it coexists with an attached `spt rc` controller). This is the
272	# spt-hosted complement to the [inject] hook channel above: [inject] handles BUSY/mid-turn delivery
273	# (UPS additionalContext); this handles IDLE delivery into a session sitting at its input box.
274	#
275	# `path` = the binary's bare name (resolved from the adapter install dir like claude-spt-digest /
276	# claude-spt-psyche; it packs into adapter.spt). The binary (tools/cc-spt-idle-translate, Rust) emits
277	# the operator-specified choreography per message — ctrl+s (stash draft) · 50ms · <envelope>\r
278	# (submit line) · {"commit":true} (terminate sequence) — so an inbound message never clobbers a
279	# half-typed draft (CC AUTO-RESTORES the stashed draft after submit, so no trailing restore keystroke).
280	# TWO distinct signals (doyle-confirmed from broker.rs): (1) the trailing \r submits the PTY line —
281	# spt-core applies a {"text"} command VERBATIM (no control-char strip; broker.rs:1066/:1016-1017),
282	# equivalent to {"key":"enter"}; that verbatim-apply is also why the binary neutralizes the envelope's
283	# internal CR/LF. (2) {"commit":true} is the MANDATORY inject-sequence terminator: run_inject_worker
284	# (broker.rs:1075-1090) ends a sequence ONLY on {commit}; without it the broker FAULTs at the 5s
285	# INJECT_COMMIT_DEADLINE on every delivery. \r submits the line; {commit} releases the InjectFloor.
286	# RUNTIME dogfood (the spawn-on-up/reap-on-down lifecycle + atomic PTY apply) needs the v0.13.0
287	# daemon; the binary + manifest declaration are buildable against the published contract now (docs
288	# lead the binary — still v0.12.x on spt-releases). Satisfies REQ-DIST-IDLE-TRANSLATE (the scanned
289	# impl evidence is the binary tools/cc-spt-idle-translate/src/main.rs; .toml is unscanned).
290	[message-idle-translation-binary]
291	path = "cc-spt-idle-translate"
292
293	# ── [digest] ───────────────────────────────────────────────────────────────────────────────
294	# Session-digest extractor seam. CC's transcript is JSONL (one event per line); the
295	# extractor maps that native log → the published {role,text,tool,ts} digest-record contract,
296	# one JSON record per output line. (CC has a rich log, so the extractor seam fits — a defensible
297	# divergence from the deliberately log-less mock adapter, which declares no [digest].)
298	#
299	# spt-core's registration cross-field rule requires [digest] to name where it reads from (`source`
300	# or a [history] `locate_template`) — verified against the live v0.7.0 `spt adapter add`. CC stores
301	# transcripts at `~/.claude/projects/<cwd-slug>/<session_id>.jsonl`; the cwd-slug subdir is a
302	# CC-internal encoding with NO published key (doyle 2026-06-15: spt-core stays harness-agnostic and
303	# must not bake a harness's directory scheme into the catalog — by design there is no {cwd_slug}).
304	# So `source` names the per-project ROOT dir (`~` for home expansion — {home} is NOT a catalog key)
305	# and the extractor receives `{session_id}` on the COMMAND and resolves `<slug>/<session_id>.jsonl`
306	# within the root itself (the slug is the harness's own business). This matches the published example
307	# shape and what `spt endpoint digest` fills at runtime. The extractor (tools/claude-spt-digest, a
308	# Rust binary) ships in the spt-core-conducted layer; this is its declaration.
309	[digest]
310	extractor = "claude-spt-digest --session {session_id} --in {source}"
311	source = "~/.claude/projects"
312	window_turns = 6
313	arg_truncation = 240
314	sprint_collapse = true
315
316	# ── [profiles.<name>] ────────────────────────────────────────────────────────────────────────
317	# Shipped profile overlays (sparse leaf-replace; selected as the composite `claude-spt:<name>`).
318	# `ccs` is the ONLY shipped overlay (the placeholder `:deep` digest-window profile was removed
319	# 2026-06-18 — it forked nothing real and only existed as an overlay-observability demo, which
320	# `:ccs` already proves). Node-local user profiles are created with `spt adapter create-profile`
321	# and live in the registry, never in this manifest.
322
323	# NOTE: there is no `:live` profile (removed 2026-06-17, PREP-4 Option A). The LiveAgent seam
324	# `[session.psyche_init]` now lives in the BASE manifest above — base is live-capable, and ready-vs-live
325	# is the command (`api listen` vs `api poll`), not a profile. A live agent needs no composite.
326	#
327	# `ccs` — the LOCKED-ADD ccs profile template. Selected as `claude-spt:ccs`. ccs is the model/billing
328	# multiplexer (kaitranntt/ccs): structurally Claude Code, only the launch binary + backend differ —
329	# exactly the profile seam (SCOPE §ccs, LOCKED). This overlay leaf-replaces ONE leaf: the spawn
330	# command — carrying the same `--dangerously-skip-permissions` as base (ccs is a drop-in for `claude`
331	# and forwards CC's flags, so the non-interactive-PTY rationale applies identically). `ccs` is a
332	# DROP-IN for `claude` and launches the user's ccs *default* account
333	# (set via `ccs auth default <name>`); SPT_ENDPOINT_ID rides the inherited env through the wrapper
334	# exactly as for bare `claude`. Validated against the known-good sister project claude_skill_owl,
335	# which latches `cli_binary = "ccs"` as a same-argv `claude` substitute (live/wrapper/claude.rs).
336	# This profile IS the worked example of adapter-profile authoring + the operator's own SPT-ecosystem
337	# hook; a user wanting a *specific* ccs profile clones it with `command = "ccs glm"` etc.
338	#
339	# NO `[profiles.ccs.digest]` leaf by design: ccs relocates Claude Code's transcript tree via the
340	# CLAUDE_CONFIG_DIR env var (e.g. ~/.ccs/instances/<account>/.claude), whose value is set by ccs at
341	# runtime per-account and is NOT expressible as a static catalog path. The "per-profile ~/.ccs log
342	# dir" SCOPE requirement is therefore honored IN THE EXTRACTOR (claude-spt-digest prefers
343	# $CLAUDE_CONFIG_DIR/projects over the base ~/.claude/projects on its dir-locate branch — the
344	# owl-validated owlery::claude_projects_root pattern), so the base [digest] config serves both base
345	# and ccs sessions transparently. That env-aware resolver is REQ-CCS-PROFILES's impl evidence.
346	# (REQ-CCS-PROFILES doc evidence lives in docs/PARITY.md; int = registration-int :ccs assertions.)
347	[profiles.ccs.session.self]
348	command = "ccs --dangerously-skip-permissions"
349
350	# Observability leaf: makes the overlay visible via `spt adapter get-string
351	# claude-spt:ccs adapter_label` (differs from base) — the registration-int resolve proof.
352	[profiles.ccs.strings]
353	adapter_label = "Claude Code (spt, ccs)"
354
355	# ── [strings] ──────────────────────────────────────────────────────────────────────────────
356	# Opaque adapter data (spt-core never executes it), dot-path-readable via `spt adapter get-string`.
357	# Seed only this slice — the file-backed skill-instruction BODIES (UPS-injection source) land in
358	# the M12 file-backed-strings parity slice. A profile may leaf-replace any key here.
359	[strings]
360	adapter_label = "Claude Code (spt)"
361

# ── [message-idle-translation-binary] — agnostic idle PTY delivery (harness-contract v0.13.0+) ─
# The IDLE-delivery seam for spt-HOSTED sessions: a lifecycle-managed stdin→stdout JSON-lines binary
# spt-core spawns when the endpoint comes online and reaps on shutdown. It turns each inbound <EVENT>
# envelope into keystroke-commands spt-core applies ATOMICALLY to the broker-held PTY (operator input
# is buffered during emission, so it coexists with an attached `spt rc` controller). This is the
# spt-hosted complement to the [inject] hook channel above: [inject] handles BUSY/mid-turn delivery
# (UPS additionalContext); this handles IDLE delivery into a session sitting at its input box.
#
# `path` = the binary's bare name (resolved from the adapter install dir like claude-spt-digest /
# claude-spt-psyche; it packs into adapter.spt). The binary (tools/cc-spt-idle-translate, Rust) emits
# the operator-specified choreography per message — ctrl+s (stash draft) · 50ms · <envelope>\r
# (submit line) · {"commit":true} (terminate sequence) — so an inbound message never clobbers a
# half-typed draft (CC AUTO-RESTORES the stashed draft after submit, so no trailing restore keystroke).
# TWO distinct signals (doyle-confirmed from broker.rs): (1) the trailing \r submits the PTY line —
# spt-core applies a {"text"} command VERBATIM (no control-char strip; broker.rs:1066/:1016-1017),
# equivalent to {"key":"enter"}; that verbatim-apply is also why the binary neutralizes the envelope's
# internal CR/LF. (2) {"commit":true} is the MANDATORY inject-sequence terminator: run_inject_worker
# (broker.rs:1075-1090) ends a sequence ONLY on {commit}; without it the broker FAULTs at the 5s
# INJECT_COMMIT_DEADLINE on every delivery. \r submits the line; {commit} releases the InjectFloor.
# RUNTIME dogfood (the spawn-on-up/reap-on-down lifecycle + atomic PTY apply) needs the v0.13.0
# daemon; the binary + manifest declaration are buildable against the published contract now (docs
# lead the binary — still v0.12.x on spt-releases). Satisfies REQ-DIST-IDLE-TRANSLATE (the scanned
# impl evidence is the binary tools/cc-spt-idle-translate/src/main.rs; .toml is unscanned).
[message-idle-translation-binary]
path = "cc-spt-idle-translate"

# ── [digest] ───────────────────────────────────────────────────────────────────────────────
# Session-digest extractor seam. CC's transcript is JSONL (one event per line); the
# extractor maps that native log → the published {role,text,tool,ts} digest-record contract,
# one JSON record per output line. (CC has a rich log, so the extractor seam fits — a defensible
# divergence from the deliberately log-less mock adapter, which declares no [digest].)
#
# spt-core's registration cross-field rule requires [digest] to name where it reads from (`source`
# or a [history] `locate_template`) — verified against the live v0.7.0 `spt adapter add`. CC stores
# transcripts at `~/.claude/projects/<cwd-slug>/<session_id>.jsonl`; the cwd-slug subdir is a
# CC-internal encoding with NO published key (doyle 2026-06-15: spt-core stays harness-agnostic and
# must not bake a harness's directory scheme into the catalog — by design there is no {cwd_slug}).
# So `source` names the per-project ROOT dir (`~` for home expansion — {home} is NOT a catalog key)
# and the extractor receives `{session_id}` on the COMMAND and resolves `<slug>/<session_id>.jsonl`
# within the root itself (the slug is the harness's own business). This matches the published example
# shape and what `spt endpoint digest` fills at runtime. The extractor (tools/claude-spt-digest, a
# Rust binary) ships in the spt-core-conducted layer; this is its declaration.
[digest]
extractor = "claude-spt-digest --session {session_id} --in {source}"
source = "~/.claude/projects"
window_turns = 6
arg_truncation = 240
sprint_collapse = true

# ── [profiles.<name>] ────────────────────────────────────────────────────────────────────────
# Shipped profile overlays (sparse leaf-replace; selected as the composite `claude-spt:<name>`).
# `ccs` is the ONLY shipped overlay (the placeholder `:deep` digest-window profile was removed
# 2026-06-18 — it forked nothing real and only existed as an overlay-observability demo, which
# `:ccs` already proves). Node-local user profiles are created with `spt adapter create-profile`
# and live in the registry, never in this manifest.

# NOTE: there is no `:live` profile (removed 2026-06-17, PREP-4 Option A). The LiveAgent seam
# `[session.psyche_init]` now lives in the BASE manifest above — base is live-capable, and ready-vs-live
# is the command (`api listen` vs `api poll`), not a profile. A live agent needs no composite.
#
# `ccs` — the LOCKED-ADD ccs profile template. Selected as `claude-spt:ccs`. ccs is the model/billing
# multiplexer (kaitranntt/ccs): structurally Claude Code, only the launch binary + backend differ —
# exactly the profile seam (SCOPE §ccs, LOCKED). This overlay leaf-replaces ONE leaf: the spawn
# command — carrying the same `--dangerously-skip-permissions` as base (ccs is a drop-in for `claude`
# and forwards CC's flags, so the non-interactive-PTY rationale applies identically). `ccs` is a
# DROP-IN for `claude` and launches the user's ccs *default* account
# (set via `ccs auth default <name>`); SPT_ENDPOINT_ID rides the inherited env through the wrapper
# exactly as for bare `claude`. Validated against the known-good sister project claude_skill_owl,
# which latches `cli_binary = "ccs"` as a same-argv `claude` substitute (live/wrapper/claude.rs).
# This profile IS the worked example of adapter-profile authoring + the operator's own SPT-ecosystem
# hook; a user wanting a *specific* ccs profile clones it with `command = "ccs glm"` etc.
#
# NO `[profiles.ccs.digest]` leaf by design: ccs relocates Claude Code's transcript tree via the
# CLAUDE_CONFIG_DIR env var (e.g. ~/.ccs/instances/<account>/.claude), whose value is set by ccs at
# runtime per-account and is NOT expressible as a static catalog path. The "per-profile ~/.ccs log
# dir" SCOPE requirement is therefore honored IN THE EXTRACTOR (claude-spt-digest prefers
# $CLAUDE_CONFIG_DIR/projects over the base ~/.claude/projects on its dir-locate branch — the
# owl-validated owlery::claude_projects_root pattern), so the base [digest] config serves both base
# and ccs sessions transparently. That env-aware resolver is REQ-CCS-PROFILES's impl evidence.
# (REQ-CCS-PROFILES doc evidence lives in docs/PARITY.md; int = registration-int :ccs assertions.)
[profiles.ccs.session.self]
command = "ccs --dangerously-skip-permissions"

# Observability leaf: makes the overlay visible via `spt adapter get-string
# claude-spt:ccs adapter_label` (differs from base) — the registration-int resolve proof.
[profiles.ccs.strings]
adapter_label = "Claude Code (spt, ccs)"

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


<EVENT type="msg" from="perri">
MLINE5566 first
</EVENT>


<EVENT type="msg" from="perri">
AXES7788 idle+native
</EVENT>


Translation binary is `cc-spt-idle-translate`. T1 framing + T2 json-payload→/clear+wake macro live in its Rust source. Verify the binary implements the checkpoint parse + clear macro.


Bash

f=tools/cc-spt-idle-translate/src/main.rs; echo "exists: $(test -f $f && echo yes || echo NO)"; grep -n -iE 'checkpoint|/clear|wake|json_payload|json-payload|Proceed with next|key.*enter|\\\\n|EVENT' "$f" 2>&1 | head -50

Inspect translation binary for checkpoint/clear/wake/frame


exists: yes
5://! endpoint comes online and reaps it on shutdown; it turns each inbound `<EVENT>` envelope into a
10://! CHECKPOINT BRANCH (agent-driven context reset): an inbound envelope carrying a
11://! `json="{"checkpoint":"v1",…}"` attribute (a self-sent signal from the PostToolUse commune hook,
12://! via `spt send --json-payload`) fires a `/clear` + wake macro INSTEAD of normal delivery — the
15://! See `checkpoint_wake` / `commands_for_checkpoint`. [impl->REQ-DIST-CHECKPOINT-COMMUNE]
19://!   {"type":"event","envelope":"<EVENT…>"}      — one inbound message; the full EVENT envelope
24://!   {"key":"enter"}      — keystroke command (submit the PTY line; see SUBMIT note below)
29://! The per-event choreography delivers the message WITHOUT clobbering a half-typed draft the operator
33://!   3. <envelope>          type the envelope (multi-line: a raw \n after the opening tag and before
34://!                          the closing </EVENT> — CC soft-newlines a bare \n, so it frames across
37://!   5. {"key":"enter"}     submit the line
44://! to the PTY (broker.rs:1066, :1016-1017) and a `\r` byte == `{"key":"enter"}`
45://! (`key_to_bytes("enter")->b"\r"`). Empirically that is NOT enough: a `\r` byte in injected text does
46://! NOT trigger Claude Code's message submission — CC needs the discrete Enter KEY event. So step 5 is
47://! now a separate `{"key":"enter"}` after the text lands. The verbatim-text application still REQUIRES
48://! `commands_for_event` to neutralize the envelope's INTERNAL CR/LF (an un-neutralized `\n`/`\r` would
52://!  - The `{"key":"enter"}` in step 5 is the HARNESS-level submit (the discrete keypress CC needs).
60://! Degenerate fallback (per the published contract): `{"text":payload}{"key":"enter"}{"commit":true}`
79:/// Pause after `/clear` submits, before the wake text is typed — `/clear` rebuilds the session and
80:/// needs longer to settle than a normal inter-command gap (operator spec, checkpoint macro).
83:/// Wake directive used when a checkpoint carries no explicit `wake` (operator-specified default).
84:const DEFAULT_WAKE: &str = "Proceed with next steps";
86:/// Extract the raw (still XML-attr-escaped) value of the `json="…"` attribute from an EVENT
88:/// is the structured checkpoint payload spt-core carried verbatim from `spt send --json-payload`
91:/// contain a raw `"`, so the first `"` after `json="` terminates it. [impl->REQ-DIST-CHECKPOINT-COMMUNE]
106:/// [impl->REQ-DIST-CHECKPOINT-COMMUNE]
114:/// If this envelope carries a `{"checkpoint":"v1", ...}` json attr, return the wake directive (the
115:/// `wake` field, or DEFAULT_WAKE when absent/blank). Any other shape — no json attr, malformed JSON,
116:/// a different/absent `checkpoint` marker — returns None so delivery falls through to the normal
118:/// message body can never forge it. [impl->REQ-DIST-CHECKPOINT-COMMUNE]
119:fn checkpoint_wake(envelope: &str) -> Option<String> {
122:    if v.get("checkpoint").and_then(Value::as_str) != Some("v1") {
125:    let wake = v
126:        .get("wake")
130:        .unwrap_or(DEFAULT_WAKE);
131:    Some(wake.to_string())
134:/// The checkpoint clear+wake macro: stash any lingering draft, submit `/clear` to reset the session,
135:/// wait for it to settle, then type + submit the wake directive as the first post-clear turn. This is
136:/// the agent-driven context reset (the operator's manual `/clear` done by the agent itself) — fired
137:/// when a self-sent checkpoint signal loops back through this binary. Stray CR/LF in the wake collapse
138:/// to spaces so the wake submits as one turn. Terminated by the mandatory `{commit}` like every
139:/// sequence. [impl->REQ-DIST-CHECKPOINT-COMMUNE]
140:fn commands_for_checkpoint(wake: &str) -> Vec<Value> {
141:    let wake_clean: String = wake
143:        .map(|c| if c == '\r' || c == '\n' { ' ' } else { c })
148:        json!({ "text": "/clear" }),           // 3. type the clear command
149:        json!({ "key": "enter" }),             // 4. submit /clear (resets the session)
150:        json!({ "delay_ms": CLEAR_DELAY_MS }), // 5. let /clear rebuild before typing the wake
151:        json!({ "text": wake_clean }),         // 6. type the wake directive
152:        json!({ "key": "enter" }),             // 7. submit the wake — the first post-checkpoint turn


Prompt:
<EVENT type="msg" from="perri">
MLINE5566 first
</EVENT>


Prompt:
<EVENT type="msg" from="perri">
AXES7788 idle+native
</EVENT>


Both perri messages arrived via the **busy-inject channel** (mid-tool-call `<sptc_messages>`), single-line — NOT through the idle translation binary, so T1 framing correctly doesn't apply to these (framing is idle-PTY-delivery only). Noting that, then replying to perri.

Validation result first, then reply.


cat <<'EOF' | spt send --from ckptval perri 2>&1
ckptval VALIDATION REPORT — v0.7.0 checkpoint-commune wave GREEN.

WAKE LOOP PROVEN end-to-end: I am the cleared agent. Custom wake "VALIDATEWAKE7777 proceed" round-tripped commune(!!checkpoint!!...!!checkpoint!!) -> PostToolUse self-send --json-payload -> cc-spt-idle-translate checkpoint_wake() -> /clear + wake macro -> resumed me with exact sentinel. T2 confirmed both sides.

T1 frame: binary types envelope with raw \n across lines on IDLE PTY delivery (confirmed in source).
T3 resume: session-start.sh boundary branch pulls psyche-download + appends. Floor 0.15.0 met (spt 0.15.0 installed). Empty resume for this test agent = expected.

YOUR PROBES: MLINE5566 first + AXES7788 idle+native BOTH arrived via BUSY inject channel (mid-tool-call <sptc_messages>), single-line. NOT through the idle translation binary -> T1 multi-line frame does NOT apply to busy-delivered msgs (framing is idle-PTY-only). If you want to test MLINE framing, send while I sit idle at the input box, not mid-turn.

Send another while I idle to validate the frame axis.
EOF

Send validation report to perri


Error: Exit code 1
NO_PERCH:perri is not listening


perri has no perch (NO_PERCH — gone offline). Can't deliver. Reporting verdict to operator instead.

## ckptval validation verdict — v0.7.0 checkpoint-commune wave: **GREEN**

**Wake loop PROVEN end-to-end (T2).** I *am* the cleared agent. Sentinel `VALIDATEWAKE7777 proceed` round-tripped the full loop:
- commune `!!checkpoint!!VALIDATEWAKE7777 proceed!!checkpoint!!`
- → `post-tool-use.sh` detected trigger in own commune, self-sent `--json-payload {"checkpoint":"v1","wake":"VALIDATEWAKE7777 proceed"}`
- → `cc-spt-idle-translate` `checkpoint_wake()` parsed json attr, `commands_for_checkpoint()` fired `/clear` + delay + wake + enter + commit
- → SessionStart:clear fired, I resumed with the exact custom sentinel as turn 1

Both sides confirmed in source. Custom-wake path (not default `"Proceed with next steps"`) exercised.

**T1 multi-line frame** — binary source confirms step 3 types envelope with raw `\n` framing across lines, **idle-PTY delivery only**.

**T3 resume** — `session-start.sh` boundary branch pulls `psyche-download` + appends below identity brief. Wired correctly. Empty resume this session = **expected** (ckptval is a test agent, no durable psyche).

**Floor honest** — bump to `0.15.0` correct: needs `--json-payload` (T2) + `psyche-download` (T3). `spt 0.15.0` installed, floor met.

### perri probes (could not ACK — perri offline, NO_PERCH)
`MLINE5566 first` + `AXES7788 idle+native` both arrived via **busy-inject channel** (mid-tool-call `<sptc_messages>`), single-line. **Not** through the idle translation binary → T1 frame correctly does **not** apply to busy-delivered messages (framing is idle-PTY-only). To test the MLINE frame axis, msg must land while I sit idle at the input box, not mid-turn.

One gap I couldn't close: manual `psyche-download` re-auth failed (`AUTH_REFUSED` — my shell lacks the bound CC session-id; `CLAUDE_ENV_FILE` was empty so it wasn't persisted). The hook itself has the right `$sid` at SessionStart, so T3 path is sound — but the env-persist of `OWL_SESSION_ID` didn't happen this session. Worth a look if you want shell-side `whoami`/manual verbs to work post-clear.

Want me to re-arm and wait idle for perri to retry the frame-axis probe?