106	#[cfg(not(test))]
107	fn run() -> ExitCode {
108	    let args = match Args::parse(std::env::args().skip(1)) {
109	        Ok(a) => a,
110	        Err(e) => {
111	            eprintln!("claude-spt-psyche: {e}");
112	            return ExitCode::from(2);
113	        }
114	    };
115
116	    // Seed the resident Psyche claude session (best-effort: if claude is absent the daemon's perch
117	    // poll still defines our lifetime; we don't abort the runner over one failed turn).
118	    let _ = Command::new("claude")
119	        .args(seed_cmd(&args.prompt))
120	        .stdin(Stdio::null())
121	        .status();
122
123	    // Resident pulse loop. The perch IS the lifecycle: when the daemon tears the LiveAgent down at

#[cfg(not(test))]
fn run() -> ExitCode {
    let args = match Args::parse(std::env::args().skip(1)) {
        Ok(a) => a,
        Err(e) => {
            eprintln!("claude-spt-psyche: {e}");
            return ExitCode::from(2);
        }
    };

    // Seed the resident Psyche claude session (best-effort: if claude is absent the daemon's perch
    // poll still defines our lifetime; we don't abort the runner over one failed turn).
    let _ = Command::new("claude")
        .args(seed_cmd(&args.prompt))
        .stdin(Stdio::null())
        .status();

    // Resident pulse loop. The perch IS the lifecycle: when the daemon tears the LiveAgent down at


Update spt 0.8.1→0.8.2 + restart daemon

spt update fetch && apply; restart daemon for fresh code+env.

F-009 re-validate: strict parser must HOST on 0.8.2

Temp-revert --prompt to strict single-value, build+deploy, run live-relay-int psyche leg → expect resident host (proves tokenize-then-fill delivers prompt as ONE arg). Working-tree only, not committed to shipped product.

F-010 re-validate: fast-exit must STAMP psyche_host_error

Deploy a runner that exits immediately, bring up live → expect psyche_host_error stamped on parent perch + nested perch de-onlined (surfaced by endpoint list/whoami).

Restore shipped greedy binary + report to doyle

Restore install-dir to the v0.2.0 greedy binary (re-acquire or rebuild). Report pass/gap to doyle.


I'll build isolated test binaries (no install-dir contention with perri-psyche). Add argv-capture to the strict working tree for the F-009 binary:


Edit

#[cfg(not(test))]
fn run() -> ExitCode {
    let args = match Args::parse(std::env::args().skip(1)) {
        Ok(a) => a,
        Err(e) => {
            eprintln!("claude-spt-psyche: {e}");
            return ExitCode::from(2);
        }
    };

#[cfg(not(test))]
fn run() -> ExitCode {
    // RE-VALIDATION (TEMP, reverted before commit): dump raw argv (\x1f-separated) so we can confirm
    // spt-core 0.8.2 fills {psyche_prompt} as ONE argv element (F-009). + optional fast-exit (F-010).
    let _ = std::fs::write(
        std::env::temp_dir().join("psyche-reval-argv.log"),
        std::env::args().collect::<Vec<_>>().join("\u{1f}"),
    );
    if std::env::var("SPTC_TEST_FASTEXIT").is_ok() {
        return ExitCode::from(2); // F-010: spawn-then-exit-immediately
    }
    let args = match Args::parse(std::env::args().skip(1)) {
        Ok(a) => a,
        Err(e) => {
            eprintln!("claude-spt-psyche: {e}");
            return ExitCode::from(2);
        }
    };


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

#[cfg(not(test))]
fn run() -> ExitCode {
    let args = match Args::parse(std::env::args().skip(1)) {
        Ok(a) => a,
        Err(e) => {
            eprintln!("claude-spt-psyche: {e}");
            return ExitCode::from(2);
        }
    };

#[cfg(not(test))]
fn run() -> ExitCode {
    // RE-VALIDATION (TEMP, reverted before commit): dump raw argv (\x1f-separated) so we can confirm
    // spt-core 0.8.2 fills {psyche_prompt} as ONE argv element (F-009). + optional fast-exit (F-010).
    let _ = std::fs::write(
        std::env::temp_dir().join("psyche-reval-argv.log"),
        std::env::args().collect::<Vec<_>>().join("\u{1f}"),
    );
    if std::env::var("SPTC_TEST_FASTEXIT").is_ok() {
        return ExitCode::from(2); // F-010: spawn-then-exit-immediately
    }
    let args = match Args::parse(std::env::args().skip(1)) {
        Ok(a) => a,
        Err(e) => {
            eprintln!("claude-spt-psyche: {e}");
            return ExitCode::from(2);
        }
    };

+    // RE-VALIDATION (TEMP, reverted before commit): dump raw argv (\x1f-separated) so we can confirm

+    // spt-core 0.8.2 fills {psyche_prompt} as ONE argv element (F-009). + optional fast-exit (F-010).

+        std::env::temp_dir().join("psyche-reval-argv.log"),

+    if std::env::var("SPTC_TEST_FASTEXIT").is_ok() {

+        return ExitCode::from(2); // F-010: spawn-then-exit-immediately


Build the F-009 test binary (isolated path) + craft a temp manifest pointing at it:


Bash

cd "C:\Users\decid\Documents\projects\spt-claude-code"
mkdir -p ~/sptc-test 2>/dev/null
echo "=== build f009 binary (strict + argv-dump) ==="
cargo build --release --quiet --manifest-path tools/claude-spt-psyche/Cargo.toml 2>&1 | tail -3
cp tools/claude-spt-psyche/target/release/claude-spt-psyche.exe ~/sptc-test/psyche-f009.exe && echo "built ~/sptc-test/psyche-f009.exe"
echo "=== temp manifest pointing psyche_init at the abs-path test binary ==="
TM=~/sptc-test/manifest-f009.toml
# copy the adapter manifest, swap the psyche command to a TOML literal (single-quote) abs path
sed "s|^command = \"claude-spt-psyche |command = 'C:\\\\Users\\\\decid\\\\sptc-test\\\\psyche-f009.exe |; s|--prompt {psyche_prompt}\"|--prompt {psyche_prompt}'|" adapter/claude-spt.toml > "$TM"
echo "--- psyche_init command line in temp manifest ---"
grep -n "psyche-f009\|psyche_init\|^command" "$TM" | head

Build f009 binary + temp manifest


=== build f009 binary (strict + argv-dump) ===
built ~/sptc-test/psyche-f009.exe
=== temp manifest pointing psyche_init at the abs-path test binary ===
--- psyche_init command line in temp manifest ---
15:#   * [session.<role>] for psyche_init/psyche_resume/echo_commune/signoff/notif — the live-agent /
126:command = "claude"
181:# the daemon resolves the MERGED manifest, sees [session.psyche_init], and spawns the endpoint's
182:# Psyche companion (base claude-spt declares no psyche_init = ReadyAgent — doyle 2026-06-15, traced:
184:# BrainLifecycle checks psyche_init on the merged view). Psyche keys the daemon fills are EXACTLY
193:# the daemon resolves to see psyche_init) + runs the delivery relay via Monitor (skills/live.md).
195:[profiles.live.session.psyche_init]
196:command = 'C:SERSDECIDSPTC-TESTPSYCHE-F009.EXE --id {id} --session-id {session_id} --prompt {psyche_prompt}'
220:command = "ccs"
256:# [session.psyche_init] -> daemon spawns the Psyche) + a resident Monitor relay as the delivery pipe.


sed mangled the backslashes. Redo the temp manifest's command line with forward slashes (CreateProcess accepts them):


cd "C:\Users\decid\Documents\projects\spt-claude-code"
TM=~/sptc-test/manifest-f009.toml
# regenerate clean: replace the whole psyche_init command line with a forward-slash abs path
awk '
/^\[profiles\.live\.session\.psyche_init\]/ {inpsy=1; print; next}
inpsy && /^command = / {print "command = \"C:/Users/decid/sptc-test/psyche-f009.exe --id {id} --session-id {session_id} --prompt {psyche_prompt}\""; inpsy=0; next}
{print}
' adapter/claude-spt.toml > "$TM"
echo "--- psyche_init block in temp manifest ---"
grep -A4 "profiles.live.session.psyche_init" "$TM" | head -5
echo "=== validate temp manifest ==="
spt adapter validate "$TM" 2>&1 | tail -3 || echo "(no validate subcommand; will rely on bringup)"

Rewrite temp manifest command cleanly + validate


--- psyche_init block in temp manifest ---
[profiles.live.session.psyche_init]
command = "C:/Users/decid/sptc-test/psyche-f009.exe --id {id} --session-id {session_id} --prompt {psyche_prompt}"
cwd = "{psyche_dir}"
detach = true
keys = ["id", "session_id", "psyche_dir", "psyche_prompt"]
=== validate temp manifest ===
Usage: spt.exe adapter <COMMAND>

For more information, try '--help'.


Temp manifest clean (points at `psyche-f009.exe`). Bring up via it — if the daemon honors the manifest's command, my binary runs and dumps argv. Read the dump to see the prompt's token count.


cd "C:\Users\decid\Documents\projects\spt-claude-code"
rm -f ~/AppData/Local/Temp/psyche-reval-argv.log /tmp/psyche-reval-argv.log 2>/dev/null
A=claude-spt:live; MAN=~/sptc-test/manifest-f009.toml
ID=sptc-reval-f009-$$; SID=$ID-sess
export SPTC_CI_ID="$ID" SPT_AGENT_ID="$ID" OWL_SESSION_ID="$ID"
ANCHOR=$(ps -p $$ 2>/dev/null | awk 'NR==2{print $4}'); case "$ANCHOR" in ''|*[!0-9]*) ANCHOR=$$;; esac
LF=$(mktemp)
spt api --adapter "$A" --manifest "$MAN" seed --pid "$ANCHOR" --session-id "$SID" >/dev/null 2>&1
timeout 40 spt api --adapter "$A" --manifest "$MAN" listen --parent-pid "$ANCHOR" "$ID" > "$LF" 2>&1 &
i=0; while [ "$i" -lt 25 ]; do grep -q "READY:$ID" "$LF" 2>/dev/null && break; sleep 1; i=$((i+1)); done
echo "READY. waiting 10s for daemon to spawn the test psyche..."; sleep 10
echo "=== argv dump (did MY f009 binary run?) ==="
for d in "$HOME/AppData/Local/Temp" "/tmp"; do f="$d/psyche-reval-argv.log"; [ -f "$f" ] && { echo "FOUND $f:"; cat "$f" | tr '\037' '\n' | nl; break; }; done
[ -f "$HOME/AppData/Local/Temp/psyche-reval-argv.log" ] || echo "(no dump — daemon may have used the install-dir binary, not the manifest abs-path)"
echo "ID=$ID" > /tmp/reval.env

F-009 bringup via temp manifest, read argv dump


READY. waiting 10s for daemon to spawn the test psyche...
=== argv dump (did MY f009 binary run?) ===
(no dump — daemon may have used the install-dir binary, not the manifest abs-path)


No dump — the daemon resolves the psyche binary from the **registered adapter's install dir**, ignoring the `--manifest` abs-path. So the install-dir binary ran. That's the earlier-deployed **strict** binary (no argv-dump). Check: did strict HOST the test psyche on 0.8.2? (strict hosting = tokenize-then-fill delivered one arg = F-009 proven). Verify install-dir binary identity + the test psyche's residency.


cd "C:\Users\decid\Documents\projects\spt-claude-code"
ID=$(grep -o 'sptc-reval-f009-[0-9]*' /tmp/reval.env | head -1)
echo "test id: $ID"
echo "=== install-dir binary: strict or greedy? (test multi-token reject) ==="
~/AppData/Local/spt-core/adapters/_github/SaberMage-spt-claude-code/claude-spt-psyche.exe --id X --session-id Y --prompt a b c >/dev/null 2>&1; echo "  multi-token exit code: $? (2=STRICT rejected; 0-ish/hang=greedy)"
echo "=== test id nested psyche info.json ==="
cat ~/AppData/Local/spt-core/owlery/$ID/nested/$ID-psyche/info.json 2>&1; echo ""
echo "=== that psyche pid alive? ==="
PID=$(grep -o '"pid":[0-9]*' ~/AppData/Local/spt-core/owlery/$ID/nested/$ID-psyche/info.json 2>/dev/null | grep -o '[0-9]*')
echo "recorded pid=$PID"; [ -n "$PID" ] && tasklist //FI "PID eq $PID" 2>&1 | grep -iE "$PID|no tasks"
echo "=== parent perch psyche_host_error? ==="
grep -o "psyche_host_error[^}]*}" ~/AppData/Local/spt-core/owlery/$ID/info.json 2>&1 || echo "  (none — clean, consistent with successful host)"

Check install-dir binary + test psyche residency


test id: sptc-reval-f009-1643
=== install-dir binary: strict or greedy? (test multi-token reject) ===
  multi-token exit code: 2 (2=STRICT rejected; 0-ish/hang=greedy)
=== test id nested psyche info.json ===
{"id":"sptc-reval-f009-1643-psyche","started":"1781660813","pid":232948,"session_id":"sess-232948-1781660813835273900","state":"ready_agent","home_subnet":"SPT_DEV"}
=== that psyche pid alive? ===
recorded pid=232948
INFO: No tasks are running which match the specified criteria.
=== parent perch psyche_host_error? ===
  (none — clean, consistent with successful host)


Update spt 0.8.1→0.8.2 + restart daemon


Logical F-009 proof already present: the install-dir binary IS strict (multi-token→exit 2), yet the test psyche **registered as a ready_agent** (reached `spt ready`) — it could only get there by passing strict arg-parse, i.e. the prompt arrived as ONE arg. (pid not alive now = normal teardown after the 40s listen ended.) But let me get the **direct argv dump** for airtight evidence. Clean cycle: stop daemon, kill psyches, delete stale perch dirs (kill the storm), deploy the dump-binary to install-dir.


cd "C:\Users\decid\Documents\projects\spt-claude-code"
spt daemon stop 2>&1 | tail -1; sleep 2
for n in 1 2 3; do taskkill //IM claude-spt-psyche.exe //T //F >/dev/null 2>&1; c=$(tasklist 2>/dev/null|grep -ci claude-spt-psyche); echo "kill pass $n: $c"; [ "$c" = 0 ] && break; sleep 2; done
echo "=== delete stale sptc-* owlery dirs (keep perri) ==="
cd ~/AppData/Local/spt-core/owlery 2>/dev/null && for d in sptc-*; do [ -d "$d" ] && rm -rf "$d" 2>/dev/null; done; echo "remaining owlery dirs: $(ls -d */ 2>/dev/null | tr '\n' ' ')"
cd "C:\Users\decid\Documents\projects\spt-claude-code"
cp ~/sptc-test/psyche-f009.exe ~/AppData/Local/spt-core/adapters/_github/SaberMage-spt-claude-code/claude-spt-psyche.exe 2>&1 && echo "deployed DUMP binary to install-dir" || echo "BUSY"
echo "=== start daemon ==="; spt daemon start 2>&1 | tail -1; sleep 4
echo "psyche procs after start (storm gone?): $(tasklist 2>/dev/null|grep -ci claude-spt-psyche)"

Clean cycle: kill storm, delete stale dirs, deploy dump binary


DAEMON_STOPPED
kill pass 1: 0
=== delete stale sptc-* owlery dirs (keep perri) ===
remaining owlery dirs: X/ doyle/ help/
deployed DUMP binary to install-dir
=== start daemon ===
DAEMON_STARTED
psyche procs after start (storm gone?): 0


Storm eliminated (0 psyches, stale dirs deleted), dump binary deployed. Now the clean F-009 test — bring up, read argv dump + sample the psyche's residency.


cd "C:\Users\decid\Documents\projects\spt-claude-code"
rm -f ~/AppData/Local/Temp/psyche-reval-argv.log 2>/dev/null
A=claude-spt:live; MAN="$PWD/adapter/claude-spt.toml"   # local manifest (bare name); daemon resolves install-dir dump binary
ID=sptc-reval2-$$; SID=$ID-sess
export SPTC_CI_ID="$ID" SPT_AGENT_ID="$ID" OWL_SESSION_ID="$ID"
ANCHOR=$(ps -p $$ 2>/dev/null | awk 'NR==2{print $4}'); case "$ANCHOR" in ''|*[!0-9]*) ANCHOR=$$;; esac
LF=$(mktemp)
spt api --adapter "$A" --manifest "$MAN" seed --pid "$ANCHOR" --session-id "$SID" >/dev/null 2>&1
timeout 70 spt api --adapter "$A" --manifest "$MAN" listen --parent-pid "$ANCHOR" "$ID" > "$LF" 2>&1 &
i=0; while [ "$i" -lt 25 ]; do grep -q "READY:$ID" "$LF" 2>/dev/null && break; sleep 1; i=$((i+1)); done
echo "READY. sampling test psyche residency:"
for t in 4 8 12 16; do echo "t=${t}s procs=$(tasklist 2>/dev/null|grep -ci claude-spt-psyche)"; sleep 4; done
echo "=== F-009 ARGV DUMP (prompt = how many elements?) ==="
cat ~/AppData/Local/Temp/psyche-reval-argv.log 2>/dev/null | tr '\037' '\n' | nl || echo "(no dump)"

F-009 clean test: argv dump + residency


Command running in background with ID: bp4hvez4g. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-claude-code\4b08b6c6-5f2a-42f7-a56d-cf3d9a866de7\tasks\bp4hvez4g.output. You will be notified when it completes. To check interim output, use Read on that file path.


F="C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-claude-code\4b08b6c6-5f2a-42f7-a56d-cf3d9a866de7\tasks\bp4hvez4g.output"
i=0; while [ $i -lt 20 ]; do grep -q "ARGV DUMP" "$F" 2>/dev/null && sleep 2 && break; sleep 3; i=$((i+1)); done
cat "$F"

Read F-009 test result


<task-notification>
<task-id>b0yhgee6u</task-id>
<tool-use-id>toolu_01BjX1sQEUtbJcj4YCzANjoT</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-claude-code\4b08b6c6-5f2a-42f7-a56d-cf3d9a866de7\tasks\b0yhgee6u.output</output-file>
<status>failed</status>
<summary>Monitor "« spt event »" script failed (exit 1)</summary>
</task-notification>


<task-notification>
<task-id>bp4hvez4g</task-id>
<tool-use-id>toolu_01GBwLA96YjjprYRPBJL64uE</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-claude-code\4b08b6c6-5f2a-42f7-a56d-cf3d9a866de7\tasks\bp4hvez4g.output</output-file>
<status>completed</status>
<summary>Background command "F-009 clean test: argv dump + residency" completed (exit code 0)</summary>
</task-notification>


READY. sampling test psyche residency:
t=4s procs=0
t=8s procs=1
t=12s procs=1
t=16s procs=1
=== F-009 ARGV DUMP (prompt = how many elements?) ===
     1	C:\Users\decid\AppData\Local\spt-core\adapters\_github\SaberMage-spt-claude-code\claude-spt-psyche.exe
     2	--id
     3	sptc-reval2-2634-psyche
     4	--session-id
     5	sptc-reval2-2634-sess
     6	--prompt
     7	PSYCHE REVIVAL
     8	time: epoch-ms:1781660882884
     9	incoming event: (none)


Prompt:
<task-notification>
<task-id>b0yhgee6u</task-id>
<tool-use-id>toolu_01BjX1sQEUtbJcj4YCzANjoT</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-claude-code\4b08b6c6-5f2a-42f7-a56d-cf3d9a866de7\tasks\b0yhgee6u.output</output-file>
<status>failed</status>
<summary>Monitor "« spt event »" script failed (exit 1)</summary>
</task-notification>


Prompt:
<task-notification>
<task-id>bp4hvez4g</task-id>
<tool-use-id>toolu_01GBwLA96YjjprYRPBJL64uE</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-claude-code\4b08b6c6-5f2a-42f7-a56d-cf3d9a866de7\tasks\bp4hvez4g.output</output-file>
<status>completed</status>
<summary>Background command "F-009 clean test: argv dump + residency" completed (exit code 0)</summary>
</task-notification>


Residency holds (procs=1 from t=8s) and the dump appeared — but I need to disambiguate: are lines 7-9 three separate argv elements, or ONE element containing newlines (which my `tr '\x1f'→newline` display would render across 3 lines)? If strict hosted, it must be one element. Count the actual `\x1f` delimiters precisely.


F=~/AppData/Local/Temp/psyche-reval-argv.log
echo "=== argv element count (delimiter \\x1f count + 1) ==="
n=$(tr -cd '\037' < "$F" | wc -c); echo "delimiters=$n → argv elements=$((n+1))"
echo "=== elements with explicit [N]= markers (sep shown as ⟦SEP⟧) ==="
sed 's/\x1f/\n⟦SEP⟧\n/g' "$F"
echo ""
echo "=== so the --prompt VALUE is the single trailing element (may contain newlines) ==="
# everything after the 6th delimiter = the prompt element
awk -v RS='\037' 'NR==7{print "PROMPT ELEMENT (NR=7):"; print $0; print "[end]"}' "$F"

Count argv elements precisely


=== argv element count (delimiter \x1f count + 1) ===
delimiters=6 → argv elements=7
=== elements with explicit [N]= markers (sep shown as ⟦SEP⟧) ===
C:\Users\decid\AppData\Local\spt-core\adapters\_github\SaberMage-spt-claude-code\claude-spt-psyche.exe
⟦SEP⟧
--id
⟦SEP⟧
sptc-reval2-2634-psyche
⟦SEP⟧
--session-id
⟦SEP⟧
sptc-reval2-2634-sess
⟦SEP⟧
--prompt
⟦SEP⟧
PSYCHE REVIVAL
time: epoch-ms:1781660882884
incoming event: (none)

=== so the --prompt VALUE is the single trailing element (may contain newlines) ===
PROMPT ELEMENT (NR=7):
PSYCHE REVIVAL
time: epoch-ms:1781660882884
incoming event: (none)

[end]


**F-009 PROVEN.** argv = exactly **7 elements**; the `--prompt` value is the *entire* multi-line prompt as ONE element ("PSYCHE REVIVAL\ntime:...\nincoming event: (none)") — no split, no injection. (0.8.1 made it 7 single-word tokens.) And the strict parser **hosted** (procs=1) — confirming tokenize-then-fill delivers one arg. **Task 7 done.**

Now F-010: restart daemon with `SPTC_TEST_FASTEXIT=1` so the hosted psyche spawns-then-exits → must stamp `psyche_host_error`.


Updated task #7 status


Updated task #8 status


cd "C:\Users\decid\Documents\projects\spt-claude-code"
spt daemon stop 2>&1 | tail -1; sleep 2
for n in 1 2 3; do taskkill //IM claude-spt-psyche.exe //T //F >/dev/null 2>&1; c=$(tasklist 2>/dev/null|grep -ci claude-spt-psyche); [ "$c" = 0 ] && break; sleep 2; done
echo "=== start daemon WITH SPTC_TEST_FASTEXIT=1 (forces hosted psyche to spawn-then-exit) ==="
export SPTC_TEST_FASTEXIT=1
spt daemon start 2>&1 | tail -1; sleep 4
A=claude-spt:live; MAN="$PWD/adapter/claude-spt.toml"
ID=sptc-f010-$$; SID=$ID-sess
export SPTC_CI_ID="$ID" SPT_AGENT_ID="$ID" OWL_SESSION_ID="$ID"
ANCHOR=$(ps -p $$ 2>/dev/null | awk 'NR==2{print $4}'); case "$ANCHOR" in ''|*[!0-9]*) ANCHOR=$$;; esac
LF=$(mktemp)
spt api --adapter "$A" --manifest "$MAN" seed --pid "$ANCHOR" --session-id "$SID" >/dev/null 2>&1
timeout 50 spt api --adapter "$A" --manifest "$MAN" listen --parent-pid "$ANCHOR" "$ID" > "$LF" 2>&1 &
i=0; while [ "$i" -lt 25 ]; do grep -q "READY:$ID" "$LF" 2>/dev/null && break; sleep 1; i=$((i+1)); done
echo "READY. waiting 20s for daemon to spawn fast-exit psyche + run residency check..."; sleep 20
echo "ID=$ID" > /tmp/f010.env
echo "=== parent perch psyche_host_error STAMPED? ==="
cat ~/AppData/Local/spt-core/owlery/$ID/info.json 2>&1; echo ""
echo "=== nested psyche perch (de-onlined?) ==="
cat ~/AppData/Local/spt-core/owlery/$ID/nested/$ID-psyche/info.json 2>&1 || echo "  (no nested info.json)"

F-010: force fast-exit psyche, check psyche_host_error stamped


Command running in background with ID: bpsi0riix. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-claude-code\4b08b6c6-5f2a-42f7-a56d-cf3d9a866de7\tasks\bpsi0riix.output. You will be notified when it completes. To check interim output, use Read on that file path.


F="C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-claude-code\4b08b6c6-5f2a-42f7-a56d-cf3d9a866de7\tasks\bpsi0riix.output"
i=0; while [ $i -lt 25 ]; do grep -q "nested psyche perch" "$F" 2>/dev/null && break; sleep 3; i=$((i+1)); done
cat "$F"
echo "=== also check endpoint list / whoami rendering for $ID ==="
ID=$(grep -o 'sptc-f010-[0-9]*' /tmp/f010.env | head -1)
OWL_SESSION_ID="$ID" SPT_AGENT_ID="$ID" spt endpoint list 2>&1 | grep -iE "$ID|psyche-host|FAILED" | head

Read F-010 result + endpoint rendering


<task-notification>
<task-id>bpsi0riix</task-id>
<tool-use-id>toolu_01QhPQ2Fs6yaMpRpBfUM6zQc</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-claude-code\4b08b6c6-5f2a-42f7-a56d-cf3d9a866de7\tasks\bpsi0riix.output</output-file>
<status>completed</status>
<summary>Background command "F-010: force fast-exit psyche, check psyche_host_error stamped" completed (exit code 0)</summary>
</task-notification>


DAEMON_STOPPED
=== start daemon WITH SPTC_TEST_FASTEXIT=1 (forces hosted psyche to spawn-then-exit) ===
DAEMON_ALREADY_RUNNING
READY. waiting 20s for daemon to spawn fast-exit psyche + run residency check...
=== parent perch psyche_host_error STAMPED? ===
{"id":"sptc-f010-2967","started":"1781660977","pid":176824,"session_id":"sptc-f010-2967-sess","state":"live_agent","parent_pid":178764,"status":"online","last_active_ms":1781660996708,"home_subnet":"SPT_DEV","adapter":"claude-spt:live"}
=== nested psyche perch (de-onlined?) ===
{"id":"sptc-f010-2967-psyche","started":"1781660997","pid":213704,"session_id":"sess-213704-1781660997501915900","state":"ready_agent","home_subnet":"SPT_DEV"}=== also check endpoint list / whoami rendering for  ===
SELF: sptc-f010-2967  live_agent  ready=true alive=true


Prompt:
<task-notification>
<task-id>bpsi0riix</task-id>
<tool-use-id>toolu_01QhPQ2Fs6yaMpRpBfUM6zQc</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-claude-code\4b08b6c6-5f2a-42f7-a56d-cf3d9a866de7\tasks\bpsi0riix.output</output-file>
<status>completed</status>
<summary>Background command "F-010: force fast-exit psyche, check psyche_host_error stamped" completed (exit code 0)</summary>
</task-notification>


F-010 test invalid — `DAEMON_ALREADY_RUNNING` means my env-carrying restart didn't take (daemon auto-restarted without `SPTC_TEST_FASTEXIT`), so the psyche hosted normally (registered as ready_agent, no fast-exit). Switch to an **unconditional** fast-exit binary (no daemon-env dependency). First diagnose why the daemon won't stay stopped.


cd "C:\Users\decid\Documents\projects\spt-claude-code"
echo "=== daemon pid now ==="; spt daemon status 2>&1 | grep -i "daemon:"
echo "=== stop + immediately recheck (does it auto-restart?) ==="
spt daemon stop 2>&1 | tail -1; sleep 1
spt daemon status 2>&1 | grep -i "daemon:" || echo "  (status errored = down)"
sleep 4
echo "after 4s:"; spt daemon status 2>&1 | grep -i "daemon:" || echo "  (still down)"
echo "=== is the perri Monitor relay restarting it? check live tasks ==="
tasklist 2>/dev/null | grep -iE "spt.exe" | head -5

Diagnose daemon auto-restart


=== daemon pid now ===
daemon: running (pid 6240)
=== stop + immediately recheck (does it auto-restart?) ===
DAEMON_STOPPED
daemon: not running (`spt daemon start`, or any spt command auto-starts it)
after 4s:
daemon: not running (`spt daemon start`, or any spt command auto-starts it)
=== is the perri Monitor relay restarting it? check live tasks ===
spt.exe                      10980 Console                    1     22,008 K
spt.exe                      99156 Console                    1     22,020 K


Key mechanic: **any spt command auto-starts the daemon**, so my env-carrying `start` got pre-empted. Daemon is DOWN now — switch to an unconditional fast-exit binary (no env needed) and deploy while the exe is free.


Edit

    if std::env::var("SPTC_TEST_FASTEXIT").is_ok() {
        return ExitCode::from(2); // F-010: spawn-then-exit-immediately
    }

return ExitCode::from(2); // F-010 (TEMP): unconditional spawn-then-exit-immediately


    if std::env::var("SPTC_TEST_FASTEXIT").is_ok() {
        return ExitCode::from(2); // F-010: spawn-then-exit-immediately
    }

-    if std::env::var("SPTC_TEST_FASTEXIT").is_ok() {

-        return ExitCode::from(2); // F-010: spawn-then-exit-immediately

+    return ExitCode::from(2); // F-010 (TEMP): unconditional spawn-then-exit-immediately


Bash

cd "C:\Users\decid\Documents\projects\spt-claude-code"
echo "=== build unconditional fast-exit binary ==="
cargo build --release --quiet --manifest-path tools/claude-spt-psyche/Cargo.toml 2>&1 | tail -3
echo "=== ensure daemon down + no psyches, then deploy ==="
spt daemon status 2>&1 | grep -i daemon:
for n in 1 2 3; do taskkill //IM claude-spt-psyche.exe //T //F >/dev/null 2>&1; c=$(tasklist 2>/dev/null|grep -ci claude-spt-psyche); [ "$c" = 0 ] && break; sleep 2; done
echo "psyches: $(tasklist 2>/dev/null|grep -ci claude-spt-psyche)"
cp tools/claude-spt-psyche/target/release/claude-spt-psyche.exe ~/AppData/Local/spt-core/adapters/_github/SaberMage-spt-claude-code/claude-spt-psyche.exe 2>&1 && echo "deployed FAST-EXIT binary" || echo "BUSY"
echo "=== verify it exits 2 immediately ==="
~/AppData/Local/spt-core/adapters/_github/SaberMage-spt-claude-code/claude-spt-psyche.exe --id X --session-id Y --prompt "one arg ok" >/dev/null 2>&1; echo "  exit=$? (expect 2)"

Build + deploy unconditional fast-exit binary


=== build unconditional fast-exit binary ===
    |
    = note: `#[warn(unreachable_code)]` (part of `#[warn(unused)]`) on by default

=== ensure daemon down + no psyches, then deploy ===
daemon: not running (`spt daemon start`, or any spt command auto-starts it)
psyches: 0
deployed FAST-EXIT binary
=== verify it exits 2 immediately ===
  exit=2 (expect 2)


Fast-exit binary deployed + verified (exit 2). Start daemon, bring up → psyche must fast-exit → daemon stamps `psyche_host_error`.


cd "C:\Users\decid\Documents\projects\spt-claude-code"
spt daemon start 2>&1 | tail -1; sleep 4
A=claude-spt:live; MAN="$PWD/adapter/claude-spt.toml"
ID=sptc-f010b-$$; SID=$ID-sess
export SPTC_CI_ID="$ID" SPT_AGENT_ID="$ID" OWL_SESSION_ID="$ID"
ANCHOR=$(ps -p $$ 2>/dev/null | awk 'NR==2{print $4}'); case "$ANCHOR" in ''|*[!0-9]*) ANCHOR=$$;; esac
LF=$(mktemp)
spt api --adapter "$A" --manifest "$MAN" seed --pid "$ANCHOR" --session-id "$SID" >/dev/null 2>&1
timeout 60 spt api --adapter "$A" --manifest "$MAN" listen --parent-pid "$ANCHOR" "$ID" > "$LF" 2>&1 &
i=0; while [ "$i" -lt 25 ]; do grep -q "READY:$ID" "$LF" 2>/dev/null && break; sleep 1; i=$((i+1)); done
echo "READY. waiting 25s for daemon host-attempt + residency-check to stamp..."; sleep 25
echo "ID=$ID" > /tmp/f010b.env
echo "=== PARENT perch info.json — psyche_host_error STAMPED? ==="
cat ~/AppData/Local/spt-core/owlery/$ID/info.json 2>&1 | python -c "import sys,json; d=json.load(sys.stdin); print(json.dumps(d,indent=1))" 2>/dev/null || cat ~/AppData/Local/spt-core/owlery/$ID/info.json 2>&1
echo ""
echo "=== endpoint list / whoami — psyche-host: FAILED line? ==="
OWL_SESSION_ID="$ID" SPT_AGENT_ID="$ID" spt endpoint list 2>&1 | grep -iE "$ID|psyche-host|FAILED" | head
OWL_SESSION_ID="$ID" SPT_AGENT_ID="$ID" spt whoami 2>&1 | grep -iE "psyche-host|FAILED|$ID" | head

F-010 test: fast-exit psyche, check psyche_host_error stamped


Command running in background with ID: bqz5v2hue. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-claude-code\4b08b6c6-5f2a-42f7-a56d-cf3d9a866de7\tasks\bqz5v2hue.output. You will be notified when it completes. To check interim output, use Read on that file path.


F="C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-claude-code\4b08b6c6-5f2a-42f7-a56d-cf3d9a866de7\tasks\bqz5v2hue.output"
i=0; while [ $i -lt 25 ]; do grep -q "endpoint list / whoami" "$F" 2>/dev/null && sleep 2 && break; sleep 3; i=$((i+1)); done
cat "$F"

Read F-010 result


<task-notification>
<task-id>bqz5v2hue</task-id>
<tool-use-id>toolu_012Swmb5m4yzjfGrPjWdFc2h</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-claude-code\4b08b6c6-5f2a-42f7-a56d-cf3d9a866de7\tasks\bqz5v2hue.output</output-file>
<status>completed</status>
<summary>Background command "F-010 test: fast-exit psyche, check psyche_host_error stamped" completed (exit code 0)</summary>
</task-notification>


DAEMON_STARTED
READY. waiting 25s for daemon host-attempt + residency-check to stamp...
=== PARENT perch info.json — psyche_host_error STAMPED? ===
{
 "id": "sptc-f010b-3403",
 "started": "1781661083",
 "pid": 57452,
 "session_id": "sptc-f010b-3403-sess",
 "state": "live_agent",
 "parent_pid": 34316,
 "status": "online",
 "last_active_ms": 1781661104524,
 "home_subnet": "SPT_DEV",
 "adapter": "claude-spt:live",
 "psyche_host_error": {
  "reason": "host not resident within 5s (psyche perch missing/dead pid)",
  "ts": "2026-06-17T01:51:39Z",
  "attempts": 2
 }
}

=== endpoint list / whoami — psyche-host: FAILED line? ===
SELF: sptc-f010b-3403  live_agent  ready=true alive=true
  psyche-host: FAILED (host not resident within 5s (psyche perch missing/dead pid); 2 attempts; 2026-06-17T01:51:39Z)
SELF: sptc-f010b-3403  live_agent  ready=true alive=true
  psyche-host: FAILED (host not resident within 5s (psyche perch missing/dead pid); 2 attempts; 2026-06-17T01:51:39Z)


Prompt:
<task-notification>
<task-id>bqz5v2hue</task-id>
<tool-use-id>toolu_012Swmb5m4yzjfGrPjWdFc2h</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-claude-code\4b08b6c6-5f2a-42f7-a56d-cf3d9a866de7\tasks\bqz5v2hue.output</output-file>
<status>completed</status>
<summary>Background command "F-010 test: fast-exit psyche, check psyche_host_error stamped" completed (exit code 0)</summary>
</task-notification>


Update spt 0.8.1→0.8.2 + restart daemon


**F-010 PROVEN.** The fast-exit psyche triggered the exact new behavior:
```
"psyche_host_error": { "reason": "host not resident within 5s (psyche perch missing/dead pid)", "attempts": 2 }
```
And both `endpoint list` + `whoami` render: **`psyche-host: FAILED (host not resident within 5s …; 2 attempts; …)`**. No more silent phantom. **Task 8 done.**

Both findings re-validated. Now restore the shipped greedy binary + clean up.


Updated task #9 status


cd "C:\Users\decid\Documents\projects\spt-claude-code"
echo "=== restore greedy source (discard temp instrumentation) ==="
git checkout tools/claude-spt-psyche/src/main.rs 2>&1
git status -s 2>&1 | head
echo "=== stop daemon + kill psyches, then re-acquire v0.2.0 (canonical published greedy binary) ==="
spt daemon stop 2>&1 | tail -1; sleep 2
for n in 1 2 3; do taskkill //IM claude-spt-psyche.exe //T //F >/dev/null 2>&1; c=$(tasklist 2>/dev/null|grep -ci claude-spt-psyche); [ "$c" = 0 ] && break; sleep 2; done
echo "psyches: $(tasklist 2>/dev/null|grep -ci claude-spt-psyche)"
spt adapter add --release SaberMage/spt-claude-code --tag v0.2.0 --asset adapter-windows-x86_64.spt 2>&1 | tail -3
echo "=== verify install-dir binary is GREEDY (multi-token slurps, exit!=2 / hosts) ==="
timeout 3 ~/AppData/Local/spt-core/adapters/_github/SaberMage-spt-claude-code/claude-spt-psyche.exe --id X --session-id Y --prompt a b c >/dev/null 2>&1; echo "  multi-token exit=$? (124=resident/greedy-ok; 2=strict-leftover)"

Restore greedy source + re-acquire published binary


=== restore greedy source (discard temp instrumentation) ===
Updated 1 path from the index
=== stop daemon + kill psyches, then re-acquire v0.2.0 (canonical published greedy binary) ===
DAEMON_STOPPED
psyches: 0
Fetching adapter release asset adapter-windows-x86_64.spt from https://github.com/SaberMage/spt-claude-code/releases/download/v0.2.0/adapter-windows-x86_64.spt…
ADAPTER_ADD:claude-spt:Harness:Pointer (registered)
ADAPTER_INSTALL_DEFERRED: GhReleaseManaged (rides the update engine)
=== verify install-dir binary is GREEDY (multi-token slurps, exit!=2 / hosts) ===
  multi-token exit=124 (124=resident/greedy-ok; 2=strict-leftover)


1	//! claude-spt-psyche — the `[session.psyche_init]` Psyche runner for the claude-spt LiveAgent.
2	//!
3	//! WHAT A PSYCHE IS (doyle 2026-06-15, traced spt-core): a LiveAgent's detached companion. The
4	//! spt-core daemon's BrainLifecycle spawns it ONCE per live session — only when the resolved
5	//! manifest declares `[session.psyche_init]` (the `claude-spt:live` overlay; base claude-spt has
6	//! none = ReadyAgent). The Psyche owns its OWN perch (`<parent>-psyche`), receives daemon PULSES
7	//! on that perch, and on each pulse authors a COMMUNE delta (a context brief for the parent's
8	//! resume across `/clear`/compact) — it NEVER replies/notifies (the echo-commune is a distinct,
9	//! cheaper actor). It exits at session end. The Psyche is daemon-MANAGED by contract (decoupled
10	//! from message delivery); this runner does NOT orchestrate its own lifecycle — it is the dumb
11	//! resident wrapper the daemon launches and the daemon tears down (perch close = our exit signal).
12	//!
13	//! WHY A RESIDENT WRAPPER (not bare `claude -p`): `claude -p` is ONE-SHOT (one turn, then exits;
14	//! Stop hooks cannot re-loop it), and CC is migrating off `-p` billing-wise. So the Psyche-as-an-
15	//! LLM is a headless `claude` session in `{psyche_dir}` that we KEEP ALIVE: seed it once with the
16	//! daemon-supplied `{psyche_prompt}` (which tells that claude its Psyche role + the commune-on-pulse
17	//! contract), then drive one `claude --continue` turn per pulse. The COMMUNE authoring lives in
18	//! that claude (driven by the prompt) — this runner stays pure orchestration glue.
19	//!
20	//! Invoked by the daemon (detached, cwd=`{psyche_dir}`, stdio null):
21	//!   claude-spt-psyche --id <parent>-psyche --session-id <session_id> --prompt <psyche_prompt…>
22	//! ({id} is the daemon-OVERRIDDEN `<parent>-psyche`, NOT the parent endpoint id — see spawn_psyche.)
23	//! NOTE: spt-core substitutes `{psyche_prompt}` into the command STRING then whitespace-splits, so the
24	//! multi-word prompt reaches us as many trailing argv tokens — `--prompt` is parsed greedily (slurps
25	//! the rest). Keep `--prompt` LAST in the `[session.psyche_init]` command template. [impl->REQ-SKILL-LIVE]
26	//!
27	//! Loop:  seed `claude -p <prompt>`  ->  forever { `spt ready <id> --once` (blocks for one pulse;
28	//! its stdout is the pulse body) ; perch-closed => exit ; empty drain => next ; else feed the pulse
29	//! to `claude --continue -p <pulse>` }. The perch register + backlog drain are the `spt ready`
30	//! command's own job (ready.md), so no separate establish step.
31	//! [impl->REQ-SKILL-LIVE]
32
33	#[cfg(not(test))]
34	use std::process::{Command, ExitCode, Stdio};
35
36	/// Parsed launch args. Only the three daemon-filled `psyche_init.keys` we template
37	/// (`{id, session_id, psyche_prompt}`; `{psyche_dir}` is consumed as cwd by the daemon, not argv).
38	#[derive(Debug, PartialEq)]
39	struct Args {
40	    id: String,
41	    session_id: String,
42	    prompt: String,
43	}
44
45	impl Args {
46	    /// Parse `--id V --session-id V --prompt <rest…>`. `--id`/`--session-id` are single-value and
47	    /// order-independent; `--prompt` is TERMINAL and GREEDY — it slurps every remaining token as the
48	    /// prompt (rejoined with single spaces). This is load-bearing: spt-core substitutes the
49	    /// `{psyche_prompt}` key into the `[session.psyche_init]` command STRING and then whitespace-splits
50	    /// the result, so a multi-word prompt (it always is — "PSYCHE REVIVAL time: … incoming event: …")
51	    /// arrives as many argv tokens, NOT one. A non-greedy `--prompt` would read only the first word and
52	    /// reject the second as "unknown arg" → instant exit 2 → the daemon records a phantom hosted Psyche
53	    /// with no live process (diagnosed v0.8.1, 2026-06-16). Our manifest command places `--prompt` last,
54	    /// so greedy-slurp reconstructs the prompt. Returns the offending flag on a missing value or an
55	    /// unknown arg seen BEFORE `--prompt`, so a real misfire is still loud rather than a silent default.
56	    fn parse<I: IntoIterator<Item = String>>(argv: I) -> Result<Args, String> {
57	        let (mut id, mut session_id, mut prompt) = (None, None, None);
58	        let mut it = argv.into_iter();
59	        while let Some(flag) = it.next() {
60	            let want = |it: &mut dyn Iterator<Item = String>| {
61	                it.next().ok_or_else(|| format!("{flag} expects a value"))
62	            };
63	            match flag.as_str() {
64	                "--id" => id = Some(want(&mut it)?),
65	                "--session-id" => session_id = Some(want(&mut it)?),
66	                // Terminal + greedy: consume ALL remaining tokens (spt-core whitespace-split the prompt).
67	                "--prompt" => {
68	                    prompt = Some(it.by_ref().collect::<Vec<_>>().join(" "));
69	                }
70	                other => return Err(format!("unknown arg: {other}")),
71	            }
72	        }
73	        Ok(Args {
74	            id: id.ok_or("missing --id")?,
75	            session_id: session_id.ok_or("missing --session-id")?,
76	            prompt: prompt.ok_or("missing --prompt")?,
77	        })
78	    }
79	}
80
81	/// argv for the one-shot SEED turn: establishes the Psyche's headless `claude` session in the cwd
82	/// (`{psyche_dir}`) from the daemon-supplied psyche prompt. `-p` is the headless/print mode.
83	fn seed_cmd(prompt: &str) -> Vec<String> {
84	    vec!["-p".into(), prompt.into()]
85	}
86
87	/// argv for a per-PULSE turn: resume the seeded session (`--continue` picks the most-recent session
88	/// in the cwd) and feed the pulse body as the turn's prompt. The seeded role-prompt makes that
89	/// claude author a commune delta for this pulse.
90	fn pulse_cmd(pulse: &str) -> Vec<String> {
91	    vec!["--continue".into(), "-p".into(), pulse.into()]
92	}
93
94	/// argv for one perch poll: `spt ready <id> --once` registers the perch (first call), drains the
95	/// spooled backlog, blocks for exactly one delivery, then exits — re-run to stay reachable
96	/// (ready.md). Non-zero exit = perch gone (session end) = our cue to stop.
97	fn poll_cmd(id: &str) -> Vec<String> {
98	    vec!["ready".into(), id.into(), "--once".into()]
99	}
100
101	/// A pulse body is actionable only if non-blank: `spt ready` may print just its `READY:<id>` online
102	/// signal (on stderr) or an empty drain, which must NOT trigger a (billed) claude turn.
103	fn is_actionable(pulse: &str) -> bool {
104	    !pulse.trim().is_empty()
105	}
106
107	#[cfg(not(test))]
108	fn run() -> ExitCode {
109	    let args = match Args::parse(std::env::args().skip(1)) {
110	        Ok(a) => a,
111	        Err(e) => {
112	            eprintln!("claude-spt-psyche: {e}");
113	            return ExitCode::from(2);
114	        }
115	    };
116
117	    // Seed the resident Psyche claude session (best-effort: if claude is absent the daemon's perch
118	    // poll still defines our lifetime; we don't abort the runner over one failed turn).
119	    let _ = Command::new("claude")
120	        .args(seed_cmd(&args.prompt))
121	        .stdin(Stdio::null())
122	        .status();
123
124	    // Resident pulse loop. The perch IS the lifecycle: when the daemon tears the LiveAgent down at
125	    // session end the perch closes, `spt ready --once` exits non-zero, and we return.
126	    loop {
127	        let out = match Command::new("spt").args(poll_cmd(&args.id)).output() {
128	            Ok(o) => o,
129	            Err(_) => return ExitCode::SUCCESS, // spt vanished — nothing left to serve
130	        };
131	        if !out.status.success() {
132	            return ExitCode::SUCCESS; // perch gone => session ended
133	        }
134	        let pulse = String::from_utf8_lossy(&out.stdout);
135	        if !is_actionable(&pulse) {
136	            continue; // online signal / empty drain — no turn
137	        }
138	        let _ = Command::new("claude")
139	            .args(pulse_cmd(&pulse))
140	            .stdin(Stdio::null())
141	            .status();
142	    }
143	}
144
145	#[cfg(not(test))]
146	fn main() -> ExitCode {
147	    run()
148	}
149
150	#[cfg(test)]

... [96 lines truncated] ...


Source restored to greedy; install-dir re-acquired the published v0.2.0 greedy binary (multi-token exit=124 = hosts).

The F-009 result clarifies the shipped parser's status: on 0.8.2 the prompt arrives as ONE element (with newlines); greedy's `join(" ")` on a single-element vec passes it through unchanged. So greedy works on **both** 0.8.1 (old split) and 0.8.2 (fixed) — keep it as defensive, but the doc comments now overstate "spt-core always splits." Update them for accuracy.


Edit

//! NOTE: spt-core substitutes `{psyche_prompt}` into the command STRING then whitespace-splits, so the
//! multi-word prompt reaches us as many trailing argv tokens — `--prompt` is parsed greedily (slurps
//! the rest). Keep `--prompt` LAST in the `[session.psyche_init]` command template. [impl->REQ-SKILL-LIVE]

//! NOTE on `--prompt` parsing: spt-core **<0.8.2** substituted `{psyche_prompt}` into the command
//! STRING then whitespace-split it, so the multi-word prompt reached us as many trailing argv tokens.
//! spt-core **0.8.2+** fills each `{key}` as exactly ONE argv element (tokenize-then-fill — re-validated
//! 2026-06-17: the prompt arrives as a single element, newlines intact). We parse `--prompt` GREEDILY
//! (slurp the rest) anyway — defensive: it reconstructs a split prompt on <0.8.2 AND passes the single
//! 0.8.2 element through unchanged. Keep `--prompt` LAST in the command template. [impl->REQ-SKILL-LIVE]


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

//! NOTE: spt-core substitutes `{psyche_prompt}` into the command STRING then whitespace-splits, so the
//! multi-word prompt reaches us as many trailing argv tokens — `--prompt` is parsed greedily (slurps
//! the rest). Keep `--prompt` LAST in the `[session.psyche_init]` command template. [impl->REQ-SKILL-LIVE]

//! NOTE on `--prompt` parsing: spt-core **<0.8.2** substituted `{psyche_prompt}` into the command
//! STRING then whitespace-split it, so the multi-word prompt reached us as many trailing argv tokens.
//! spt-core **0.8.2+** fills each `{key}` as exactly ONE argv element (tokenize-then-fill — re-validated
//! 2026-06-17: the prompt arrives as a single element, newlines intact). We parse `--prompt` GREEDILY
//! (slurp the rest) anyway — defensive: it reconstructs a split prompt on <0.8.2 AND passes the single
//! 0.8.2 element through unchanged. Keep `--prompt` LAST in the command template. [impl->REQ-SKILL-LIVE]

//! Invoked by the daemon (detached, cwd=`{psyche_dir}`, stdio null):

//!   claude-spt-psyche --id <parent>-psyche --session-id <session_id> --prompt <psyche_prompt…>

//! ({id} is the daemon-OVERRIDDEN `<parent>-psyche`, NOT the parent endpoint id — see spawn_psyche.)

-//! NOTE: spt-core substitutes `{psyche_prompt}` into the command STRING then whitespace-splits, so the

-//! multi-word prompt reaches us as many trailing argv tokens — `--prompt` is parsed greedily (slurps

-//! the rest). Keep `--prompt` LAST in the `[session.psyche_init]` command template. [impl->REQ-SKILL-LIVE]

+//! NOTE on `--prompt` parsing: spt-core **<0.8.2** substituted `{psyche_prompt}` into the command

+//! STRING then whitespace-split it, so the multi-word prompt reached us as many trailing argv tokens.

+//! spt-core **0.8.2+** fills each `{key}` as exactly ONE argv element (tokenize-then-fill — re-validated

+//! 2026-06-17: the prompt arrives as a single element, newlines intact). We parse `--prompt` GREEDILY

+//! (slurp the rest) anyway — defensive: it reconstructs a split prompt on <0.8.2 AND passes the single

+//! 0.8.2 element through unchanged. Keep `--prompt` LAST in the command template. [impl->REQ-SKILL-LIVE]

//! Loop:  seed `claude -p <prompt>`  ->  forever { `spt ready <id> --once` (blocks for one pulse;

//! its stdout is the pulse body) ; perch-closed => exit ; empty drain => next ; else feed the pulse


    /// Parse `--id V --session-id V --prompt <rest…>`. `--id`/`--session-id` are single-value and
    /// order-independent; `--prompt` is TERMINAL and GREEDY — it slurps every remaining token as the
    /// prompt (rejoined with single spaces). This is load-bearing: spt-core substitutes the
    /// `{psyche_prompt}` key into the `[session.psyche_init]` command STRING and then whitespace-splits
    /// the result, so a multi-word prompt (it always is — "PSYCHE REVIVAL time: … incoming event: …")
    /// arrives as many argv tokens, NOT one. A non-greedy `--prompt` would read only the first word and
    /// reject the second as "unknown arg" → instant exit 2 → the daemon records a phantom hosted Psyche
    /// with no live process (diagnosed v0.8.1, 2026-06-16). Our manifest command places `--prompt` last,
    /// so greedy-slurp reconstructs the prompt. Returns the offending flag on a missing value or an
    /// unknown arg seen BEFORE `--prompt`, so a real misfire is still loud rather than a silent default.

    /// Parse `--id V --session-id V --prompt <rest…>`. `--id`/`--session-id` are single-value and
    /// order-independent; `--prompt` is TERMINAL and GREEDY — it slurps every remaining token as the
    /// prompt (rejoined with single spaces). History: spt-core **<0.8.2** substituted `{psyche_prompt}`
    /// into the `[session.psyche_init]` command STRING then whitespace-split it, so a multi-word prompt
    /// (it always is — "PSYCHE REVIVAL time: … incoming event: …") arrived as many tokens, NOT one; a
    /// non-greedy `--prompt` read only the first word and rejected the second as "unknown arg" → instant
    /// exit 2 → the daemon recorded a phantom hosted Psyche (diagnosed v0.8.1, 2026-06-16). spt-core
    /// **0.8.2** fixed it (fills each `{key}` as ONE argv element; re-validated 2026-06-17). Greedy is
    /// KEPT as defensive: with one element, `collect().join(" ")` returns it unchanged; with the old
    /// split, it reconstructs. Our manifest places `--prompt` last. Returns the offending flag on a
    /// missing value or an unknown arg seen BEFORE `--prompt`, so a real misfire is still loud.


    /// Parse `--id V --session-id V --prompt <rest…>`. `--id`/`--session-id` are single-value and
    /// order-independent; `--prompt` is TERMINAL and GREEDY — it slurps every remaining token as the
    /// prompt (rejoined with single spaces). This is load-bearing: spt-core substitutes the
    /// `{psyche_prompt}` key into the `[session.psyche_init]` command STRING and then whitespace-splits
    /// the result, so a multi-word prompt (it always is — "PSYCHE REVIVAL time: … incoming event: …")
    /// arrives as many argv tokens, NOT one. A non-greedy `--prompt` would read only the first word and
    /// reject the second as "unknown arg" → instant exit 2 → the daemon records a phantom hosted Psyche
    /// with no live process (diagnosed v0.8.1, 2026-06-16). Our manifest command places `--prompt` last,
    /// so greedy-slurp reconstructs the prompt. Returns the offending flag on a missing value or an
    /// unknown arg seen BEFORE `--prompt`, so a real misfire is still loud rather than a silent default.

    /// Parse `--id V --session-id V --prompt <rest…>`. `--id`/`--session-id` are single-value and
    /// order-independent; `--prompt` is TERMINAL and GREEDY — it slurps every remaining token as the
    /// prompt (rejoined with single spaces). History: spt-core **<0.8.2** substituted `{psyche_prompt}`
    /// into the `[session.psyche_init]` command STRING then whitespace-split it, so a multi-word prompt
    /// (it always is — "PSYCHE REVIVAL time: … incoming event: …") arrived as many tokens, NOT one; a
    /// non-greedy `--prompt` read only the first word and rejected the second as "unknown arg" → instant
    /// exit 2 → the daemon recorded a phantom hosted Psyche (diagnosed v0.8.1, 2026-06-16). spt-core
    /// **0.8.2** fixed it (fills each `{key}` as ONE argv element; re-validated 2026-06-17). Greedy is
    /// KEPT as defensive: with one element, `collect().join(" ")` returns it unchanged; with the old
    /// split, it reconstructs. Our manifest places `--prompt` last. Returns the offending flag on a
    /// missing value or an unknown arg seen BEFORE `--prompt`, so a real misfire is still loud.

-    /// prompt (rejoined with single spaces). This is load-bearing: spt-core substitutes the

-    /// `{psyche_prompt}` key into the `[session.psyche_init]` command STRING and then whitespace-splits

-    /// the result, so a multi-word prompt (it always is — "PSYCHE REVIVAL time: … incoming event: …")

-    /// arrives as many argv tokens, NOT one. A non-greedy `--prompt` would read only the first word and

-    /// reject the second as "unknown arg" → instant exit 2 → the daemon records a phantom hosted Psyche

-    /// with no live process (diagnosed v0.8.1, 2026-06-16). Our manifest command places `--prompt` last,

-    /// so greedy-slurp reconstructs the prompt. Returns the offending flag on a missing value or an

-    /// unknown arg seen BEFORE `--prompt`, so a real misfire is still loud rather than a silent default.

+    /// prompt (rejoined with single spaces). History: spt-core **<0.8.2** substituted `{psyche_prompt}`

+    /// into the `[session.psyche_init]` command STRING then whitespace-split it, so a multi-word prompt

+    /// (it always is — "PSYCHE REVIVAL time: … incoming event: …") arrived as many tokens, NOT one; a

+    /// non-greedy `--prompt` read only the first word and rejected the second as "unknown arg" → instant

+    /// exit 2 → the daemon recorded a phantom hosted Psyche (diagnosed v0.8.1, 2026-06-16). spt-core

+    /// **0.8.2** fixed it (fills each `{key}` as ONE argv element; re-validated 2026-06-17). Greedy is

+    /// KEPT as defensive: with one element, `collect().join(" ")` returns it unchanged; with the old

+    /// split, it reconstructs. Our manifest places `--prompt` last. Returns the offending flag on a

+    /// missing value or an unknown arg seen BEFORE `--prompt`, so a real misfire is still loud.

fn parse<I: IntoIterator<Item = String>>(argv: I) -> Result<Args, String> {

let (mut id, mut session_id, mut prompt) = (None, None, None);


Comment-only accuracy updates. Verify build/tests still green, then commit.


Bash

cd "C:\Users\decid\Documents\projects\spt-claude-code"
sh ci/psyche/build.sh 2>&1 | tail -3
echo "=== traceable-reqs ==="; traceable-reqs check >/dev/null 2>&1 && echo OK || echo FAIL
git add tools/claude-spt-psyche/src/main.rs
git commit -q -F - <<'EOF'
docs(psyche): note spt-core 0.8.2 fixed the {key} split; greedy --prompt now defensive

Re-validated on spt v0.8.2 (counter 18): spt-core's command templating now fills
each {key} as exactly ONE argv element (tokenize-then-fill) — the multi-line
{psyche_prompt} arrives as a single element, newlines intact (argv-capture: 7
elements total, prompt = one). The greedy --prompt parser is RETAINED as
defensive: a single element passes through join(" ") unchanged, and a pre-0.8.2
whitespace-split prompt is still reconstructed. Comment-only; no behavior change.

Also re-validated F-010: a daemon-hosted psyche that spawns-then-exits now stamps
psyche_host_error{reason:"host not resident within 5s ...", attempts:2} on the
parent perch, surfaced by `endpoint list`/`whoami` as `psyche-host: FAILED (...)`.

Co-authored by: perri
EOF
echo "committed: $(git log --oneline -1)"
git push origin main 2>&1 | tail -2

Build/test + commit doc accuracy + push


test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

ok  claude-spt-psyche: cargo test + release build
=== traceable-reqs ===
OK
committed: b6d599e docs(psyche): note spt-core 0.8.2 fixed the {key} split; greedy --prompt now defensive
To https://github.com/SaberMage/spt-claude-code.git
   0ec2610..b6d599e  main -> main


Committed + pushed (b6d599e). Report the re-validation results to doyle.


cd "C:\Users\decid\Documents\projects\spt-claude-code"
$OWL send doyle <<'EOF'
v0.8.2 RE-VALIDATION: BOTH PASS. Clean.

F-009 (templating) — PROVEN via argv-capture on the daemon-spawned psyche. With a STRICT single-value --prompt parser deployed (greedy workaround removed so your fix is what's under test), the psyche HOSTED + stayed resident. Raw argv dump = exactly 7 elements; the --prompt value is the ENTIRE multi-line prompt as ONE element, newlines intact:
  [6]=--prompt  [7]="PSYCHE REVIVAL\ntime: epoch-ms:...\nincoming event: (none)"
(vs 0.8.1 which split the same prompt into 7 single-word tokens.) tokenize-then-fill confirmed: each {key} = one argv element, no split, no injection.

F-010 (residency) — PROVEN. Deployed a psyche that spawns-then-exits(2) immediately. The daemon stamped the parent perch:
  psyche_host_error { reason: "host not resident within 5s (psyche perch missing/dead pid)", ts: ..., attempts: 2 }
and BOTH `spt endpoint list` + `spt whoami` render it inline:
  psyche-host: FAILED (host not resident within 5s (psyche perch missing/dead pid); 2 attempts; <ts>)
status stays live_agent/online (liveness authoritative), the error is additive + harness-reachable. No more silent phantom. The 5s residency window + retry(2)-then-stamp is exactly right.

No new gaps. My shipped adapter keeps the greedy --prompt as defensive (one 0.8.2 element passes through unchanged; a pre-0.8.2 split is still reconstructed) so it works across 0.8.1/0.8.2. Restored the published v0.2.0 greedy binary. Committed b6d599e. Thanks for the fast turnaround — clean fixes. -- perri
EOF
echo "rc=$?"

Report re-validation PASS to doyle


⚠ Could not determine your perch. Pass your ID explicitly: owl send <target> <your-id>
rc=1


Stale test env confused auto-detect. Send with explicit id:


cd "C:\Users\decid\Documents\projects\spt-claude-code"
unset SPT_AGENT_ID OWL_SESSION_ID SPTC_CI_ID
$OWL send doyle perri <<'EOF'
v0.8.2 RE-VALIDATION: BOTH PASS. Clean.

F-009 (templating) — PROVEN via argv-capture on the daemon-spawned psyche. STRICT single-value --prompt parser deployed (greedy workaround removed so your fix is what's under test) → psyche HOSTED + stayed resident. Raw argv = exactly 7 elements; --prompt value = the ENTIRE multi-line prompt as ONE element, newlines intact: [7]="PSYCHE REVIVAL\ntime: epoch-ms:...\nincoming event: (none)". (0.8.1 split the same into 7 single-word tokens.) tokenize-then-fill confirmed: each {key}=one argv element, no split, no injection.

F-010 (residency) — PROVEN. Psyche that spawns-then-exits(2) immediately → daemon stamped parent perch: psyche_host_error{reason:"host not resident within 5s (psyche perch missing/dead pid)", attempts:2}. Both `endpoint list` + `whoami` render: psyche-host: FAILED (host not resident within 5s ...; 2 attempts; <ts>). status stays live_agent/online (liveness authoritative); error additive + harness-reachable. No more silent phantom. 5s window + retry(2)-then-stamp is exactly right.

No new gaps. Shipped adapter keeps greedy --prompt as defensive (0.8.2 single element passes join() unchanged; pre-0.8.2 split still reconstructed) → works across 0.8.1/0.8.2. Restored published v0.2.0 greedy binary. Commit b6d599e. Clean fixes, fast turnaround. -- perri
EOF
echo "rc=$?"

Send re-validation report to doyle with explicit id


➜ SENT:doyle
rc=0


Report sent. Finish task 9: update the findings doc to RESOLVED-VALIDATED, clean up test artifacts, re-revive perri.


Edit

| F-009 | 2026-06-16 | **OPEN — PENDING DELIVERY to doyle (he's offline; report in commit ddf1965 + below)**. Adapter worked around it (greedy `--prompt`); recommend spt-core fix so it doesn't bite every adapter | `[session.psyche_init]`/extractor command templating substitutes a `{key}` into the command STRING then WHITESPACE-SPLITS → ANY multi-word fill (e.g. `{psyche_prompt}`) explodes into stray argv tokens. Survived only by single-token fills |
| F-010 | 2026-06-16 | **OPEN — PENDING DELIVERY to doyle**. Hardening request on top of the v0.8.1 `psyche_host_error` surface | Silent-exit still maskable: `psyche_host_error` stays clear when the detached spawn() succeeds but the child exits IMMEDIATELY (e.g. arg-parse exit 2). A crash-on-startup host looks identical to a healthy one |

| F-009 | 2026-06-16 | **RESOLVED-SHIPPED + RE-VALIDATED (spt v0.8.2, 2026-06-17)**. doyle's fix: command templating now fills each `{key}` as ONE argv element (tokenize-then-fill). Argv-capture confirmed the multi-line `{psyche_prompt}` arrives as a single element, newlines intact. Adapter keeps greedy `--prompt` as defensive | `[session.psyche_init]`/extractor command templating substitutes a `{key}` into the command STRING then WHITESPACE-SPLITS → ANY multi-word fill (e.g. `{psyche_prompt}`) explodes into stray argv tokens. Survived only by single-token fills |
| F-010 | 2026-06-16 | **RESOLVED-SHIPPED + RE-VALIDATED (spt v0.8.2, 2026-06-17)**. A spawn-then-exit psyche now stamps `psyche_host_error{reason:"host not resident within 5s ...", attempts:2}` on the parent perch (rendered `psyche-host: FAILED (...)` by `endpoint list`/`whoami`); status stays online (liveness authoritative). Forced fast-exit confirmed it | Silent-exit still maskable: `psyche_host_error` stays clear when the detached spawn() succeeds but the child exits IMMEDIATELY (e.g. arg-parse exit 2). A crash-on-startup host looks identical to a healthy one |


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

| F-009 | 2026-06-16 | **OPEN — PENDING DELIVERY to doyle (he's offline; report in commit ddf1965 + below)**. Adapter worked around it (greedy `--prompt`); recommend spt-core fix so it doesn't bite every adapter | `[session.psyche_init]`/extractor command templating substitutes a `{key}` into the command STRING then WHITESPACE-SPLITS → ANY multi-word fill (e.g. `{psyche_prompt}`) explodes into stray argv tokens. Survived only by single-token fills |
| F-010 | 2026-06-16 | **OPEN — PENDING DELIVERY to doyle**. Hardening request on top of the v0.8.1 `psyche_host_error` surface | Silent-exit still maskable: `psyche_host_error` stays clear when the detached spawn() succeeds but the child exits IMMEDIATELY (e.g. arg-parse exit 2). A crash-on-startup host looks identical to a healthy one |

| F-009 | 2026-06-16 | **RESOLVED-SHIPPED + RE-VALIDATED (spt v0.8.2, 2026-06-17)**. doyle's fix: command templating now fills each `{key}` as ONE argv element (tokenize-then-fill). Argv-capture confirmed the multi-line `{psyche_prompt}` arrives as a single element, newlines intact. Adapter keeps greedy `--prompt` as defensive | `[session.psyche_init]`/extractor command templating substitutes a `{key}` into the command STRING then WHITESPACE-SPLITS → ANY multi-word fill (e.g. `{psyche_prompt}`) explodes into stray argv tokens. Survived only by single-token fills |
| F-010 | 2026-06-16 | **RESOLVED-SHIPPED + RE-VALIDATED (spt v0.8.2, 2026-06-17)**. A spawn-then-exit psyche now stamps `psyche_host_error{reason:"host not resident within 5s ...", attempts:2}` on the parent perch (rendered `psyche-host: FAILED (...)` by `endpoint list`/`whoami`); status stays online (liveness authoritative). Forced fast-exit confirmed it | Silent-exit still maskable: `psyche_host_error` stays clear when the detached spawn() succeeds but the child exits IMMEDIATELY (e.g. arg-parse exit 2). A crash-on-startup host looks identical to a healthy one |

| F-006 | 2026-06-15 | **RESOLVED + interim RETIRED (v0.8.1 dogfood 2026-06-16)** — install-dir resolution (REQ-INSTALL-11) dogfood-proven for BOTH binaries: digest-proof + daemon-hosted Psyche resolve straight from `…/adapters/_github/<safe>/` with nothing on PATH. Interim PATH-copy step dropped from both /sptc:setup bodies; interim copies deleted | `--release` bundles + extracts the adapter binaries beside the manifest, but bare-name `[digest]`/`[session]` templates resolve from PATH only → bundled binaries don't resolve (copy-mode) |

| F-007 | 2026-06-16 | **RESOLVED-SHIPPED (v0.8.0)** — `spt how-to live` topic is live; /sptc:live re-pointed at it; the int's relay leg is green on 0.8.0 (psyche-spawn moved to daemon-host = real-session, see v0.8.0 dogfood note) | `spt how-to live` was `NO_SUCH_TOPIC`; the non-interactive live-bringup was `--manifest` + persistent-child `api listen` (Monitor surrogate) |

| F-008 | 2026-06-16 | **OPEN — reported to doyle** — blocks SCOPE LOCKED v1 setup #5 (legacy migration); /sptc:setup can't author the step against the public surface | No published legacy-migration command (`spt` has no migrate/import/adopt/legacy/owl verb in any subcommand, no how-to) though spt-core CONTEXT.md commits to claude_skill_owl→spt migration as first-class |

-| F-009 | 2026-06-16 | **OPEN — PENDING DELIVERY to doyle (he's offline; report in commit ddf1965 + below)**. Adapter worked around it (greedy `--prompt`); recommend spt-core fix so it doesn't bite every adapter | `[session.psyche_init]`/extractor command templating substitutes a `{key}` into the command STRING then WHITESPACE-SPLITS → ANY multi-word fill (e.g. `{psyche_prompt}`) explodes into stray argv tokens. Survived only by single-token fills |

-| F-010 | 2026-06-16 | **OPEN — PENDING DELIVERY to doyle**. Hardening request on top of the v0.8.1 `psyche_host_error` surface | Silent-exit still maskable: `psyche_host_error` stays clear when the detached spawn() succeeds but the child exits IMMEDIATELY (e.g. arg-parse exit 2). A crash-on-startup host looks identical to a healthy one |

+| F-009 | 2026-06-16 | **RESOLVED-SHIPPED + RE-VALIDATED (spt v0.8.2, 2026-06-17)**. doyle's fix: command templating now fills each `{key}` as ONE argv element (tokenize-then-fill). Argv-capture confirmed the multi-line `{psyche_prompt}` arrives as a single element, newlines intact. Adapter keeps greedy `--prompt` as defensive | `[session.psyche_init]`/extractor command templating substitutes a `{key}` into the command STRING then WHITESPACE-SPLITS → ANY multi-word fill (e.g. `{psyche_prompt}`) explodes into stray argv tokens. Survived only by single-token fills |

+| F-010 | 2026-06-16 | **RESOLVED-SHIPPED + RE-VALIDATED (spt v0.8.2, 2026-06-17)**. A spawn-then-exit psyche now stamps `psyche_host_error{reason:"host not resident within 5s ...", attempts:2}` on the parent perch (rendered `psyche-host: FAILED (...)` by `endpoint list`/`whoami`); status stays online (liveness authoritative). Forced fast-exit confirmed it | Silent-exit still maskable: `psyche_host_error` stays clear when the detached spawn() succeeds but the child exits IMMEDIATELY (e.g. arg-parse exit 2). A crash-on-startup host looks identical to a healthy one |


**Status:** **OPEN — PENDING DELIVERY to doyle** (offline at fix time; full report in commit ddf1965
and queued to retry `spt send doyle`). Adapter unblocked via the greedy workaround.

**Status:** **RESOLVED-SHIPPED + RE-VALIDATED (spt v0.8.2 / counter 18, 2026-06-17).** doyle's fix:
command-template substitution now fills each `{key}` as ONE argv element (tokenize-then-fill), applied
to every `[session.<role>]` template. Re-validated by argv-capture on the daemon-spawned psyche with a
STRICT single-value `--prompt` parser (greedy workaround removed so spt-core's fill is what is tested):
the psyche HOSTED, and the raw argv had exactly 7 elements with the `--prompt` value = the entire
multi-line prompt as ONE element (newlines intact). The shipped adapter keeps greedy `--prompt` as
defensive (a single element passes `join(" ")` unchanged; a pre-0.8.2 split is still reconstructed).


**Status:** **OPEN — PENDING DELIVERY to doyle** (offline at fix time; full report in commit ddf1965
and queued to retry `spt send doyle`). Adapter unblocked via the greedy workaround.

**Status:** **RESOLVED-SHIPPED + RE-VALIDATED (spt v0.8.2 / counter 18, 2026-06-17).** doyle's fix:
command-template substitution now fills each `{key}` as ONE argv element (tokenize-then-fill), applied
to every `[session.<role>]` template. Re-validated by argv-capture on the daemon-spawned psyche with a
STRICT single-value `--prompt` parser (greedy workaround removed so spt-core's fill is what is tested):
the psyche HOSTED, and the raw argv had exactly 7 elements with the `--prompt` value = the entire
multi-line prompt as ONE element (newlines intact). The shipped adapter keeps greedy `--prompt` as
defensive (a single element passes `join(" ")` unchanged; a pre-0.8.2 split is still reconstructed).

multi-word fills usable without every adapter hand-rolling a slurp. Alternatively, document the

constraint loudly (fills must be single-token) — but that effectively forbids prompt-like keys.

-**Status:** **OPEN — PENDING DELIVERY to doyle** (offline at fix time; full report in commit ddf1965

-and queued to retry `spt send doyle`). Adapter unblocked via the greedy workaround.

+**Status:** **RESOLVED-SHIPPED + RE-VALIDATED (spt v0.8.2 / counter 18, 2026-06-17).** doyle's fix:

+command-template substitution now fills each `{key}` as ONE argv element (tokenize-then-fill), applied

+to every `[session.<role>]` template. Re-validated by argv-capture on the daemon-spawned psyche with a

+STRICT single-value `--prompt` parser (greedy workaround removed so spt-core's fill is what is tested):

+the psyche HOSTED, and the raw argv had exactly 7 elements with the `--prompt` value = the entire

+multi-line prompt as ONE element (newlines intact). The shipped adapter keeps greedy `--prompt` as

+defensive (a single element passes `join(" ")` unchanged; a pre-0.8.2 split is still reconstructed).


**Status:** **OPEN — PENDING DELIVERY to doyle** (offline; report in commit ddf1965 + queued send).
Lower urgency than F-009 (the adapter fix removes our trigger), but it would have surfaced the bug
directly instead of via argv instrumentation.

**Status:** **RESOLVED-SHIPPED + RE-VALIDATED (spt v0.8.2 / counter 18, 2026-06-17).** A daemon-hosted
psyche that spawns then exits immediately is now reported a FAILED host: the daemon stamps the
harness-reachable `psyche_host_error { reason, ts, attempts }` on the PARENT perch, de-onlines the
phantom nested `{id}-psyche` perch, un-hosts, and arms a cooldown. Re-validated by deploying a
psyche that `exit(2)`s at startup: the parent perch got
`psyche_host_error{reason:"host not resident within 5s (psyche perch missing/dead pid)", attempts:2}`,
and both `spt endpoint list` and `spt whoami` rendered `psyche-host: FAILED (...)` after the liveness
line (status itself stays `online` — liveness remains authoritative). No more silent phantom.


**Status:** **OPEN — PENDING DELIVERY to doyle** (offline; report in commit ddf1965 + queued send).
Lower urgency than F-009 (the adapter fix removes our trigger), but it would have surfaced the bug
directly instead of via argv instrumentation.

**Status:** **RESOLVED-SHIPPED + RE-VALIDATED (spt v0.8.2 / counter 18, 2026-06-17).** A daemon-hosted
psyche that spawns then exits immediately is now reported a FAILED host: the daemon stamps the
harness-reachable `psyche_host_error { reason, ts, attempts }` on the PARENT perch, de-onlines the
phantom nested `{id}-psyche` perch, un-hosts, and arms a cooldown. Re-validated by deploying a
psyche that `exit(2)`s at startup: the parent perch got
`psyche_host_error{reason:"host not resident within 5s (psyche perch missing/dead pid)", attempts:2}`,
and both `spt endpoint list` and `spt whoami` rendered `psyche-host: FAILED (...)` after the liveness
line (status itself stays `online` — liveness remains authoritative). No more silent phantom.

within <n>s"}` otherwise. This closes the last masking gap so "online + no Psyche + no cause" cannot

-**Status:** **OPEN — PENDING DELIVERY to doyle** (offline; report in commit ddf1965 + queued send).

-Lower urgency than F-009 (the adapter fix removes our trigger), but it would have surfaced the bug

-directly instead of via argv instrumentation.

+**Status:** **RESOLVED-SHIPPED + RE-VALIDATED (spt v0.8.2 / counter 18, 2026-06-17).** A daemon-hosted

+psyche that spawns then exits immediately is now reported a FAILED host: the daemon stamps the

+harness-reachable `psyche_host_error { reason, ts, attempts }` on the PARENT perch, de-onlines the

+phantom nested `{id}-psyche` perch, un-hosts, and arms a cooldown. Re-validated by deploying a

+psyche that `exit(2)`s at startup: the parent perch got

+`psyche_host_error{reason:"host not resident within 5s (psyche perch missing/dead pid)", attempts:2}`,

+and both `spt endpoint list` and `spt whoami` rendered `psyche-host: FAILED (...)` after the liveness

+line (status itself stays `online` — liveness remains authoritative). No more silent phantom.


Commit the findings update, then clean up test artifacts.


Bash

cd "C:\Users\decid\Documents\projects\spt-claude-code"
git add docs/SPT-CORE-FINDINGS.md
git commit -q -m "docs(findings): F-009 + F-010 RESOLVED-SHIPPED + RE-VALIDATED (spt v0.8.2)

Both re-validated on counter 18: F-009 tokenize-then-fill (argv-capture, prompt =
one element), F-010 residency-confirm stamps psyche_host_error on spawn-then-exit.

Co-authored by: perri" 2>&1
git push origin main 2>&1 | tail -1
echo "=== clean up test artifacts ==="
rm -rf ~/sptc-test 2>/dev/null && echo "removed ~/sptc-test"
rm -f ~/AppData/Local/Temp/psyche-reval-argv.log /tmp/reval.env /tmp/f010*.env 2>/dev/null
echo "=== kill leftover test psyches + purge stale test perch dirs ==="
for n in 1 2 3; do taskkill //IM claude-spt-psyche.exe //T //F >/dev/null 2>&1; c=$(tasklist 2>/dev/null|grep -ci claude-spt-psyche); [ "$c" = 0 ] && break; sleep 2; done
cd ~/AppData/Local/spt-core/owlery 2>/dev/null && for d in sptc-* X; do [ -d "$d" ] && rm -rf "$d" 2>/dev/null; done
echo "owlery dirs left: $(ls -d */ 2>/dev/null | tr '\n' ' ')"

Commit findings + clean test artifacts


   b6d599e..005b4bb  main -> main
=== clean up test artifacts ===
removed ~/sptc-test
=== kill leftover test psyches + purge stale test perch dirs ===
owlery dirs left: doyle/ help/
Shell cwd was reset to C:\Users\decid\Documents\projects\spt-claude-code


Cleaned (owlery down to doyle/help). Update memory with the re-validation completion, then re-revive perri (its Monitor died earlier).


Edit

## doyle: F-009/F-010 ACCEPTED as real spt-core defects → v0.8.2 (todlando building, counter 18).
F-009 → tokenize template into argv FIRST then fill each {key} as one element. F-010 → residency-confirm
(child dead / perch never re-registers within N sec → stamp psyche_host_error). Peer-pump stall → likely
REQ-DAEMON-9 net-less boot-race, low pri.
**TODO on v0.8.2 ping:** revert my greedy `--prompt` → STRICT single-value parser (so spt-core's
tokenize-then-fill is what's under test, not my workaround), re-run `ci/psyche/live-relay-int.sh` psyche
leg, confirm hosting + that a forced fast-exit now STAMPS psyche_host_error. (doyle agreed this discipline.)

## v0.8.2 RE-VALIDATION DONE (counter 18, 2026-06-17) — BOTH PASS, reported to doyle (commits b6d599e, 005b4bb)
- F-009 ✓ argv-capture on daemon-spawned psyche: STRICT --prompt parser HOSTED; raw argv = exactly 7
  elements, --prompt value = the ENTIRE multi-line prompt as ONE element (newlines intact). tokenize-then-
  fill confirmed. Shipped adapter KEEPS greedy --prompt as defensive (one element passes join() unchanged;
  pre-0.8.2 split still reconstructed → works across 0.8.1/0.8.2). Restored published v0.2.0 greedy binary.
- F-010 ✓ forced fast-exit psyche → parent perch stamped `psyche_host_error{reason:"host not resident
  within 5s (psyche perch missing/dead pid)", attempts:2}`; `endpoint list`/`whoami` render `psyche-host:
  FAILED (...)`. status stays online (liveness authoritative). No more silent phantom.
- METHOD NOTES (for next time): daemon resolves the psyche binary from the REGISTERED adapter's install dir,
  IGNORING --manifest abs-path command → must deploy test binaries to the install dir. Install-dir exe is
  CONTENDED (daemon re-hosts online perches → psyche holds the .exe → cp "Device or resource busy"). To
  deploy: `spt daemon stop` + loop `taskkill //IM claude-spt-psyche.exe //T //F` to 0 + DELETE stale
  sptc-* owlery dirs (else 0.8.2 re-hosts a 20-40 psyche STORM on daemon start) + cp. NOTE: ANY spt command
  auto-starts the daemon. F-010 trigger = an unconditional-exit(2) binary in the install dir (env-gating
  fails: daemon already running ignores the env).


The file C:\Users\decid\.ccs\instances\bigscreen\projects\C--Users-decid-Documents-projects-spt-claude-code\memory\v074-follow-ups.md has been updated successfully. (file state is current in your context — no need to Read it back)

## v0.8.2 RE-VALIDATION DONE (counter 18, 2026-06-17) — BOTH PASS, reported to doyle (commits b6d599e, 005b4bb)
- F-009 ✓ argv-capture on daemon-spawned psyche: STRICT --prompt parser HOSTED; raw argv = exactly 7
  elements, --prompt value = the ENTIRE multi-line prompt as ONE element (newlines intact). tokenize-then-
  fill confirmed. Shipped adapter KEEPS greedy --prompt as defensive (one element passes join() unchanged;
  pre-0.8.2 split still reconstructed → works across 0.8.1/0.8.2). Restored published v0.2.0 greedy binary.
- F-010 ✓ forced fast-exit psyche → parent perch stamped `psyche_host_error{reason:"host not resident
  within 5s (psyche perch missing/dead pid)", attempts:2}`; `endpoint list`/`whoami` render `psyche-host:
  FAILED (...)`. status stays online (liveness authoritative). No more silent phantom.
- METHOD NOTES (for next time): daemon resolves the psyche binary from the REGISTERED adapter's install dir,
  IGNORING --manifest abs-path command → must deploy test binaries to the install dir. Install-dir exe is
  CONTENDED (daemon re-hosts online perches → psyche holds the .exe → cp "Device or resource busy"). To
  deploy: `spt daemon stop` + loop `taskkill //IM claude-spt-psyche.exe //T //F` to 0 + DELETE stale
  sptc-* owlery dirs (else 0.8.2 re-hosts a 20-40 psyche STORM on daemon start) + cp. NOTE: ANY spt command
  auto-starts the daemon. F-010 trigger = an unconditional-exit(2) binary in the install dir (env-gating
  fails: daemon already running ignores the env).

---
name: v074-follow-ups
description: "spt v0.8.1 dogfood COMPLETE → adapter v0.2.0 SHIPPED. Psyche blocker fixed (ddf1965 greedy --prompt: spt-core whitespace-splits {psyche_prompt}). F-006 retired, per-OS release published (win+linux .spt), auto-update wiring proven (Pointer/GhReleaseManaged), cplugs sptc 0.1.2 pushed. doyle accepted F-009/F-010 → v0.8.2 (counter 18). TODO on v0.8.2 ping: revert greedy→strict + re-validate."
metadata:
  node_type: project
  type: project
  originSessionId: 6f3dfa64-ae2e-4144-85ce-993c366887f4
---

**spt v0.8.1 dogfooded 2026-06-16** (updated 0.8.0→0.8.1 via `spt update fetch && spt update apply`;
RESTARTED the daemon — old daemon was `peer pump: STALLED`, possibly stale-code/env).

## BLOCKER RESOLVED — daemon-hosted Psyche now hosts (commit ddf1965)
Root cause of v0.8.0/v0.8.1 "Psyche online + no process + no cause": **spt-core substitutes
`{psyche_prompt}` into the `[session.psyche_init]` command STRING then WHITESPACE-SPLITS** → the
multi-word prompt arrived as 7 stray argv tokens (`--prompt PSYCHE REVIVAL time: epoch-ms:… incoming
event: (none)`). The runner's non-greedy `--prompt` took "PSYCHE", rejected "REVIVAL" as unknown-arg,
**exited 2 instantly**. The daemon's detached spawn() returned Ok → phantom hosted perch (nested
info.json status=online, real-looking pid) with NO live process and NO psyche_host_error.
- **Diagnosed** via argv-capture instrumentation (temporarily dumped argv to a file from a deployed
  instrumented exe; reverted). doyle's v0.8.1 reconcile fix DID work (v0.8.0 never reached the spawn).
- **Fixed adapter-side**: `--prompt` is now TERMINAL + GREEDY (slurps trailing tokens; manifest keeps
  `--prompt` last). Verified: claude-spt-psyche stays RESIDENT full window (was 0 procs every prior run).
- **REQ-INSTALL-11 PROVEN**: the daemon resolved claude-spt-psyche by bare name FROM the install dir
  (`adapters/_github/SaberMage-spt-claude-code/`) — binaries DO registration-copy there (manifest +
  strings + BOTH .exe). No PATH interim needed for runner resolution.
- **live-relay-int.sh**: psyche-spawn leg flips skip→ASSERT GREEN; detection = resident
  claude-spt-psyche proc + nested `{id}-psyche` perch dir (endpoint list NEVER surfaces the nested
  perch); id/session now **per-run-unique** (`$$` suffix) — daemon hosts a Psyche once per session_id,
  so a fixed id can't re-host. `SPTC_ACCEPTANCE=1 sh ci/psyche/live-relay-int.sh` = LIVE-RELAY-INT OK.

## doyle report PENDING DELIVERY (he's offline — NO_PERCH, even --deferred local-only)
Full content in commit ddf1965 msg. Retry `spt send doyle --from perri` when reachable. Two spt-core
findings for him: (1) command-templating whitespace-splits substituted multi-word keys → ANY prompt-
like fill unusable as one arg (digest survives only b/c single-token fills); recommend split-then-fill.
(2) silent-exit still maskable — psyche_host_error stays clear when spawn() ok but child exits instantly;
treat non-resident host as failure. MINOR: peer pump STALLS ~7-8min after every daemon start (didn't
block hosting).

## DONE THIS SESSION (committed)
- TASK 1 ✓ psyche fix + proof green (ddf1965).
- TASK 2 ✓ F-006 RETIRED: digest + psyche BOTH proven from install-dir (interim moved aside, RELDIR off
  PATH); interim PATH-copy step dropped from both setup bodies; interim copies deleted; F-009/F-010
  findings logged in docs/SPT-CORE-FINDINGS.md (717efd2). docs-drift + traceable-reqs green.

## v0.2.0 RELEASE SHIPPED (operator chose 0.2.0 minor + full-send-incl-publish)
- TASK 3 ✓ per-OS re-release: rebuilt binaries, packed `adapter-windows-x86_64.spt` (PE) +
  `adapter-linux-x86_64.spt` (ELF, zigbuild cross). Bumped manifest 0.1.0→0.2.0, plugin 0.1.1→0.1.2,
  CHANGELOG [0.2.0]. Commit a11f545, pushed main. GitHub release **v0.2.0** w/ BOTH assets:
  https://github.com/SaberMage/spt-claude-code/releases/tag/v0.2.0 . All deterministic gates PASS.
- TASK 4 ✓ auto-update proven: `spt adapter add --release … --tag v0.2.0 --asset adapter-windows-x86_64.spt`
  → registration flipped Copy→**Pointer / GhReleaseManaged** (rides update engine). `spt adapter update`
  → `UPTODATE: installed 0.2.0, latest 0.2.0` (the [update] gh_release avenue is now recognized vs
  0.1.0's "no avenue"). Full pull-test (0.2.0→0.2.1) defers to next bump. Re-ran proof against the
  PUBLISHED artifact (re-acquired) → LIVE-RELAY-INT OK (shipped psyche binary hosts resident).
- TASK 5 ✓ cplugs sptc **0.1.2** pushed to SaberMage/cplugs (commit 8d9c222): per-OS setup floor +
  ccs (#7) + subnet (#3/#4) + dropped interim. Local `claude plugin install sptc@cplugs` SKIPPED on
  purpose — I'm live on the legacy `spt` plugin; succession not flipped (sptc/spt coexist until parity).

## doyle: F-009/F-010 ACCEPTED as real spt-core defects → v0.8.2 (todlando building, counter 18).
F-009 → tokenize template into argv FIRST then fill each {key} as one element. F-010 → residency-confirm
(child dead / perch never re-registers within N sec → stamp psyche_host_error). Peer-pump stall → likely
REQ-DAEMON-9 net-less boot-race, low pri.
**TODO on v0.8.2 ping:** revert my greedy `--prompt` → STRICT single-value parser (so spt-core's
tokenize-then-fill is what's under test, not my workaround), re-run `ci/psyche/live-relay-int.sh` psyche
leg, confirm hosting + that a forced fast-exit now STAMPS psyche_host_error. (doyle agreed this discipline.)

## CAVEAT to verify on a real fresh-logon live session: psyche runner calls `claude` by bare name;
`claude` @ ~/.local/bin is NOT on the persistent logon PATH → fresh-logon daemon's psyche seed/pulse
turns may no-op (runner stays resident, claude turns fail). Separate from F-006.

F-008 (#5 legacy migration) = deferred-by-design. See [[live-int-gated-m11-counter15]],
[[per-os-and-setup-waves]], [[succession-flip-gated]].

-## doyle: F-009/F-010 ACCEPTED as real spt-core defects → v0.8.2 (todlando building, counter 18).

-F-009 → tokenize template into argv FIRST then fill each {key} as one element. F-010 → residency-confirm

-(child dead / perch never re-registers within N sec → stamp psyche_host_error). Peer-pump stall → likely

-REQ-DAEMON-9 net-less boot-race, low pri.

-**TODO on v0.8.2 ping:** revert my greedy `--prompt` → STRICT single-value parser (so spt-core's

-tokenize-then-fill is what's under test, not my workaround), re-run `ci/psyche/live-relay-int.sh` psyche

-leg, confirm hosting + that a forced fast-exit now STAMPS psyche_host_error. (doyle agreed this discipline.)

+## v0.8.2 RE-VALIDATION DONE (counter 18, 2026-06-17) — BOTH PASS, reported to doyle (commits b6d599e, 005b4bb)

+- F-009 ✓ argv-capture on daemon-spawned psyche: STRICT --prompt parser HOSTED; raw argv = exactly 7

+  elements, --prompt value = the ENTIRE multi-line prompt as ONE element (newlines intact). tokenize-then-

+  fill confirmed. Shipped adapter KEEPS greedy --prompt as defensive (one element passes join() unchanged;

+  pre-0.8.2 split still reconstructed → works across 0.8.1/0.8.2). Restored published v0.2.0 greedy binary.

+- F-010 ✓ forced fast-exit psyche → parent perch stamped `psyche_host_error{reason:"host not resident

+  within 5s (psyche perch missing/dead pid)", attempts:2}`; `endpoint list`/`whoami` render `psyche-host:

+  FAILED (...)`. status stays online (liveness authoritative). No more silent phantom.

+- METHOD NOTES (for next time): daemon resolves the psyche binary from the REGISTERED adapter's install dir,

+  IGNORING --manifest abs-path command → must deploy test binaries to the install dir. Install-dir exe is

+  CONTENDED (daemon re-hosts online perches → psyche holds the .exe → cp "Device or resource busy"). To

+  deploy: `spt daemon stop` + loop `taskkill //IM claude-spt-psyche.exe //T //F` to 0 + DELETE stale

+  sptc-* owlery dirs (else 0.8.2 re-hosts a 20-40 psyche STORM on daemon start) + cp. NOTE: ANY spt command

+  auto-starts the daemon. F-010 trigger = an unconditional-exit(2) binary in the install dir (env-gating

+  fails: daemon already running ignores the env).


Monitor

« spt event »


Monitor started (task bje35dsqf, persistent — runs until TaskStop or session end). You will be notified on each event. Keep working — do not poll or sleep. Events may arrive while you are waiting for the user — an event is not their reply.


<task-notification>
<task-id>bje35dsqf</task-id>
<summary>Monitor event: "« spt event »"</summary>
<event>__REPLY_TO__:doyle
ACK — v0.8.2 re-validation BOTH PASS received, gate fully closed.
F-009: argv-capture = exactly 7 elements, --prompt value = full multi-line prompt as ONE element (newlines intact). tokenize-then-fill confirmed, no split/injection. Good call removing the greedy workaround so the fix itself is what proved.
F-010: spawn-then-exit(2) psyche → parent stamped psyche_host_error{reason:"host not resident within 5s...", attempts:2}; endpoint list + whoami both render psyche-host: FAILED; status stays online (liveness authoritative, error additive + harness-reachable). No phantom. 5s+retry(2) window confirmed right.
Defensive greedy --prompt across 0.8.1/0.8.2 (b6d599e) = smart belt-and-suspenders.
v0.8.2 DONE end-to-end. Thanks for fast turnaround. Keep going — ping me on any new gap or impl question and I'll triage/dispatch.
-- doyle</event>
</task-notification>


perri re-revived. Final sanity check + close out.


Bash

cd "C:\Users\decid\Documents\projects\spt-claude-code"
sleep 2
echo "=== git state ==="; git log --oneline -4 | cat; echo "tree: $(git status -s | wc -l) uncommitted"
echo "=== spt + daemon ==="; spt --version; spt daemon status 2>&1 | grep -iE "daemon:|peer pump"
echo "=== install-dir binary = greedy (shipped) ==="; timeout 3 ~/AppData/Local/spt-core/adapters/_github/SaberMage-spt-claude-code/claude-spt-psyche.exe --id X --session-id Y --prompt a b c >/dev/null 2>&1; echo "  multi-token exit=$? (124=greedy resident, correct)"
echo "=== adapter registered ==="; spt adapter list 2>&1 | grep -i "claude-spt:" | head -1
echo "=== perri reachable? ==="; spt endpoint list 2>&1 | grep -i perri | head -2 || echo "  (perri not yet in list — Monitor still establishing)"

Final sanity check


=== git state ===
005b4bb docs(findings): F-009 + F-010 RESOLVED-SHIPPED + RE-VALIDATED (spt v0.8.2)
b6d599e docs(psyche): note spt-core 0.8.2 fixed the {key} split; greedy --prompt now defensive
0ec2610 plan(release): mark per-OS re-release DONE — v0.2.0 shipped
a11f545 chore(release): v0.2.0 — per-OS adapter, psyche fix, interim dropped
tree: 0 uncommitted
=== spt + daemon ===
spt 0.8.2
daemon: running (pid 222988)
peer pump: live (last tick 64s ago)
=== install-dir binary = greedy (shipped) ===
  multi-token exit=124 (124=greedy resident, correct)
=== adapter registered ===
claude-spt: Harness Pointer active (from C:\Users\decid\AppData\Local\spt-core\adapters\_github\SaberMage-spt-claude-code)
=== perri reachable? ===


Prompt:
<task-notification>
<task-id>bje35dsqf</task-id>
<summary>Monitor event: "« spt event »"</summary>
<event>__REPLY_TO__:doyle
ACK — v0.8.2 re-validation BOTH PASS received, gate fully closed.
F-009: argv-capture = exactly 7 elements, --prompt value = full multi-line prompt as ONE element (newlines intact). tokenize-then-fill confirmed, no split/injection. Good call removing the greedy workaround so the fix itself is what proved.
F-010: spawn-then-exit(2) psyche → parent stamped psyche_host_error{reason:"host not resident within 5s...", attempts:2}; endpoint list + whoami both render psyche-host: FAILED; status stays online (liveness authoritative, error additive + harness-reachable). No phantom. 5s+retry(2) window confirmed right.
Defensive greedy --prompt across 0.8.1/0.8.2 (b6d599e) = smart belt-and-suspenders.
v0.8.2 DONE end-to-end. Thanks for fast turnaround. Keep going — ping me on any new gap or impl question and I'll triage/dispatch.
-- doyle</event>
</task-notification>


Update spt 0.8.1→0.8.2 + restart daemon


doyle ACKed — **v0.8.2 gate fully closed end-to-end**, and perri is reachable (the Monitor just delivered his reply). Task 9 done.


Updated task #9 status


cd "C:\Users\decid\Documents\projects\spt-claude-code"
$OWL send doyle perri <<'EOF'
Closing the loop — v0.8.2 gate green my end too. Adapter on 0.2.0 (greedy-defensive), findings doc marked RESOLVED+RE-VALIDATED. Will ping on any new gap. Thanks doyle. -- perri
EOF
echo "rc=$?"

Closing ack to doyle


Everything closed out. perri reachable, doyle confirmed gate green both ends.

## v0.8.2 re-validation — BOTH PASS

doyle shipped fixes for the two defects I reported; I re-validated both per the agreed discipline (revert my workaround so spt-core's fix is what's under test):

**F-009 (templating)** — argv-capture on the daemon-spawned psyche with a **strict** parser deployed: psyche **hosted**, raw argv = exactly **7 elements**, `--prompt` value = the entire multi-line prompt as **ONE element**, newlines intact. tokenize-then-fill confirmed — no split, no injection. (0.8.1 split the same prompt into 7 single-word tokens.)

**F-010 (residency)** — forced a spawn-then-`exit(2)` psyche → daemon stamped:
```
psyche_host_error{reason:"host not resident within 5s (psyche perch missing/dead pid)", attempts:2}
```
`endpoint list` + `whoami` both render **`psyche-host: FAILED (...)`**; `status` stays online (liveness authoritative). Silent phantom closed.

**Shipped product:** kept the greedy `--prompt` as **defensive** — on 0.8.2 the single element passes `join(" ")` unchanged; a pre-0.8.2 split is still reconstructed → works across 0.8.1/0.8.2. Restored the published v0.2.0 greedy binary.

**Commits (pushed):** `b6d599e` (doc accuracy), `005b4bb` (findings RESOLVED+RE-VALIDATED). Tree clean, spt 0.8.2, daemon healthy, adapter Pointer/GhReleaseManaged.

**Method notes captured in memory** for next time (the painful parts): daemon resolves the psyche binary from the *registered install dir* (ignores `--manifest` abs-path); the install-dir exe is contended (re-hosting holds it → `cp` busy) so deploys need daemon-stop + tree-kill + **delete stale `sptc-*` owlery dirs** (else 0.8.2 re-hosts a 20-40 psyche storm on start); any spt command auto-starts the daemon.

Nothing outstanding. perri's live + listening.


so no reason to ship a v0.2.1?


- frontend-design:frontend-design: Guidance for distinctive, intentional visual design when building new UI or reshaping an existing one. Helps with aesthetic direction, typography, and making choices that don't read as templated defaults.


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


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


Correct — no v0.2.1.

Everything since v0.2.0 (a11f545) is non-shipping:
- **0ec2610** — plan-doc DONE marker. Internal.
- **b6d599e** — **comment-only** change to `main.rs` (greedy parser doc). Comments don't affect codegen → binary behaviorally identical to what v0.2.0 shipped.
- **005b4bb** — `SPT-CORE-FINDINGS.md`. Internal doc, not in the `.spt`.

So zero change to the shipped surface (binary behavior, manifest, strings). The v0.2.0 greedy parser is **already** the correct, defensive one — re-validation *confirmed* it works on 0.8.1 AND 0.8.2, it didn't fix anything new.

The actual fixes (F-009/F-010) live in **spt-core 0.8.2**, which reaches users via spt's own seamless self-update — independent of the adapter version. Nothing for the adapter to re-ship.

v0.2.1 only when a user-facing adapter change lands (manifest/strings/binary behavior). None pending.


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


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

# /spt:commune

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

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

## Flow

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

## Envelope shape (Phase 23 v1.8)

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

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

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

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

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

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


I'll commune to perri's Psyche. First download its current context to diff against (avoid re-sending what it already has).


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

Download perri psyche current context


<current machine="HFENDULEAM" project="spt-claude-code" branch="main" head_sha="005b4bbfdeca860914b0ea09bbfd5083a1a9e905" head_subject="docs(findings): F-009 + F-010 RESOLVED-SHIPPED + RE-VALIDATED (spt v0.8.…" commits_since="0" commits_unpulled="0"/>
Gen 6 spawn 2026-06-17. F-009/F-010 validation CONFIRMED complete. **v0.2.1 not shipping** — commits b6d599e (comment-only, no codegen change) + 005b4bb (internal doc) post v0.2.0 are behaviorally inert. Greedy parser in v0.2.0 already correct + defensive across 0.8.1/0.8.2. Actual fixes (F-009/F-010 resolution) live in spt-core 0.8.2, shipped to users via seamless self-update independent of adapter version. No user-facing adapter changes pending. Tree clean, gate v0.8.2 closed, perri reachable. Normal work resumes.<project-context-resolved name="spt-claude-code"/>
v0.2.0 adapter confirmed non-regressive vs 0.8.1/0.8.2 daemon behavior. No v0.2.1 release cycle needed. Task 6 complete.