=== runtime.rs fill_template_tokens (F-009) ===
diff --git a/crates/spt-runtime/src/runtime.rs b/crates/spt-runtime/src/runtime.rs
index 58dac47..c077ed5 100644
--- a/crates/spt-runtime/src/runtime.rs
+++ b/crates/spt-runtime/src/runtime.rs
@@ -116,6 +116,51 @@ pub fn fill_template(
     Ok(out)
 }

+/// Tokenize an opaque command **template** into argv elements FIRST, then
+/// [`fill_template`] **each token individually**, so a `{key}` slot resolves to
+/// exactly ONE argv element regardless of spaces/quotes/semicolons in the value
+/// — the value never participates in tokenization (no whitespace-split, no
+/// quote/semicolon injection). This is the fill order spt-core uses for every
+/// `[session.<role>]` command and every bounded command line (REQ-HAZARD-TEMPLATE-ARGV-FILL):
+///
+/// - The TEMPLATE is tokenized (double-quotes honored, KH-free) so the adapter's
+///   own quoting structure determines argv boundaries.
+/// - Each resulting token is then filled. A token that is exactly `{key}` becomes
+///   the whole value as one element; a `{key}` inside a larger token
+///   (`--flag={key}`, `pre{key}post`) substitutes in place, still one element; a
+///   multi-word / quote-bearing / semicolon-bearing value stays ONE element (it
+///   is NOT re-tokenized). Tokens with no placeholder pass through unchanged.
+/// - Per-token errors are the same as [`fill_template`]: a missing key yields
+///   [`RuntimeError::MissingKey`] (carrying the original template for context),
+///   and `{{`/`}}` literal-brace escaping works per token.
+///
+/// Returns the filled argv vector; an empty template yields an empty vector
+/// (callers map that to [`RuntimeError::EmptyCommand`]).
+// [impl->REQ-HAZARD-TEMPLATE-ARGV-FILL]
+pub fn fill_template_tokens(
+    template: &str,
+    keys: &BTreeMap<String, String>,
+) -> Result<Vec<String>, RuntimeError> {
+    let tokens = tokenize(template);
+    let mut filled = Vec::with_capacity(tokens.len());
+    for tok in tokens {
+        // Fill EACH token in isolation: the value lands as one argv element and
+        // never re-enters tokenization. Keep fill_template's missing-key error
+        // but report the FULL template (not the single token) for operator context.
+        match fill_template(&tok, keys) {
+            Ok(v) => filled.push(v),
+            Err(RuntimeError::MissingKey { key, .. }) => {
+                return Err(RuntimeError::MissingKey {
+                    key,
+                    command: template.to_string(),
+                });
+            }
+            Err(other) => return Err(other),
+        }
+    }
+    Ok(filled)
+}
+
 /// Split a filled command line into program + args, honoring double quotes so a
 /// quoted argument with spaces stays one token. Clean-room (no shell dependency,
 /// portable). Single quotes are literal — adapters quote with `"`.
@@ -253,8 +298,11 @@ impl ManifestRuntime {
         role: &SessionRole,
         keys: &BTreeMap<String, String>,
     ) -> Result<Command, RuntimeError> {
-        let filled = fill_template(&role.command, keys)?;
-        let tokens = tokenize(&filled);
+        // [impl->REQ-HAZARD-TEMPLATE-ARGV-FILL] tokenize the TEMPLATE first, then
+        // fill each token, so a multi-word/quote/semicolon-bearing {key} value is
+        // exactly ONE argv element (never re-split / injected). The program token
+        // (tokens[0], filled) then resolves against the install dir below.
+        let tokens = fill_template_tokens(&role.command, keys)?;
         let (program, args) = tokens.split_first().ok_or(RuntimeError::EmptyCommand)?;
         // [impl->REQ-INSTALL-11] resolve a bare program token against the adapter
         // install dir before PATH so a `.spt` that ships its binary (e.g. the
@@ -462,9 +510,12 @@ pub fn run_bounded_command_in(
     stdin: Option<&[u8]>,
     install_dir: Option<&Path>,
 ) -> Result<BoundedOutput, RuntimeError> {
-    let filled = fill_template(template, keys)?;
-    let tokens = tokenize(&filled);
+    // [impl->REQ-HAZARD-TEMPLATE-ARGV-FILL] tokenize-the-template-then-fill-each so a
+    // multi-word/quote/semicolon-bearing {key} value is exactly ONE argv element.
+    let tokens = fill_template_tokens(template, keys)?;
     let (program, args) = tokens.split_first().ok_or(RuntimeError::EmptyCommand)?;
+    // The timeout/label string mirrors the resolved command line for diagnostics.
+    let filled = tokens.join(" ");
     // [impl->REQ-INSTALL-11] adapter install dir wins over PATH for a bare token.
     let program = match install_dir {
         Some(dir) => resolve_program_in_dir(program, dir),
@@ -529,6 +580,102 @@ mod tests {
         }
     }

+    // [unit->REQ-HAZARD-TEMPLATE-ARGV-FILL] (a) a MULTI-WORD value fills to exactly
+    // ONE argv element — the value never whitespace-splits into stray tokens (the
+    // F-009 defect: psyche_prompt's spaces became argv[6..12]).
+    #[test]
+    fn multiword_value_is_one_argv_element() {
+        let k = keys(&[(
+            "psyche_prompt",
+            "PSYCHE REVIVAL time: epoch incoming event: (none)",
+        )]);
+        let argv = fill_template_tokens("runner --prompt {psyche_prompt}", &k).unwrap();
+        assert_eq!(
+            argv,
+            vec![
+                "runner".to_string(),
+                "--prompt".to_string(),
+                "PSYCHE REVIVAL time: epoch incoming event: (none)".to_string(),
+            ],
+            "the multi-word value is exactly ONE argv element, not re-split"
+        );
+    }
+
+    // [unit->REQ-HAZARD-TEMPLATE-ARGV-FILL] (b) a value containing a `\"` or a `;`
+    // each stay ONE element — no injection, no extra tokens (shell-injection-adjacent
+    // characters do not participate in tokenization).
+    #[test]
+    fn quote_and_semicolon_values_stay_one_element() {
+        let q = keys(&[("psyche_prompt", r#"a "quoted" b"#)]);
+        let argv_q = fill_template_tokens("runner --prompt {psyche_prompt}", &q).unwrap();
+        assert_eq!(argv_q, vec!["runner", "--prompt", r#"a "quoted" b"#]);
+        assert_eq!(argv_q.len(), 3, "a value with a quote injects no extra token");
+


F-009 fix is correct: `fill_template_tokens` = tokenize(template) then fill each token; value lands as one argv element, never re-tokenized; errors preserved; `command_for` + `run_bounded_command_in` rewired, install-dir resolution kept. Units assert exact argv for multi-word + quote/semicolon. **F-009 PASS.**

Now the residency logic + the note-2 timing crux.


Bash

cd /c/Users/decid/Documents/projects/spt-core
git diff origin/main..HEAD -- crates/spt-daemon/src/livehost.rs | head -200

Read F-010 residency diff in livehost.rs


diff --git a/crates/spt-daemon/src/livehost.rs b/crates/spt-daemon/src/livehost.rs
index 7ede331..13f24ea 100644
--- a/crates/spt-daemon/src/livehost.rs
+++ b/crates/spt-daemon/src/livehost.rs
@@ -28,7 +28,7 @@ use std::path::Path;
 use std::sync::atomic::{AtomicBool, Ordering};
 use std::sync::{Arc, Mutex};
 use std::thread::JoinHandle;
-use std::time::Duration;
+use std::time::{Duration, Instant};

 use spt_runtime::registry::AdapterRecord;
 use spt_runtime::Manifest;
@@ -48,19 +48,73 @@ const LIVE_AGENT_STATE: &str = "live_agent";
 /// (mirrors [`crate::shellwake::RECONCILE_INTERVAL_MS`]).
 pub const LIVE_RECONCILE_INTERVAL_MS: u64 = 5_000;

-/// One brain-hosted live lifecycle: its config-paced pulse-driver thread plus the
-/// held stop flag (tripped to un-host on an offline-transition).
+/// Residency grace after a host attempt: a hosted child's `{id}-psyche` perch
+/// must show a LIVE pid within this window, else the host is declared failed
+/// (REQ-HAZARD-LIVEHOST-NONRESIDENT). Chosen as ~one reconcile interval (5s): a
+/// child that exited fast (the F-009 bad-argv case, ~1s) is well past it on the
+/// next tick, while a slow-but-healthy psyche binary that is still coming up
+/// inside the first interval is NOT prematurely torn down. The check runs on a
+/// LATER tick (never a synchronous wait inside the sweep), so the effective
+/// detection latency is `grace + up to one reconcile interval`.
+pub const PSYCHE_RESIDENCY_GRACE: Duration = Duration::from_secs(5);
+
+/// The residency verdict for a hosted endpoint's `{id}-psyche` child, decided
+/// purely from observed facts (no IO) so it is unit-testable in isolation.
+// [impl->REQ-HAZARD-LIVEHOST-NONRESIDENT]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Residency {
+    /// The psyche child is alive (a live recorded pid) — host confirmed.
+    Resident,
+    /// Still inside the grace window — too early to judge; check again next tick.
+    Pending,
+    /// Past grace with a missing/dead `{id}-psyche` pid — a host failure.
+    Failed,
+}
+
+/// Decide residency from observed facts: how long since the host attempt, the
+/// grace window, and whether the `{id}-psyche` perch currently shows a LIVE pid
+/// (`None` ⇒ the perch is missing or carries no/dead numeric pid). Pure.
+// [impl->REQ-HAZARD-LIVEHOST-NONRESIDENT]
+pub fn classify_residency(elapsed: Duration, grace: Duration, psyche_alive: bool) -> Residency {
+    if psyche_alive {
+        Residency::Resident
+    } else if elapsed < grace {
+        Residency::Pending
+    } else {
+        Residency::Failed
+    }
+}
+
+/// One brain-hosted live lifecycle: its config-paced pulse-driver thread, the
+/// held stop flag (tripped to un-host on an offline-transition), and the host
+/// start instant (the anchor the residency grace counts from).
 struct HostedLife {
     stop: Arc<AtomicBool>,
     thread: JoinHandle<()>,
+    /// When [`host_one`] spawned this endpoint's Psyche — the residency-grace
+    /// anchor (REQ-HAZARD-LIVEHOST-NONRESIDENT).
+    hosted_at: Instant,
 }

+/// After a residency failure, hold off re-hosting the doomed endpoint for this
+/// long so the system goes QUIESCENT in the failed state — the parent's
+/// `psyche_host_error` stamp stays visible and the phantom nested perch stays
+/// cleared, instead of a spawn-storm of a fast-dying psyche every reconcile tick
+/// (and a flickering stamp). One reconcile interval: the next host attempt is the
+/// tick AFTER the cooldown, so a transiently-broken psyche still gets retried, a
+/// permanently-broken one is stamped + quiet (REQ-HAZARD-LIVEHOST-NONRESIDENT).
+pub const RESIDENCY_RETRY_COOLDOWN: Duration = Duration::from_millis(LIVE_RECONCILE_INTERVAL_MS);
+
 /// The brain's hosted live-endpoint table, keyed by endpoint id. Shared between
 /// the reconcile loop and anything that must stop a host. Mirror of
 /// [`crate::shellwake::WakeSet`].
 #[derive(Default)]
 pub struct LiveSet {
     inner: Mutex<HashMap<String, HostedLife>>,
+    /// Per-endpoint cooldown deadline after a residency failure: the start-side
+    /// skips re-hosting until now passes this instant, so a doomed psyche is not
+    /// re-spawned every tick (REQ-HAZARD-LIVEHOST-NONRESIDENT).
+    nonresident_until: Mutex<HashMap<String, Instant>>,
 }

 impl LiveSet {
@@ -105,6 +159,41 @@ impl LiveSet {
         map.keys().cloned().collect()
     }

+    /// How long since `id` was hosted (the residency-grace anchor), or `None` if
+    /// it is not tabled. [`host_one`] stamps `hosted_at` at spawn time.
+    // [impl->REQ-HAZARD-LIVEHOST-NONRESIDENT]
+    fn hosted_elapsed(&self, id: &str) -> Option<Duration> {
+        let map = self.inner.lock().unwrap_or_else(|p| p.into_inner());
+        map.get(id).map(|h| h.hosted_at.elapsed())
+    }
+
+    /// Arm the residency-failure cooldown for `id` (the start-side then skips
+    /// re-hosting until it expires). // [impl->REQ-HAZARD-LIVEHOST-NONRESIDENT]
+    fn arm_nonresident_cooldown(&self, id: &str) {
+        let mut map = self
+            .nonresident_until
+            .lock()
+            .unwrap_or_else(|p| p.into_inner());
+        map.insert(id.to_string(), Instant::now() + RESIDENCY_RETRY_COOLDOWN);
+    }
+
+    /// Is `id` still inside its residency-failure cooldown (skip re-host)? Expired
+    /// entries are pruned. // [impl->REQ-HAZARD-LIVEHOST-NONRESIDENT]
+    fn in_nonresident_cooldown(&self, id: &str) -> bool {
+        let mut map = self
+            .nonresident_until
+            .lock()
+            .unwrap_or_else(|p| p.into_inner());
+        match map.get(id) {
+            Some(until) if Instant::now() < *until => true,
+            Some(_) => {
+                map.remove(id);
+                false
+            }
+            None => false,
+        }
+    }
+
     /// Un-host one lifecycle: trip its stop flag, join the driver, de-table. The
     /// pulse loop checks the flag each sleep-slice, so the join is prompt. We do
     /// NOT re-stamp `offline` — the perch already left the online set (graceful
@@ -163,7 +252,15 @@ pub fn reconcile_once(
         };
         if unhost {
             set.stop_host(&id);
+            continue;
         }
+        // [impl->REQ-HAZARD-LIVEHOST-NONRESIDENT] RESIDENCY confirm: the parent is
+        // still online, but if its `{id}-psyche` child spawned then DIED within the
+        // grace window (the F-009 bad-argv exit-2 case — spawn() returned Ok so the
+        // v0.8.1 spawn-Err stamp never fired), stamp the parent psyche_host_error,
+        // un-host, and clear the phantom online nested perch. Runs on a LATER tick,
+        // never a synchronous wait — the sweep is not blocked.
+        confirm_residency_or_unhost(set, &id);
     }

     // Start side: every online live endpoint not yet hosted.
@@ -171,6 +268,13 @@ pub fn reconcile_once(
         if set.contains(&id) {
             continue; // already hosted (leave-alone)
         }
+        // [impl->REQ-HAZARD-LIVEHOST-NONRESIDENT] a just-failed residency endpoint is
+        // held off until its cooldown expires — do NOT re-spawn the doomed psyche this
+        // same sweep (that would clear+recreate the phantom + churn). One quiescent
+        // window, then a single retry the tick after the cooldown.
+        if set.in_nonresident_cooldown(&id) {
+            continue;
+        }
         let perch = perch::resolve_perch_path(&id, ParentHint::Infer);
         let Some(info) = spt_store::info::read_info(&perch) else {
             continue;
@@ -228,10 +332,12 @@ fn host_one(
     match lifecycle.spawn_psyche(Some(&session_id), None) {
         Ok(h) => {
             eprintln!("LIVEHOST_PSYCHE:{id} pid={}", h.pid);
-            // Recovered: CLEAR any prior psyche-host-failure stamp so a now-hosted
-            // live agent shows clean. Best-effort — status is untouched.
-            // [impl->REQ-HAZARD-LIVEHOST-BOOT-RACE]
-            let _ = spt_store::info::set_psyche_host_error(&perch, None);
+            // NOTE: the prior psyche-host-failure stamp is NOT cleared here. Post
+            // F-010 a successful detached spawn() is NOT a successful host — the
+            // child may die within moments (the bad-argv exit-2 case). The clear
+            // moved to confirm_residency_or_unhost's Resident arm, where a LIVE
+            // resident psyche past the grace window proves the host truly took.
+            // [impl->REQ-HAZARD-LIVEHOST-NONRESIDENT]
         }
         Err(e) => {
             // Keep the (invisible) brain-stderr line AND stamp the failure onto the
@@ -240,7 +346,8 @@ fn host_one(
             // reason+ts, increment attempts (the helper reads the prior count inside
             // its locked RMW). `status` is NOT touched — liveness stays authoritative.
             // host_one returns without tabling, so the 5s reconcile retries and each
-            // retry re-stamps; an eventual success clears it above.
+            // retry re-stamps; a later CONFIRMED-RESIDENT host clears it (in
+            // confirm_residency_or_unhost, not on bare spawn-Ok — see F-010 note above).
             // [impl->REQ-HAZARD-LIVEHOST-BOOT-RACE]
             eprintln!("LIVEHOST_PSYCHE_FAIL:{id}: {e}");
             let _ = spt_store::info::set_psyche_host_error(&perch, Some(&e.to_string()));
@@ -254,7 +361,83 @@ fn host_one(
             lifecycle.run_pulse_loop(Some(&session_id), &stop, reason, |_report| {});
         })
     };
-    set.insert(id, HostedLife { stop, thread: handle });
+    // Stamp the host instant — the residency-grace anchor a later tick measures
+    // the `{id}-psyche` perch's liveness against (REQ-HAZARD-LIVEHOST-NONRESIDENT).
+    set.insert(
+        id,
+        HostedLife {
+            stop,
+            thread: handle,


Residency design is sound: pure `classify_residency` (Resident/Pending/Failed), 5s grace checked on a later tick (no blocking wait), Failed→stamp+un-host+de-phantom+cooldown (no spawn-storm), clear moved to Resident arm. Reading the rest of `confirm_residency_or_unhost` + the nonresident E2E, then I rule on note 2 — which I suspect needs a re-spin.


cd /c/Users/decid/Documents/projects/spt-core
echo "=== rest of livehost diff (confirm_residency_or_unhost) ==="; git diff origin/main..HEAD -- crates/spt-daemon/src/livehost.rs | sed -n '200,320p'

Read confirm_residency_or_unhost body


=== rest of livehost diff (confirm_residency_or_unhost) ===
+            thread: handle,
+            hosted_at: Instant::now(),
+        },
+    );
+}
+
+/// Confirm RESIDENCY of a hosted endpoint's `{id}-psyche` child past its grace
+/// window — the F-010 fix (REQ-HAZARD-LIVEHOST-NONRESIDENT). `spawn_psyche`
+/// detaches (the summarizer handle is dropped), so a child that spawned Ok but
+/// DIED within moments (a bad-argv exit-2 child — the F-009 case) leaves the
+/// nested `{id}-psyche` info.json written `status=online` with a real-but-DEAD
+/// pid while the PARENT carries no failure. The reconcile-tick guard:
+///
+/// - Read the `{id}-psyche` perch's RECORDED pid and pid-probe it DIRECTLY
+///   ([`spt_store::proc::is_process_alive`]) — NOT [`is_perch_alive`], which
+///   short-circuits on the `status=online` stamp and would mask the dead pid.
+/// - [`classify_residency`] then decides: alive ⇒ resident (leave alone);
+///   inside grace ⇒ pending (retry next tick); past grace + dead/missing ⇒ a
+///   host failure → stamp the PARENT perch `psyche_host_error{reason: host
+///   exited within <n>s}`, un-host the driver, and clear the phantom online
+///   nested perch so it is not falsely reported live.
+///
+/// Returns `true` when it un-hosted (so the caller knows the endpoint left the
+/// table). Best-effort: every store call ignores transient errors.
+// [impl->REQ-HAZARD-LIVEHOST-NONRESIDENT]
+fn confirm_residency_or_unhost(set: &Arc<LiveSet>, id: &str) -> bool {
+    let Some(elapsed) = set.hosted_elapsed(id) else {
+        return false; // not tabled (or just un-hosted) — nothing to confirm
+    };
+    let psyche_perch = perch::resolve_perch_path(&format!("{id}-psyche"), ParentHint::Explicit(id));
+    // DIRECT pid probe — the psyche perch is stamped status=online by spawn_psyche,
+    // so is_perch_alive() would read it alive even with a dead pid (that is the
+    // exact masking this closes). read_pid → dead/missing ⇒ not alive.
+    let psyche_alive = spt_store::info::read_pid(&psyche_perch)
+        .map(spt_store::proc::is_process_alive)
+        .unwrap_or(false);
+    let parent_perch = perch::resolve_perch_path(id, ParentHint::Infer);
+    match classify_residency(elapsed, PSYCHE_RESIDENCY_GRACE, psyche_alive) {
+        Residency::Resident => {
+            // RESIDENT past observation: the host truly succeeded. CLEAR any prior
+            // psyche-host-failure stamp so a now-confirmed live agent shows clean.
+            // (The clear was deliberately MOVED here from host_one's spawn-Ok leg:
+            // post-F-010 a successful spawn() is NOT a successful host — only a
+            // live, resident psyche is. Best-effort; status untouched.)
+            // [impl->REQ-HAZARD-LIVEHOST-NONRESIDENT]
+            let _ = spt_store::info::set_psyche_host_error(&parent_perch, None);
+            false
+        }
+        Residency::Pending => false, // inside grace — judge next tick
+        Residency::Failed => {
+            let reason = format!(
+                "host exited within {}s (psyche perch missing/dead pid)",
+                PSYCHE_RESIDENCY_GRACE.as_secs()
+            );
+            // Stamp the PARENT (harness-reachable signal) — status is NOT touched
+            // (liveness stays daemon-authoritative; the SIGNAL is psyche_host_error,
+            // sibling of the v0.8.1 spawn-Err stamp).
+            let _ = spt_store::info::set_psyche_host_error(&parent_perch, Some(&reason));
+            // Clear the phantom online nested perch so it is not falsely reported
+            // live: mark it offline (best-effort; the perch dir may already be gone).
+            let _ = spt_store::info::set_status(&psyche_perch, spt_store::liveness::STATUS_OFFLINE);
+            // Un-host the driver + de-table, then ARM a cooldown so the start-side
+            // does NOT re-spawn the doomed psyche this same sweep (which would clear
+            // the phantom then re-create it + churn). The system goes quiescent in
+            // the failed state until the cooldown expires (then one retry).
+            eprintln!("LIVEHOST_PSYCHE_NONRESIDENT:{id}: {reason}");
+            set.stop_host(id);
+            set.arm_nonresident_cooldown(id);
+            true
+        }
+    }
 }

 /// Spawn the brain's live host: one thread sweeping [`reconcile_once`] at boot
@@ -290,6 +473,42 @@ mod tests {
     use crate::test_home::with_home;
     use std::time::{Duration, Instant};

+    // [unit->REQ-HAZARD-LIVEHOST-NONRESIDENT] the pure residency decision: a live
+    // psyche pid is RESIDENT regardless of elapsed; a dead/missing pid is PENDING
+    // inside the grace (too early to judge — a slow-but-healthy psyche may still be
+    // coming up) and FAILED only once past the grace (the F-009 fast-exit case).
+    #[test]
+    fn residency_classification() {
+        let grace = Duration::from_secs(5);
+        // alive ⇒ resident (even at t=0, even past grace).
+        assert_eq!(
+            classify_residency(Duration::from_millis(0), grace, true),
+            Residency::Resident
+        );
+        assert_eq!(
+            classify_residency(Duration::from_secs(60), grace, true),
+            Residency::Resident
+        );
+        // dead/missing inside grace ⇒ pending (retry next tick, no premature stamp).
+        assert_eq!(
+            classify_residency(Duration::from_secs(1), grace, false),
+            Residency::Pending
+        );
+        assert_eq!(
+            classify_residency(Duration::from_millis(4999), grace, false),
+            Residency::Pending
+        );
+        // dead/missing AT/past grace ⇒ failed (a host that exited within the window).
+        assert_eq!(
+            classify_residency(grace, grace, false),
+            Residency::Failed
+        );
+        assert_eq!(
+            classify_residency(Duration::from_secs(10), grace, false),
+            Residency::Failed
+        );
+    }
+
     // A no-op Psyche summarizer command: spawns + exits 0 (we never depend on its
     // output — the daemon online-stamp is what hosts it, not the pid).
     #[cfg(windows)]


Residency impl is **sound** — direct `is_process_alive` pid-probe (not the masking `is_perch_alive`), clear-moved-to-Resident-arm, de-phantom + cooldown, clean pure unit. The negative path is well-built.

But **note 2 is a real gate condition, confirmed.** The positive E2E (`livehost_bootrace_e2e`, unchanged from v0.8.1) uses a fast-exit fixture (`psychebin --version`) — which is now, by F-010's own definition, a *failed* host. It passes only by killing the brain before the 5s grace fires. That's (a) semantically hollow as a positive, (b) flake-prone on the slow kitsubito runner, and (c) leaves the **Resident path entirely untested at E2E level** (clear-on-resident + don't-false-fail-a-healthy-psyche). Requiring the long-lived fixture todlando offered.

Quick confirm of the negative E2E fixture/asserts, then I issue the ruling.


cd /c/Users/decid/Documents/projects/spt-core
echo "=== nonresident E2E: fixture + key asserts ==="
grep -nE "exit|command=|psychebin|assert|psyche_host_error|status|STATUS_ONLINE|brain.ready|daemon.*run|spawn|fn .*\(\)" crates/spt/tests/livehost_nonresident_e2e.rs | head -50

Skim nonresident E2E fixture + asserts


=== nonresident E2E: fixture + key asserts ===
2://! live agent goes `status=online` and its Psyche binary EXISTS and SPAWNS Ok, but
3://! the child EXITS FAST (code 2 — the F-009 bad-argv shape). `spawn_psyche` detaches
4://! (handle dropped), so the v0.8.1 spawn-Err stamp never fires; the residual masking
5://! gap is: the nested `{id}-psyche` info.json is written `status=online` with a
6://! real-but-DEAD pid while the PARENT carries NO `psyche_host_error` (perri's F-010:
11://! PARENT `psyche_host_error{reason: host exited within <n>s}` AND clears the phantom
14://! Sibling of `livehost_psyche_fail_e2e.rs` (the spawn-Err case). Same real
15://! production path — `spt daemon run` broker → supervised `spt daemon brain` CHILD →
19://! clap exits 2 within ~1s — the detached spawn() returns Ok, then the child dies.
23://! stamp wait is a bounded poll, so a regression is a clean assert, never a hang. All
24://! spawned processes (broker + supervised brain + listen) are reaped — scoped to OUR
28://! psyche argv (`psychebin definitely-not-a-real-subcommand`) is built by the new
30://! site, so a regression in that reorder shows up here as a mis-spawned psyche.
43:/// Best-effort cross-platform kill of a single pid tree we spawned — NEVER a
59:    std::thread::spawn(move || {
67:/// Read the brain `pid` out of a `brain.ready` body (self-describing JSON).
76:/// Poll `brain.ready` for a pid, up to `budget`. `None` if it never appears.
91:fn online_live_agent_with_fast_exiting_psyche_stamps_parent_and_clears_phantom() {
108:    //    is a BARE token (`psychebin`) naming a REAL binary that ships in the install
110:    //    + SPAWNS Ok but clap exits 2 within ~1s — the exact F-010 residual: spawn()
116:    let psyche_bin = src.join(format!("psychebin{exe_suffix}"));
118:    assert!(psyche_bin.is_file(), "the psyche fixture must exist in the install dir");
124:    // `psychebin not-a-subcommand` → clap "unrecognized subcommand" → exit code 2,
125:    // within ~1s. A REAL spawn that dies fast (the bad-argv shape), NOT a spawn-Err.
130:         [session.psyche_init]\ncommand='psychebin definitely-not-a-real-subcommand'\ncwd=\"{{psyche_dir}}\"\nkeys=[]\n"
136:    // Sanity: the bare token DOES resolve to a real file (so the spawn succeeds — the
140:            spt_runtime::runtime::resolve_program_in_dir("psychebin", Path::new(&rec.source_dir))
142:    assert!(
147:        "the bare psychebin MUST resolve to a real file (got {resolved:?})"
150:    // ── (3) Launch the REAL daemon broker PROCESS (spawns a supervised brain CHILD). ──
155:        .args(["daemon", "run"])
159:        .spawn()
160:        .expect("spawn spt daemon run (broker process)");
162:    let brain_pid = match wait_for_ready_pid(&home.path().join("brain.ready"), Duration::from_secs(30))
169:                "PRECONDITION FAILED: the real broker never spawned a ready brain child.\n{}",
177:    let id = "fastexit-agent";
192:                "sid-fastexit",
197:    if !seed.status.success() {
224:        .spawn()
225:        .expect("spawn the persistent api listen (harness-hosted live agent)");
231:            let st = spt_store::info::read_info(&self_perch).and_then(|i| i.status);
232:            if st.as_deref() == Some(spt_store::liveness::STATUS_ONLINE) {
253:    // ── (5) THE ASSERTION: the psyche spawns Ok but dies fast, so RESIDENCY fails
255:    //    `psyche_host_error` (reason non-empty, mentions exit/within) AND the nested
256:    //    `{id}-psyche` perch must NOT be left status=online (the phantom is cleared).
264:        if let Some(e) = spt_store::info::read_info(&self_perch).and_then(|i| i.psyche_host_error) {
273:    let psyche_status = spt_store::info::read_info(&psyche_perch).and_then(|i| i.status);
274:    let phantom_online = psyche_status.as_deref() == Some(spt_store::liveness::STATUS_ONLINE);
275:    // Parent status stays daemon-authoritative online — the residency stamp never
276:    // de-stamps it (the SIGNAL is psyche_host_error, not a status flip).


Negative E2E is excellent (real broker→brain-child, `psychebin definitely-not-a-real-subcommand` → clap exit 2 ~1s, asserts parent stamped + phantom de-onlined + parent stays online, and exercises F-009's fill order). Build is high-quality. **Only gate gap = note 2.** Issuing the conditional ruling — one re-spin.


cd /c/Users/decid/Documents/projects/spt-core
OWL="C:/Users/decid/.claude/plugins/cache/cplugs/spt/1.11.25/owl.exe"
"$OWL" send todlando doyle <<'EOF'
GATE on PR #18 = CONDITIONAL PASS, one re-spin. Code-read is strong: F-009 fill_template_tokens (tokenize-then-fill, value=1 element, errors preserved, callers rewired) ✓; F-010 residency (DIRECT is_process_alive probe not is_perch_alive, clear-moved-to-Resident-arm, de-phantom + cooldown, pure classify unit) ✓; the negative real-daemon E2E (psychebin bad-subcommand → exit2 → parent stamped + phantom de-onlined, exercises the new fill order) ✓. Notes 1 & 3 resolved below. Note 2 is a REQUIRED re-spin.

NOTE 3 (clear-on-Resident move): ACCEPTED, no veto — a detached spawn-Ok isn't host success; only a live resident psyche is. Correct.

NOTE 1 (title <code>): you're right, the exit code is unobtainable across the detached/fire-and-forget seam (handle dropped; reaping it crosses the daemon-authoritative-via-perch seam — correctly rejected). I own the title — reword its reason clause to MATCH what the impl actually emits: change `host exited <code> within <n>s` → `host not resident within <n>s (psyche perch missing/dead pid)`. Apply it yourself in the re-spin commit (no branch race), I've ratified the wording.

NOTE 2 (positive E2E) — REQUIRED FIX: the v0.8.1 positive E2E (livehost_bootrace_e2e) uses a FAST-EXIT fixture (psychebin --version). Post-F-010 that is, by your own definition, a FAILED host — it passes ONLY by killing the brain before the 5s grace fires. Two problems: (a) it's a hollow positive + flake-prone on the slow kitsubito runner (any scheduling slip past 5s before the phe-read → residency-Failed stamps → assert flips RED); (b) the RESIDENT path now has ZERO E2E coverage — nothing proves residency-confirm CONFIRMS a healthy psyche, clears the stamp, and does NOT false-fail a slow-but-alive one. Fix: give the positive E2E a LONG-LIVED resident psyche fixture (stays alive well past PSYCHE_RESIDENCY_GRACE — e.g. a binary that sleeps ~20s), then WAIT past grace+one reconcile tick (~7-8s) and assert: {id}-psyche pid still ALIVE (Resident) AND parent psyche_host_error is None. That kills the timing race AND gives the Resident-path proof. Bonus (nice-to-have, not required): induce a transient stamp first, then prove the resident host CLEARS it. Bounded + pid-scoped reaps as usual (reap the resident psyche pid too).

Re-run the FULL gate on re-spin (clippy --workspace -D warnings, traceable EXIT0, docs-drift, units + BOTH real-daemon E2Es: the new resident-positive + the nonresident-negative). Ping me PR-ready and I re-gate. Everything else is cleared — this is the one blocker.
EOF
echo "ruling rc=$?"

Issue conditional gate ruling to todlando


➜ SENT:todlando
ruling rc=0


Conditional gate ruling sent. The gate **caught a real issue** — not a rubber-stamp:

**PR #18 code-read PASS** on F-009 (tokenize-then-fill, value=1 argv element, errors preserved) and F-010 residency (direct pid-probe not the masking perch-status, de-phantom + cooldown, clear-moved-to-Resident). Negative E2E solid.

**One required re-spin (note 2):** the v0.8.1 positive E2E uses a fast-exit fixture — which F-010 now *defines as a failed host*. It passes only by racing the 5s grace (kill brain first), so it's (a) flake-prone on slow kitsubito and (b) leaves the **Resident path with zero E2E coverage**. Required todlando to swap in a long-lived resident fixture + assert resident-past-grace + no error. Closes the race AND the coverage hole.

Resolved the other two gater notes: dropped the unobtainable `<code>` from the REQ title (detached handle, exit code unreachable); accepted clear-on-Resident.

Holding for todlando's re-spin → re-gate → deployah ships v0.8.2 (counter 18) → ping perri. Listener up.


<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\.ccs\instances\bigscreen\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


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


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


I'll download my Psyche's current context first, then commune only the delta.


LIVE="${LIVE:-C:/Users/decid/.claude/plugins/cache/cplugs/spt/1.11.25/live.exe}"; echo "LIVE=$LIVE"; "$LIVE" psyche-download doyle 2>&1 | head -80

Download current Psyche context


LIVE=C:/Users/decid/.claude/plugins/cache/cplugs/spt/1.11.25/owl.exe live
/usr/bin/bash: line 4: C:/Users/decid/.claude/plugins/cache/cplugs/spt/1.11.25/owl.exe live: No such file or directory


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


ls "C:/Users/decid/.claude/plugins/cache/cplugs/spt/1.11.25/" 2>&1 | grep -iE "live|owl"

Locate live binary


owl.exe


$LIVE = `owl.exe live` (subcommand form). Invoking correctly.


OWL="C:/Users/decid/.claude/plugins/cache/cplugs/spt/1.11.25/owl.exe"; "$OWL" live psyche-download doyle 2>&1 | head -90

Download Psyche context via owl live


<owl_messages>
<owl_message from="todlando" priority="high">todlando — v0.8.2 RE-SPIN pushed, PR #18 @0aad7b9. Both your notes addressed; CI re-running.

NOTE 1 (reason reword, ratified): applied across all 3 spots — livehost.rs Failed-arm reason → "host not resident within 5s (psyche perch missing/dead pid)" (grace via PSYCHE_RESIDENCY_GRACE); REQ-HAZARD-LIVEHOST-NONRESIDENT title reason clause matched; nonresident E2E assertion → contains "not resident" && "within". Verified the negative E2E stamps exactly that string.

NOTE 2 (resident-positive E2E, REQUIRED): reworked livehost_bootrace_e2e — fixture now `psychebin ready <throwaway>` (LONG-LIVED: spt-copy `ready` = persistent blocking listener, still bare-token-resolved from install dir = REQ-INSTALL-11 fidelity, throwaway id namespaced off the live agent). New residency-confirm phase: waits ~13s (past 5s grace + one 5s reconcile tick) → read_pid → is_process_alive (the impl's own probe) → asserts BOTH the {id}-psyche pid STILL ALIVE (Resident) AND parent psyche_host_error None. Kills the timing race AND gives the Resident-path proof that was missing. Reaps the resident pid scoped. You were right — my timing-margin acceptance was the weaker call; the Resident path now has real coverage.

FULL GATE GREEN (re-ran on the re-spin tree): clippy --workspace --all-targets -D warnings clean · traceable EXIT0 · reworked positive E2E pass (psyche_alive=true, phe=None, 21s) · nonresident negative pass (reason = ratified string) · v0.8.1 missing-binary regression pass · livehost units (incl residency_classification) pass · xtask OK.

Over to you to re-gate. deployah ships v0.8.2 (counter 18) → you ping perri. I'll watch CI + report.</owl_message>
</owl_messages>

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


<memformat>
<?xml version="1.0"?>
<memformat version="1">
<!-- Changelog: v1 - initial seed -->
<topics>
  <topic name="events">Notable or unusual events and requests</topic>
  <topic name="interactions">Agent interactions -- who, roles, contributions</topic>
  <topic name="reflections">Thoughts on recent work -- what went well, what was tricky</topic>
  <topic name="ideas">Ideas to raise with the user</topic>
  <topic name="improvements">Further improvement ideas for recent problem spaces</topic>
  <topic name="timeline">What happened when, timing, sequencing</topic>
  <topic name="blockers">Current blockers or concerns</topic>
  <topic name="focus">Current focus and immediate next steps</topic>
</topics>
</memformat>
</memformat>

<current machine="HFENDULEAM" project="spt-core" branch="v0.8.2-templating-residency" head_sha="0aad7b946b1c291e9dae8dc768c78498287b5f94" head_subject="v0.8.2 re-spin: resident-positive E2E + residency reason reword (gater n…" commits_since="0" commits_unpulled="0"/>
DELTA — v0.8.1 SHIPPED + perri UNBLOCKED; v0.8.2 delegated & awaiting todlando PR-ready (2026-06-17).

v0.8.1 (counter 17) PUBLIC + perri re-validated: her live-agent-Psyche parity proof PASSED, REQ-INSTALL-11 + F-006 proven, psyche resident. The harness-reachable psyche_host_error signal worked. See [[v081-published]] [[v081-livehost-bootrace]].

perri's v0.8.1 dogfood (argv-capture-instrumented) surfaced TWO follow-on spt-core defects → both minted + delegated v0.8.2 to todlando:
- **F-009** → REQ-HAZARD-TEMPLATE-ARGV-FILL: runtime.rs fill_template(:94)→tokenize(:122) fills {key} into the command STRING then whitespace-splits → multi-word {psyche_prompt} became argv[6..12] (7 stray tokens) → her psyche child exited 2 → phantom hosted perch. FIX = tokenize TEMPLATE first, then fill each token (filled value = always one argv element; kills split + quote/`;` injection).
- **F-010** → REQ-HAZARD-LIVEHOST-NONRESIDENT: the v0.8.1 fix stamps psyche_host_error only on spawn() Err, NOT on spawn-then-immediate-exit → nested {id}-psyche perch online with a DEAD pid, parent clean = residual masking. FIX = residency-confirm within N sec → stamp on non-resident. Sibling of REQ-HAZARD-LIVEHOST-BOOT-RACE.
- MINOR (flagged to todlando, investigate-not-scope-creep): peer-pump STALL ~7-8min after daemon start (last-tick 455s/520s), didn't block hosting. Likely REQ-DAEMON-9/brain-loop.

STATE: branch v0.8.2-templating-residency @3cd5e6f, 2 REQs minted, pushed, traceable EXIT=0. Delegated to todlando (self-drives, plans W/T, pings doyle at PR-ready). Folded held FLAKE-LEDGER rider into change.

**perri's re-validation plan recorded:** On v0.8.2 ping, revert greedy --prompt to strict single-value parser, re-run live-relay-int psyche leg, confirm tokenize-then-fill hosts + psyche_host_error stamps on forced fast-exit. She's carrying on, not blocked.

MY NEXT (when todlando pings PR-ready): gate (clippy --workspace -D warnings, traceable EXIT0, docs-drift, units + F-010 real-daemon early-exit E2E) → deployah ships v0.8.2 (counter 18) → ping perri for re-validation per her plan.

HOLDING for todlando PR-ready. Listener up.<project-context-resolved name="spt-core"/>
v0.8.2 in-flight (delegated to todlando, awaiting PR-ready). REQs F-009 (templating argv-fill) + F-010 (residency-confirm gap) minted & pushed. perri has concrete re-validation TODO for v0.8.2 delivery: revert --prompt workaround, test the real fix (tokenize-then-fill) + psyche_host_error on fast-exit. doyle gates next, then deployah ships counter 18.


todlando re-spun PR #18 @0aad7b9 (both gate notes addressed) — acknowledging, then communing the delta. The re-gate itself defers to next session per your wrap-up.


cd /c/Users/decid/Documents/projects/spt-core
OWL="C:/Users/decid/.claude/plugins/cache/cplugs/spt/1.11.25/owl.exe"
"$OWL" send todlando doyle <<'EOF'
Got the re-spin @0aad7b9 — both notes look properly addressed from your report (resident-positive E2E with the ~13s past-grace wait + is_process_alive Resident assert + phe None = the Resident-path proof I wanted; reason reword applied 3 spots + ratified title clause). Good call owning the timing-margin weakness.

Heads-up on cadence: my operator is wrapping this session — I'll do the RE-GATE at the top of next session (code-read the reworked livehost_bootrace_e2e + confirm CI green BOTH runners on 0aad7b9), then hand deployah the v0.8.2 GO (counter 18) → ping perri. No action needed from you; keep watching CI and flag if 0aad7b9 goes red (rerun --failed if it's the kitsubito notify-shell curl flake again). Solid turnaround.
EOF
echo "ack rc=$?"

ACK todlando re-spin + cadence heads-up


➜ SENT:todlando
ack rc=0


Now composing the commune — delta only (the Psyche knows the delegation; it does NOT yet know the gate→conditional-pass→re-spin arc or the precise resume step). Two-slice envelope (project resolved = `spt-core`).


Write

<live-context>
DELTA — v0.8.2 GATED conditionally → todlando RE-SPUN → awaiting my RE-GATE (session wrap, resume next session).

Since the last download, the v0.8.2 arc moved from "awaiting PR-ready" to "re-spin PR-ready, awaiting re-gate":
- todlando submitted PR #18 (v0.8.2, F-009 + F-010). I GATED it — code-read PASS on F-009 (fill_template_tokens: tokenize-then-fill, value=1 argv element, errors preserved, callers rewired) and F-010 residency (DIRECT is_process_alive pid-probe NOT the masking is_perch_alive, clear-moved-to-Resident-arm, de-phantom + cooldown, pure classify_residency unit). Negative real-daemon E2E solid.
- I caught ONE real issue → CONDITIONAL PASS (not rubber-stamp): the v0.8.1 positive E2E used a FAST-EXIT fixture, which F-010 now DEFINES as a failed host — it passed only by racing the 5s grace (kill brain first) = hollow + flake-prone on slow kitsubito AND left the RESIDENT path with zero E2E coverage. Required todlando swap in a long-lived resident fixture + assert resident-past-grace + phe None. Resolved 2 other gater notes: dropped the unobtainable `<code>` from the REQ title (detached spawn handle dropped → exit code unreachable; reaping it crosses the daemon-authoritative-via-perch seam, correctly rejected), and accepted clear-on-Resident.
- todlando RE-SPUN @0aad7b9: reworked livehost_bootrace_e2e (fixture `psychebin ready <throwaway>` = long-lived blocking listener, bare-token install-dir-resolved for REQ-INSTALL-11 fidelity; waits ~13s past grace+tick → is_process_alive → asserts {id}-psyche pid STILL ALIVE Resident AND parent phe None = the missing Resident-path proof). Reason reword applied 3 spots. todlando reports full gate green local (clippy -D warnings, traceable EXIT0, both real-daemon E2Es, units incl residency_classification, xtask).

IMMEDIATE NEXT STEP (resume here): RE-GATE the re-spin @0aad7b9 — (1) code-read the reworked livehost_bootrace_e2e to confirm it genuinely waits past PSYCHE_RESIDENCY_GRACE + one reconcile tick, probes is_process_alive, and asserts Resident + phe None (timing-robust, not racing); (2) verify CI GREEN BOTH runners on 0aad7b9 (watch for the kitsubito "Build notify-shell" crates.io curl flake → `gh run rerun --failed` clears it, NOT code). On green → hand deployah the v0.8.2 RELEASE GO (counter 18, PATCH, vetted version-framed changelog) → on publish + hashes, PING PERRI for re-validation.

perri's re-validation plan (recorded, she's not blocked): on my v0.8.2 ping she reverts her greedy --prompt back to a STRICT single-value parser (so spt-core's tokenize-then-fill is what's under test, not her workaround), re-runs ci/psyche/live-relay-int.sh psyche leg, confirms tokenize-then-fill hosts + psyche_host_error stamps on a forced fast-exit. She's meanwhile doing her v0.2.0 per-OS re-release + cplugs v0.1.2.

Roles holding: todlando=executor (built + re-spun, self-drives, watching CI) · deployah=publisher (11 clean legs, ships counter 18 on my GO) · perri=adapter builder/validator (needs replies to continue). MINOR open: peer-pump STALL ~7-8min after daemon start (perri-reported, non-blocking) — flagged todlando to investigate while in brainproc, NOT scope-creeping v0.8.2.
</live-context>
<project-context>
spt-core v0.8.2 IN-FLIGHT (re-gate pending) — two perri-dogfood follow-up defect fixes, branch v0.8.2-templating-residency @0aad7b9 (off main = v0.8.1, counter 17 PUBLIC). Both REQs minted registry-first + activated [impl,unit,int]:

- REQ-HAZARD-TEMPLATE-ARGV-FILL (F-009): command-template substitution must fill argv ELEMENTS, not a re-tokenized string. Was fill_template(into string)→tokenize(split) so a multi-word/quote/`;` {key} value split/injected (perri's {psyche_prompt} → argv[6..12]). FIX shipped on branch: fill_template_tokens = tokenize TEMPLATE first, then fill each token → value is always exactly one argv element. Applies to every [session.<role>] template (psyche_init/extractor/notif); digest dodged it only via single-token fills.
- REQ-HAZARD-LIVEHOST-NONRESIDENT (F-010): a daemon-hosted Psyche that spawns then EXITS IMMEDIATELY is a host failure. The v0.8.1 psyche_host_error stamped only on spawn() Err; a detached spawn-Ok-then-die left a phantom nested {id}-psyche perch online with a dead pid + clean parent. FIX on branch: reconcile-tick residency-confirm (DIRECT is_process_alive pid-probe past a 5s grace) → Failed stamps parent psyche_host_error{reason:"host not resident within 5s (psyche perch missing/dead pid)"} + de-onlines the phantom + un-hosts + arms a 5s cooldown (no spawn-storm); the stamp CLEAR moved from spawn-Ok to the confirmed-Resident arm (a spawn-Ok is not a host success). Sibling of REQ-HAZARD-LIVEHOST-BOOT-RACE (v0.8.1).

These build directly on v0.8.1 (counter 17): the harness-reachable psyche_host_error signal on the Self perch info.json, surfaced by `spt endpoint list`/`whoami`. v0.8.0=16, v0.8.1=17, v0.8.2→18.

END GOAL (unchanged): spt-claude-code (perri's repo) is the v1 ACCEPTANCE PROOF that spt-core is a harness-independent agent-ecosystem core, proving parity with legacy claude_skill_owl (which retires once parity holds). perri's dogfood IS the parity validation: v0.8.1 unblocked her live-agent-Psyche proof (REQ-INSTALL-11 + F-006 proven); v0.8.2 closes the two residual defects her instrumented run surfaced (F-009 templating, F-010 residency-masking). The loop is healthy: perri finds gap (runtime-grounded) → doyle designs/mints REQ → todlando builds → doyle gates → deployah ships → perri re-validates.
</project-context>


<owl_messages>
<owl_message from="todlando" priority="high">todlando — ACK. Watching CI on 0aad7b9 (run 27658258329). If it goes red on the kitsubito "Build notify-shell" crates.io curl flake (FLAKE-LEDGER #8), I'll `gh run rerun --failed` + flag; any real failure I'll diagnose + report. Otherwise green stands for your next-session re-gate. Nothing else pending on my side — clean handoff. Thanks for the thorough gate + the Resident-path catch.</owl_message>
</owl_messages>

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


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

<live-context>
DELTA — v0.8.2 GATED conditionally → todlando RE-SPUN → awaiting my RE-GATE (session wrap, resume next session).

Since the last download, the v0.8.2 arc moved from "awaiting PR-ready" to "re-spin PR-ready, awaiting re-gate":
- todlando submitted PR #18 (v0.8.2, F-009 + F-010). I GATED it — code-read PASS on F-009 (fill_template_tokens: tokenize-then-fill, value=1 argv element, errors preserved, callers rewired) and F-010 residency (DIRECT is_process_alive pid-probe NOT the masking is_perch_alive, clear-moved-to-Resident-arm, de-phantom + cooldown, pure classify_residency unit). Negative real-daemon E2E solid.
- I caught ONE real issue → CONDITIONAL PASS (not rubber-stamp): the v0.8.1 positive E2E used a FAST-EXIT fixture, which F-010 now DEFINES as a failed host — it passed only by racing the 5s grace (kill brain first) = hollow + flake-prone on slow kitsubito AND left the RESIDENT path with zero E2E coverage. Required todlando swap in a long-lived resident fixture + assert resident-past-grace + phe None. Resolved 2 other gater notes: dropped the unobtainable `<code>` from the REQ title (detached spawn handle dropped → exit code unreachable; reaping it crosses the daemon-authoritative-via-perch seam, correctly rejected), and accepted clear-on-Resident.
- todlando RE-SPUN @0aad7b9: reworked livehost_bootrace_e2e (fixture `psychebin ready <throwaway>` = long-lived blocking listener, bare-token install-dir-resolved for REQ-INSTALL-11 fidelity; waits ~13s past grace+tick → is_process_alive → asserts {id}-psyche pid STILL ALIVE Resident AND parent phe None = the missing Resident-path proof). Reason reword applied 3 spots. todlando reports full gate green local (clippy -D warnings, traceable EXIT0, both real-daemon E2Es, units incl residency_classification, xtask).

IMMEDIATE NEXT STEP (resume here): RE-GATE the re-spin @0aad7b9 — (1) code-read the reworked livehost_bootrace_e2e to confirm it genuinely waits past PSYCHE_RESIDENCY_GRACE + one reconcile tick, probes is_process_alive, and asserts Resident + phe None (timing-robust, not racing); (2) verify CI GREEN BOTH runners on 0aad7b9 (watch for the kitsubito "Build notify-shell" crates.io curl flake → `gh run rerun --failed` clears it, NOT code). On green → hand deployah the v0.8.2 RELEASE GO (counter 18, PATCH, vetted version-framed changelog) → on publish + hashes, PING PERRI for re-validation.

perri's re-validation plan (recorded, she's not blocked): on my v0.8.2 ping she reverts her greedy --prompt back to a STRICT single-value parser (so spt-core's tokenize-then-fill is what's under test, not her workaround), re-runs ci/psyche/live-relay-int.sh psyche leg, confirms tokenize-then-fill hosts + psyche_host_error stamps on a forced fast-exit. She's meanwhile doing her v0.2.0 per-OS re-release + cplugs v0.1.2.

Roles holding: todlando=executor (built + re-spun, self-drives, watching CI) · deployah=publisher (11 clean legs, ships counter 18 on my GO) · perri=adapter builder/validator (needs replies to continue). MINOR open: peer-pump STALL ~7-8min after daemon start (perri-reported, non-blocking) — flagged todlando to investigate while in brainproc, NOT scope-creeping v0.8.2.
</live-context>
<project-context>
spt-core v0.8.2 IN-FLIGHT (re-gate pending) — two perri-dogfood follow-up defect fixes, branch v0.8.2-templating-residency @0aad7b9 (off main = v0.8.1, counter 17 PUBLIC). Both REQs minted registry-first + activated [impl,unit,int]:

- REQ-HAZARD-TEMPLATE-ARGV-FILL (F-009): command-template substitution must fill argv ELEMENTS, not a re-tokenized string. Was fill_template(into string)→tokenize(split) so a multi-word/quote/`;` {key} value split/injected (perri's {psyche_prompt} → argv[6..12]). FIX shipped on branch: fill_template_tokens = tokenize TEMPLATE first, then fill each token → value is always exactly one argv element. Applies to every [session.<role>] template (psyche_init/extractor/notif); digest dodged it only via single-token fills.
- REQ-HAZARD-LIVEHOST-NONRESIDENT (F-010): a daemon-hosted Psyche that spawns then EXITS IMMEDIATELY is a host failure. The v0.8.1 psyche_host_error stamped only on spawn() Err; a detached spawn-Ok-then-die left a phantom nested {id}-psyche perch online with a dead pid + clean parent. FIX on branch: reconcile-tick residency-confirm (DIRECT is_process_alive pid-probe past a 5s grace) → Failed stamps parent psyche_host_error{reason:"host not resident within 5s (psyche perch missing/dead pid)"} + de-onlines the phantom + un-hosts + arms a 5s cooldown (no spawn-storm); the stamp CLEAR moved from spawn-Ok to the confirmed-Resident arm (a spawn-Ok is not a host success). Sibling of REQ-HAZARD-LIVEHOST-BOOT-RACE (v0.8.1).

These build directly on v0.8.1 (counter 17): the harness-reachable psyche_host_error signal on the Self perch info.json, surfaced by `spt endpoint list`/`whoami`. v0.8.0=16, v0.8.1=17, v0.8.2→18.

END GOAL (unchanged): spt-claude-code (perri's repo) is the v1 ACCEPTANCE PROOF that spt-core is a harness-independent agent-ecosystem core, proving parity with legacy claude_skill_owl (which retires once parity holds). perri's dogfood IS the parity validation: v0.8.1 unblocked her live-agent-Psyche proof (REQ-INSTALL-11 + F-006 proven); v0.8.2 closes the two residual defects her instrumented run surfaced (F-009 templating, F-010 residency-masking). The loop is healthy: perri finds gap (runtime-grounded) → doyle designs/mints REQ → todlando builds → doyle gates → deployah ships → perri re-validates.
</project-context>


Commune written (Self listener ingests + deletes it). todlando's final ACK — quick close-out, then we're wrapped.


Bash

cd /c/Users/decid/Documents/projects/spt-core
OWL="C:/Users/decid/.claude/plugins/cache/cplugs/spt/1.11.25/owl.exe"
"$OWL" send todlando doyle <<'EOF'
Ack — clean handoff. You hold CI on 0aad7b9 (rerun --failed on the notify-shell curl flake, diagnose any real red), I re-gate at next session start → deployah GO → perri. Thanks for the tight build + re-spin. Out.
EOF
echo "rc=$?"

Close-out ack to todlando


➜ SENT:todlando
rc=0


Communed + session wrapped. Summary for the record:

**Commune sent** (delta only — Psyche already had the delegation; new delta = the gate→conditional-pass→re-spin arc + precise resume step). Two-slice envelope, project `spt-core`.

**Where things stand:**
- **v0.8.1** (counter 17): PUBLIC, perri-validated. ✅
- **v0.8.2**: branch `v0.8.2-templating-residency` @0aad7b9 (re-spin) PR-ready.
  - F-009 (templating argv-fill) + F-010 (residency-confirm) both built, gated conditionally, re-spun.
  - My gate **caught a real issue** (note 2: hollow/flaky fast-exit positive E2E + uncovered Resident path) → todlando fixed it (long-lived resident fixture, waits past grace, asserts Resident + no error).

**Immediate next step (resume):** RE-GATE @0aad7b9 — code-read the reworked positive E2E + confirm CI green both runners (watch the kitsubito notify-shell curl flake; `gh run rerun --failed` clears it) → hand deployah the v0.8.2 GO (counter 18) → ping perri for her re-validation (she reverts `--prompt` to strict so the real fix is under test).

**End goal:** spt-claude-code = v1 parity proof of spt-core as harness-independent; legacy owl retires on parity. perri's dogfood is the live validation; v0.8.2 closes the last two residual defects she surfaced.

todlando holds CI till next session. deployah on call. Listener up. Out.