1	---
2	name: v081-livehost-bootrace
3	description: "v0.8.1 task — fix REQ-HAZARD-LIVEHOST-BOOT-RACE: net-less node, live agent goes online but daemon never hosts Psyche (no LIVEHOST_PSYCHE). doyle delegation, I own waves+execution. Verified root-cause-so-far + wave plan + the non-negotiable real-daemon E2E gate."
4	metadata:
5	  node_type: memory
6	  type: project
7	  originSessionId: 8350d43e-fe2d-4e24-9999-eecbcc6d3a50
8	---
9
10	**v0.8.1 livehost boot-race** — doyle delegation (operator-directed), I own W/T planning + execution, doyle gates + drives release (counter 17). Branch `v0.8.1-livehost-bootrace` off main (v0.8.0), base @2aab895 mints `REQ-HAZARD-LIVEHOST-BOOT-RACE` (required_stages=[]; I activate [impl,unit,int] per wave, same-commit tags). PR → ping doyle at PR-ready (self-drive until then). See [[v080-merge-reconciliation]] (the v0.8.0 livehost reapply this builds on).
11
12	**DEFECT (perri v0.8.0 dogfood, real bug):** harness-hosted live agent (`spt api seed` + `spt api listen <id>`) goes status=online but NEVER gets a daemon-hosted Psyche on a net-less/unpaired/peer-pump-STALLED node. Host-predicate fully passes (state=live_agent, status=online, adapter resolvable, psyche_init present) yet `reconcile_once` never emits `LIVEHOST_PSYCHE`. "peer pump: STALLED" reappears after every restart.
13
14	**VERIFIED from source (brainproc.rs:159-255):** run_brain :161 `connect_retry(&broker_socket)?` connects to the LOCAL BROKER (not net); broker binds its IPC listener INDEPENDENT of net (broker.rs:575 bind_in_with_net). So net-less the broker-connect succeeds → :230 `spawn_live_host` IS reached (unconditional, own thread, before the heartbeat loop). The heartbeat loop (:233) net-gates ONLY the consumers (dispatcher/peer pump) via consumer_gate, NOT livehost. ⇒ **H1 (brain starved before :230 by net) REFUTED by source** — the live reconcile thread runs net-less. (Caveat NOT yet verified: whether the BROKER itself spawns the brain child net-less, or the broker's own boot stalls before binding — H3/broker-side, open.)
15
16	**Explore report (afe7959) — TREAT WITH CAUTION, its mechanism is shaky:** concluded "registry load-order race → install_dir=None → spawn_psyche fails." INTERNALLY INCONSISTENT: `resolve_option_in(adapter)` and the install_dir `find` use the SAME `registered` slice + same parent — can't succeed-then-miss. And a first-tick registry miss self-heals next tick (5s) ≠ the PERSISTENT defect. So the specific theory is likely WRONG. doyle warned "do NOT presume (perri refuted doyle's first guess)."
17
18	**TRUE failure = runtime-observable only.** Real candidates: (a) `spawn_psyche` persistently fails → `LIVEHOST_PSYCHE_FAIL` every tick — most likely REQ-INSTALL-11 psyche-binary-not-found (perri's blocked proof, copy-mode binary absent from install_dir+PATH); (b) H3 broker doesn't spawn brain child net-less; (c) reconcile predicate fails on a field. doyle's brief: "check LIVEHOST_PSYCHE_FAIL."
19
20	**WAVE PLAN:**
21	- **W1 — reproduce via the non-negotiable gate artifact:** a REAL detached-daemon E2E (spawn the real brain CHILD via `spt daemon`, NOT in-process; existing live_firsthost_e2e uses in-process daemon + hand-writes perch, so the production broker→brain-child→run_brain→spawn_live_host→reconcile path was NEVER E2E-tested). `api seed` + `api listen` a live agent on a net-less/unpaired node → assert `LIVEHOST_PSYCHE`. Watch it FAIL → read the ACTUAL failure (LIVEHOST_PSYCHE_FAIL text / behavior). Ground the fix in THIS, not the report. (Look at crates/spt-daemon/tests/daemon_lifecycle_real_brain.rs for real-brain-spawn infra.)
22	- **W2 — fix** per observed failure: live-host reconcile runs net-INDEPENDENTLY + survives the boot-race, self-healing like REQ-DAEMON-9 (dispatcher/peer-pump boot-race self-heal in broker.rs attach_net). Likely also fix REQ-INSTALL-11 psyche binary resolution if that's the observed failure.
23	- **W3 — green + activate + gate.**
24
25	**WHY IT MATTERS (doyle):** unblocks perri's last 2 parity proofs — REQ-INSTALL-11 install-dir binary resolution (psyche must actually SPAWN to exercise it) + F-006 interim PATH-copy.
26
27	**GATE (pre-flight before pinging doyle):** `clippy --workspace --all-targets -D warnings` (EXACT CI invocation, NOT -p) · `traceable-reqs check` EXIT=0 · docs-drift `xtask gen` if any CLI touched · the real-daemon E2E GREEN. Then doyle code-reads + verifies the E2E spawns the brain CHILD + gates → deployah ships v0.8.1 (counter 17) → doyle pings perri.
28
29	**W1 DONE — real-daemon E2E built + bug DID NOT reproduce as a net-race (RUNTIME-PROVEN reframe):** `crates/spt/tests/livehost_bootrace_e2e.rs` (`netless_online_live_agent_is_hosted_by_the_real_daemon_brain`) launches the REAL `spt daemon run` broker → real `spt daemon brain` CHILD, forces net-less (corrupt node.key → NODE_KEY_FAIL no-retry), real `api seed`+`listen` establish→online, REAL psyche binary (`psychebin`=copy of spt) by BARE token ONLY in install source_dir (exercises REQ-INSTALL-11), bounded 25s psyche-perch poll. **PASSES net-less** — `LIVEHOST_PSYCHE` fires with net fully starved. Negative control (binary removed) → `LIVEHOST_PSYCHE_FAIL: ... program not found` per 5s tick. So:
30	- **H1 net-gating REFUTED by RUNTIME** (not just source): spawn_live_host @brainproc:230 unconditional, before the heartbeat net-gate (consumer_gate gates ONLY dispatcher/peer-pump). Net-independence ALREADY HOLDS — the E2E locks it in; NO behavior fix needed there.
31	- **install-dir resolution CORRECT + CONSISTENT:** github/release registration sets `source_dir = adapters/_github/<safe>/` (cli.rs:5470,5487; runtime.rs:421-424), copy-mode source_dir = manifest dir; livehost (rec.source_dir) AND api seam (resolve_ctx_manifest, mod.rs:501) use the SAME source_dir. The W1 "divergence" candidate REFUTED. resolve_program_in_dir (runtime.rs:434) works (E2E proves).
32	- **REAL spt-core gap = SILENT-FAILURE MASKING (the REQ title names it):** spawn_psyche Err → `LIVEHOST_PSYCHE_FAIL` is eprintln-ONLY on the brain's INVISIBLE stderr (brainproc.rs:927-948 brain inherits broker stderr → foreground `spt daemon run`, unseen by a harness watching only `api listen` stdout). Agent stays state=live_agent+alive=true+status=online (cmd_listen stamps unconditionally, startup.rs:284) with NO Psyche, NO harness-visible error. = perri's exact diagnostic.
33
34	**PROPOSED v0.8.1 SCOPE (pending doyle confirm — premise shifted):** (1) KEEP the real-daemon E2E as the non-negotiable gate artifact; re-tag its evidence to `[int->REQ-HAZARD-LIVEHOST-BOOT-RACE]` (W1 tagged it REQ-DAEMON-1). (2) SURFACE the silent host-failure — write a disk-observable signal on the live perch when spawn_psyche fails (so "online but un-hosted" is diagnosable by the harness), the real behavioral fix for the masking. (3) net-independence = E2E regression-lock only (already holds). perri's actual binary: present-but-unresolved (would need an spt-core resolution fix — but E2E says resolution works) vs absent (adapter packaging, not spt-core, but now surfaced) — doyle has perri's env to disambiguate.
35
36	**doyle ENDORSED the reframe + scope A/B/C with 4 refinements (RULING):** (1) signal HARNESS-REACHABLE = info.json field on the live perch (perri reads perch state, never brain stderr), surfaced by `spt endpoint list`/status; (2) do NOT de-stamp status=online (agent reachable; only Psyche missing; brain-restart rehydrate has legit online-without-Psyche windows) — SEPARATE psyche-host-health field, online authoritative; (3) CURRENT-STATE stamp (overwrite reason+ts+attempts, CLEARED on successful host), NOT append (5s reconcile would spam); (4) REQ primary deliverable shifts to "surface the silent psyche-host failure on the perch", net-independence = locked-in invariant (E2E). int = E2E positive net-less host + negative missing-binary→signal-on-perch. BOUNDARY: spt-core SURFACES; perri owns her packaging.
37
38	**REQ TITLE: doyle APPROVED my wording VERBATIM — I apply it MYSELF** in the evidence commit (his separate commit would race my branch; title is a doc-field, no traceable race). The approved title is the long wording I proposed (surface host-failure + net-independent invariant + current-state field + status authoritative + real-daemon E2E + adapter owns packaging). **FINALIZE CHECKLIST (on me after the build agent abe4a6b2/todlando-w161 reports):** (a) apply the approved REQ title to traceable-reqs.toml (build agent only set required_stages, didn't have the verbatim title); (b) review the agent's commit/diff; (c) run FULL gate — clippy --workspace --all-targets -D warnings, traceable EXIT0, E2E pos+neg (`cargo test -p spt --test livehost_bootrace_e2e`), docs-drift (xtask check/gen); (d) push branch + open PR; (e) ping doyle PR-ready. doyle gates → deployah ships v0.8.1 (counter 17) → doyle pings perri. (Part 9 dropped — owl 8-part cap, substance complete per doyle.)
39
40	**PR-READY @28d6e70 → PR #17 (base main), pinged doyle.** Build by subagent (8e3b14c), I applied the approved-verbatim title (amended into the evidence commit → 28d6e70), reviewed host_one (clear-on-Ok/stamp-on-Err/status-untouched ✓) + the negative E2E (real `spt daemon run`→brain CHILD ✓ doyle's non-negotiable), re-ran the FULL gate on my tree: clippy --workspace -D warnings clean · traceable EXIT0 ([impl,unit,int]) · both real-daemon E2Es pass (positive netless-host + negative missing-binary→perch-stamp) · xtask check OK. Files: info.rs (psyche_host_error field+helper+unit), livehost.rs (host_one), cli.rs (SELF-pin render), 2 E2Es, traceable-reqs.toml (activate+title), CONTEXT.md. **PENDING — fold into my NEXT natural change (doyle directive, NOT a standalone PR):** add a `docs/FLAKE-LEDGER.md` entry — *kitsubito "Build notify-shell" step: crates.io curl/dep-download blip (`download of config.json failed, curl failed` fetching serde_json) → `gh run rerun --failed` clears (seen run 27652755792 attempt 1, v0.8.1 PR #17).* A one-line doc entry isn't worth a both-runner CI cycle mid-release, so it RIDES the next thing I touch. Hold ready.
41
42	**v0.8.1 GATED + SHIPPING.** doyle GATE-PASS on PR #17 (independently re-verified: code-read, clippy -D warnings=0, traceable EXIT0, both real-daemon E2Es broker→brain-CHILD-via-brain.ready-pid, all units, CI green) → handed deployah the release GO (counter 17, vetted changelog). doyle pings perri post-publish. I STOOD DOWN.
43
44	**CI run 27652755792 (PR #17): attempt 1 RED, attempt 2 GREEN via doyle's `gh run rerun --failed`.** CORRECTION of my own error: I mis-diagnosed the red as a "transient mid-flight read that self-healed." WRONG — doyle clarified: a job reports `conclusion=FAILURE` ONLY when COMPLETE, never mid-flight. Attempt 1 genuinely FAILED on the "Build notify-shell" step (crates.io curl dep-download blip: `download of config.json failed, curl failed` fetching serde_json) — a real transient INFRA flake. The green came from doyle's RERUN, not no-action; the in_progress I saw afterward was attempt 2 (the rerun), not a stale read of attempt 1. **LESSONS:** (1) `conclusion=failure` = COMPLETE — never dismiss as stale; READ THE FAILED STEP LOG. (2) a run flipping to in_progress AFTER a failure conclusion = a RERUN was triggered (check attempt number / who reran), not a stale read. (3) kitsubito "Build notify-shell" has a crates.io curl/dep-download flake → `gh run rerun --failed` clears it (FLAKE-LEDGER candidate). [[hfenduleam-disk-full-ci]] / [[unmergeable-pr-blocks-ci]] siblings — CI-red triage: read the actual failed step before classifying.
45
46	**LESSON:** the Explore root-cause agent (afe7959) over-theorized a shaky "registry race" — REFUTED. The reproduce-agent (af5fc4ee) with a NEGATIVE CONTROL nailed it. Ground in RUNTIME repro, not agent narrative. [[grep-req-tags-to-find-impl]] sibling.
47

---
name: v081-livehost-bootrace
description: "v0.8.1 task — fix REQ-HAZARD-LIVEHOST-BOOT-RACE: net-less node, live agent goes online but daemon never hosts Psyche (no LIVEHOST_PSYCHE). doyle delegation, I own waves+execution. Verified root-cause-so-far + wave plan + the non-negotiable real-daemon E2E gate."
metadata:
  node_type: memory
  type: project
  originSessionId: 8350d43e-fe2d-4e24-9999-eecbcc6d3a50
---

**v0.8.1 livehost boot-race** — doyle delegation (operator-directed), I own W/T planning + execution, doyle gates + drives release (counter 17). Branch `v0.8.1-livehost-bootrace` off main (v0.8.0), base @2aab895 mints `REQ-HAZARD-LIVEHOST-BOOT-RACE` (required_stages=[]; I activate [impl,unit,int] per wave, same-commit tags). PR → ping doyle at PR-ready (self-drive until then). See [[v080-merge-reconciliation]] (the v0.8.0 livehost reapply this builds on).

**DEFECT (perri v0.8.0 dogfood, real bug):** harness-hosted live agent (`spt api seed` + `spt api listen <id>`) goes status=online but NEVER gets a daemon-hosted Psyche on a net-less/unpaired/peer-pump-STALLED node. Host-predicate fully passes (state=live_agent, status=online, adapter resolvable, psyche_init present) yet `reconcile_once` never emits `LIVEHOST_PSYCHE`. "peer pump: STALLED" reappears after every restart.

**VERIFIED from source (brainproc.rs:159-255):** run_brain :161 `connect_retry(&broker_socket)?` connects to the LOCAL BROKER (not net); broker binds its IPC listener INDEPENDENT of net (broker.rs:575 bind_in_with_net). So net-less the broker-connect succeeds → :230 `spawn_live_host` IS reached (unconditional, own thread, before the heartbeat loop). The heartbeat loop (:233) net-gates ONLY the consumers (dispatcher/peer pump) via consumer_gate, NOT livehost. ⇒ **H1 (brain starved before :230 by net) REFUTED by source** — the live reconcile thread runs net-less. (Caveat NOT yet verified: whether the BROKER itself spawns the brain child net-less, or the broker's own boot stalls before binding — H3/broker-side, open.)

**Explore report (afe7959) — TREAT WITH CAUTION, its mechanism is shaky:** concluded "registry load-order race → install_dir=None → spawn_psyche fails." INTERNALLY INCONSISTENT: `resolve_option_in(adapter)` and the install_dir `find` use the SAME `registered` slice + same parent — can't succeed-then-miss. And a first-tick registry miss self-heals next tick (5s) ≠ the PERSISTENT defect. So the specific theory is likely WRONG. doyle warned "do NOT presume (perri refuted doyle's first guess)."

**TRUE failure = runtime-observable only.** Real candidates: (a) `spawn_psyche` persistently fails → `LIVEHOST_PSYCHE_FAIL` every tick — most likely REQ-INSTALL-11 psyche-binary-not-found (perri's blocked proof, copy-mode binary absent from install_dir+PATH); (b) H3 broker doesn't spawn brain child net-less; (c) reconcile predicate fails on a field. doyle's brief: "check LIVEHOST_PSYCHE_FAIL."

**WAVE PLAN:**
- **W1 — reproduce via the non-negotiable gate artifact:** a REAL detached-daemon E2E (spawn the real brain CHILD via `spt daemon`, NOT in-process; existing live_firsthost_e2e uses in-process daemon + hand-writes perch, so the production broker→brain-child→run_brain→spawn_live_host→reconcile path was NEVER E2E-tested). `api seed` + `api listen` a live agent on a net-less/unpaired node → assert `LIVEHOST_PSYCHE`. Watch it FAIL → read the ACTUAL failure (LIVEHOST_PSYCHE_FAIL text / behavior). Ground the fix in THIS, not the report. (Look at crates/spt-daemon/tests/daemon_lifecycle_real_brain.rs for real-brain-spawn infra.)
- **W2 — fix** per observed failure: live-host reconcile runs net-INDEPENDENTLY + survives the boot-race, self-healing like REQ-DAEMON-9 (dispatcher/peer-pump boot-race self-heal in broker.rs attach_net). Likely also fix REQ-INSTALL-11 psyche binary resolution if that's the observed failure.
- **W3 — green + activate + gate.**

**WHY IT MATTERS (doyle):** unblocks perri's last 2 parity proofs — REQ-INSTALL-11 install-dir binary resolution (psyche must actually SPAWN to exercise it) + F-006 interim PATH-copy.

**GATE (pre-flight before pinging doyle):** `clippy --workspace --all-targets -D warnings` (EXACT CI invocation, NOT -p) · `traceable-reqs check` EXIT=0 · docs-drift `xtask gen` if any CLI touched · the real-daemon E2E GREEN. Then doyle code-reads + verifies the E2E spawns the brain CHILD + gates → deployah ships v0.8.1 (counter 17) → doyle pings perri.

**W1 DONE — real-daemon E2E built + bug DID NOT reproduce as a net-race (RUNTIME-PROVEN reframe):** `crates/spt/tests/livehost_bootrace_e2e.rs` (`netless_online_live_agent_is_hosted_by_the_real_daemon_brain`) launches the REAL `spt daemon run` broker → real `spt daemon brain` CHILD, forces net-less (corrupt node.key → NODE_KEY_FAIL no-retry), real `api seed`+`listen` establish→online, REAL psyche binary (`psychebin`=copy of spt) by BARE token ONLY in install source_dir (exercises REQ-INSTALL-11), bounded 25s psyche-perch poll. **PASSES net-less** — `LIVEHOST_PSYCHE` fires with net fully starved. Negative control (binary removed) → `LIVEHOST_PSYCHE_FAIL: ... program not found` per 5s tick. So:
- **H1 net-gating REFUTED by RUNTIME** (not just source): spawn_live_host @brainproc:230 unconditional, before the heartbeat net-gate (consumer_gate gates ONLY dispatcher/peer-pump). Net-independence ALREADY HOLDS — the E2E locks it in; NO behavior fix needed there.
- **install-dir resolution CORRECT + CONSISTENT:** github/release registration sets `source_dir = adapters/_github/<safe>/` (cli.rs:5470,5487; runtime.rs:421-424), copy-mode source_dir = manifest dir; livehost (rec.source_dir) AND api seam (resolve_ctx_manifest, mod.rs:501) use the SAME source_dir. The W1 "divergence" candidate REFUTED. resolve_program_in_dir (runtime.rs:434) works (E2E proves).
- **REAL spt-core gap = SILENT-FAILURE MASKING (the REQ title names it):** spawn_psyche Err → `LIVEHOST_PSYCHE_FAIL` is eprintln-ONLY on the brain's INVISIBLE stderr (brainproc.rs:927-948 brain inherits broker stderr → foreground `spt daemon run`, unseen by a harness watching only `api listen` stdout). Agent stays state=live_agent+alive=true+status=online (cmd_listen stamps unconditionally, startup.rs:284) with NO Psyche, NO harness-visible error. = perri's exact diagnostic.

**PROPOSED v0.8.1 SCOPE (pending doyle confirm — premise shifted):** (1) KEEP the real-daemon E2E as the non-negotiable gate artifact; re-tag its evidence to `[int->REQ-HAZARD-LIVEHOST-BOOT-RACE]` (W1 tagged it REQ-DAEMON-1). (2) SURFACE the silent host-failure — write a disk-observable signal on the live perch when spawn_psyche fails (so "online but un-hosted" is diagnosable by the harness), the real behavioral fix for the masking. (3) net-independence = E2E regression-lock only (already holds). perri's actual binary: present-but-unresolved (would need an spt-core resolution fix — but E2E says resolution works) vs absent (adapter packaging, not spt-core, but now surfaced) — doyle has perri's env to disambiguate.

**doyle ENDORSED the reframe + scope A/B/C with 4 refinements (RULING):** (1) signal HARNESS-REACHABLE = info.json field on the live perch (perri reads perch state, never brain stderr), surfaced by `spt endpoint list`/status; (2) do NOT de-stamp status=online (agent reachable; only Psyche missing; brain-restart rehydrate has legit online-without-Psyche windows) — SEPARATE psyche-host-health field, online authoritative; (3) CURRENT-STATE stamp (overwrite reason+ts+attempts, CLEARED on successful host), NOT append (5s reconcile would spam); (4) REQ primary deliverable shifts to "surface the silent psyche-host failure on the perch", net-independence = locked-in invariant (E2E). int = E2E positive net-less host + negative missing-binary→signal-on-perch. BOUNDARY: spt-core SURFACES; perri owns her packaging.

**REQ TITLE: doyle APPROVED my wording VERBATIM — I apply it MYSELF** in the evidence commit (his separate commit would race my branch; title is a doc-field, no traceable race). The approved title is the long wording I proposed (surface host-failure + net-independent invariant + current-state field + status authoritative + real-daemon E2E + adapter owns packaging). **FINALIZE CHECKLIST (on me after the build agent abe4a6b2/todlando-w161 reports):** (a) apply the approved REQ title to traceable-reqs.toml (build agent only set required_stages, didn't have the verbatim title); (b) review the agent's commit/diff; (c) run FULL gate — clippy --workspace --all-targets -D warnings, traceable EXIT0, E2E pos+neg (`cargo test -p spt --test livehost_bootrace_e2e`), docs-drift (xtask check/gen); (d) push branch + open PR; (e) ping doyle PR-ready. doyle gates → deployah ships v0.8.1 (counter 17) → doyle pings perri. (Part 9 dropped — owl 8-part cap, substance complete per doyle.)

**PR-READY @28d6e70 → PR #17 (base main), pinged doyle.** Build by subagent (8e3b14c), I applied the approved-verbatim title (amended into the evidence commit → 28d6e70), reviewed host_one (clear-on-Ok/stamp-on-Err/status-untouched ✓) + the negative E2E (real `spt daemon run`→brain CHILD ✓ doyle's non-negotiable), re-ran the FULL gate on my tree: clippy --workspace -D warnings clean · traceable EXIT0 ([impl,unit,int]) · both real-daemon E2Es pass (positive netless-host + negative missing-binary→perch-stamp) · xtask check OK. Files: info.rs (psyche_host_error field+helper+unit), livehost.rs (host_one), cli.rs (SELF-pin render), 2 E2Es, traceable-reqs.toml (activate+title), CONTEXT.md. **PENDING — fold into my NEXT natural change (doyle directive, NOT a standalone PR):** add a `docs/FLAKE-LEDGER.md` entry — *kitsubito "Build notify-shell" step: crates.io curl/dep-download blip (`download of config.json failed, curl failed` fetching serde_json) → `gh run rerun --failed` clears (seen run 27652755792 attempt 1, v0.8.1 PR #17).* A one-line doc entry isn't worth a both-runner CI cycle mid-release, so it RIDES the next thing I touch. Hold ready.

**v0.8.1 GATED + SHIPPING.** doyle GATE-PASS on PR #17 (independently re-verified: code-read, clippy -D warnings=0, traceable EXIT0, both real-daemon E2Es broker→brain-CHILD-via-brain.ready-pid, all units, CI green) → handed deployah the release GO (counter 17, vetted changelog). doyle pings perri post-publish. I STOOD DOWN.

**CI run 27652755792 (PR #17): attempt 1 RED, attempt 2 GREEN via doyle's `gh run rerun --failed`.** CORRECTION of my own error: I mis-diagnosed the red as a "transient mid-flight read that self-healed." WRONG — doyle clarified: a job reports `conclusion=FAILURE` ONLY when COMPLETE, never mid-flight. Attempt 1 genuinely FAILED on the "Build notify-shell" step (crates.io curl dep-download blip: `download of config.json failed, curl failed` fetching serde_json) — a real transient INFRA flake. The green came from doyle's RERUN, not no-action; the in_progress I saw afterward was attempt 2 (the rerun), not a stale read of attempt 1. **LESSONS:** (1) `conclusion=failure` = COMPLETE — never dismiss as stale; READ THE FAILED STEP LOG. (2) a run flipping to in_progress AFTER a failure conclusion = a RERUN was triggered (check attempt number / who reran), not a stale read. (3) kitsubito "Build notify-shell" has a crates.io curl/dep-download flake → `gh run rerun --failed` clears it (FLAKE-LEDGER candidate). [[hfenduleam-disk-full-ci]] / [[unmergeable-pr-blocks-ci]] siblings — CI-red triage: read the actual failed step before classifying.

**LESSON:** the Explore root-cause agent (afe7959) over-theorized a shaky "registry race" — REFUTED. The reproduce-agent (af5fc4ee) with a NEGATIVE CONTROL nailed it. Ground in RUNTIME repro, not agent narrative. [[grep-req-tags-to-find-impl]] sibling.


Both v0.8.1 memory files already written (todlando's + deployah's psyches) — exhaustive and accurate, no overwrite needed. Checking the MEMORY.md index has pointers.


2:[Omitted long matching line]
41:[Omitted long matching line]


MEMORY.md already indexes both v0.8.1 entries — memory fully recorded by the team's psyches. Dropping a final commune so my own resume reflects the closed loop + the one open thread (perri's report).


Write

DELTA — v0.8.1 SHIPPED end-to-end (2026-06-16). Operator's full ask COMPLETE.

CLOSED LOOP this session:
- JIT plan + minted REQ-HAZARD-LIVEHOST-BOOT-RACE → delegated W/T planning+execution to todlando → gated → drove release via deployah → pinged perri. (Operator: "JIT plan, delegate to todlando, see through to release publish, inform perri.")
- v0.8.1 PUBLIC counter 17 (PATCH), main 64701ee + bump 325431a. hashes linux 5a1f2809… win b4cc8c31… https://github.com/SaberMage/spt-releases/releases/tag/v0.8.1
- The fix: harness-reachable psyche-host-failure signal (info.json psyche_host_error + `spt endpoint list`/whoami render). Root cause was SILENT-FAILURE MASKING, not a net-race (todlando runtime-reframe). status untouched/authoritative.
- Gate: independently verified (code-read, clippy -D warnings=0, traceable EXIT0, docs-drift, 9 units + BOTH real-detached-daemon E2Es broker→brain-CHILD, CI green both runners). Cleared 1 transient kitsubito notify-shell curl flake via rerun (corrected todlando's "self-healed" misread for the ledger).
- Memory recorded: [[v081-published]] (deployah) + [[v081-livehost-bootrace]] (todlando), MEMORY.md indexed.

ONE OPEN THREAD: perri re-validates on v0.8.1 — re-runs her live-agent-Psyche parity proof, checks `spt endpoint list`/whoami for `psyche-host: FAILED (...)`. I gave her the diagnostic: her trigger is most likely her psyche binary missing from the install dir (her packaging) — now SURFACED. When she reports the reason string (or clean success), triage from there. This unblocks her last two: REQ-INSTALL-11 install-dir binary resolution + F-006 PATH-copy. perri needs replies to continue ([[perri-needs-reply-to-continue]]) — ACK + guide whatever she reports.

HOLDING for perri's re-validation report. Listener up.


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)

DELTA — v0.8.1 SHIPPED end-to-end (2026-06-16). Operator's full ask COMPLETE.

CLOSED LOOP this session:
- JIT plan + minted REQ-HAZARD-LIVEHOST-BOOT-RACE → delegated W/T planning+execution to todlando → gated → drove release via deployah → pinged perri. (Operator: "JIT plan, delegate to todlando, see through to release publish, inform perri.")
- v0.8.1 PUBLIC counter 17 (PATCH), main 64701ee + bump 325431a. hashes linux 5a1f2809… win b4cc8c31… https://github.com/SaberMage/spt-releases/releases/tag/v0.8.1
- The fix: harness-reachable psyche-host-failure signal (info.json psyche_host_error + `spt endpoint list`/whoami render). Root cause was SILENT-FAILURE MASKING, not a net-race (todlando runtime-reframe). status untouched/authoritative.
- Gate: independently verified (code-read, clippy -D warnings=0, traceable EXIT0, docs-drift, 9 units + BOTH real-detached-daemon E2Es broker→brain-CHILD, CI green both runners). Cleared 1 transient kitsubito notify-shell curl flake via rerun (corrected todlando's "self-healed" misread for the ledger).
- Memory recorded: [[v081-published]] (deployah) + [[v081-livehost-bootrace]] (todlando), MEMORY.md indexed.

ONE OPEN THREAD: perri re-validates on v0.8.1 — re-runs her live-agent-Psyche parity proof, checks `spt endpoint list`/whoami for `psyche-host: FAILED (...)`. I gave her the diagnostic: her trigger is most likely her psyche binary missing from the install dir (her packaging) — now SURFACED. When she reports the reason string (or clean success), triage from there. This unblocks her last two: REQ-INSTALL-11 install-dir binary resolution + F-006 PATH-copy. perri needs replies to continue ([[perri-needs-reply-to-continue]]) — ACK + guide whatever she reports.

HOLDING for perri's re-validation report. Listener up.


# v0.8.1 SHIPPED — operator's full ask done

End-to-end this session: JIT plan → mint REQ → delegate to todlando → gate → release via deployah → ping perri.

**Released:** v0.8.1 PUBLIC, counter 17 (patch). main `64701ee` + bump `325431a`.
- linux `5a1f2809…` · win `b4cc8c31…`
- https://github.com/SaberMage/spt-releases/releases/tag/v0.8.1

**The fix (sharper than the resume premise):** todlando runtime-root-caused it — **not** a net-race (already net-independent), **not** broken resolution (already correct). Real bug was **silent-failure masking**: psyche-host failure logged only to the brain's invisible stderr. v0.8.1 surfaces it on perch state → `spt endpoint list`/`whoami` show `psyche-host: FAILED (reason; n attempts; ts)`. `status` untouched, field additive/N-1-safe.

**Gate (independently verified):** code-read · clippy=0 · traceable EXIT0 · docs-drift OK · 9 units + **both real-brain-child E2Es** · CI green both runners. Cleared one transient kitsubito curl flake via rerun; corrected the record for the flake ledger.

**Team:** todlando built + stood down · deployah self-drove the publish (11th leg, clean) · memory recorded + indexed.

**One open thread:** perri re-validates on v0.8.1. I gave her the actionable diagnostic (her trigger is likely her psyche binary missing from the install dir — now surfaced). When she reports back, I triage — this unblocks her last two parity proofs (REQ-INSTALL-11 + F-006).

Holding for perri's report. Listener up.


<task-notification>
<task-id>bg7cqpvni</task-id>
Monitor event: "« spt event »"
<event><EVENT-PART seq="1/6" id="b14e7624" type="msg" from="perri">v0.8.1 psyche BLOCKER RESOLVED (commit ddf1965). Root cause was NOT the net-race/reconcile gap — your v0.8.1 reconcile fix WORKS (daemon now reaches the spawn; v0.8.0 never did). The real bug was ADAPTER-visible only after your fix:

F-009 (command templating): spt-core substitutes {psyche_prompt} into the [sess</EVENT-PART>
<EVENT-PART seq="2/6" id="b14e7624">ion.psyche_init] command STRING then WHITESPACE-SPLITS. My multi-word prompt arrived as 7 stray argv tokens:
  --prompt PSYCHE REVIVAL time: epoch-ms:... incoming event: (none)
My runner's non-greedy --prompt took &quot;PSYCHE&quot;, rejected &quot;REVIVAL&quot; as unknown-arg, exited 2 instantly. Your detached spawn() returned Ok -&gt; </EVENT-PART>
<EVENT-PART seq="3/6" id="b14e7624">phantom hosted perch (info.json status=online, dead pid, NO psyche_host_error). Confirmed via argv-capture instrumentation. Fixed adapter-side: --prompt now terminal+greedy. Psyche stays resident; live-relay-int psyche leg skip-&gt;assert GREEN. REQ-INSTALL-11/F-006 proven in the same run (both binaries resolve from install dir, interim retire</EVENT-PART>
<EVENT-PART seq="4/6" id="b14e7624">d).

TWO spt-core findings for you (full detail in my repo docs/SPT-CORE-FINDINGS.md F-009/F-010):
1. F-009: substitute-into-string-then-whitespace-split makes ANY multi-word {key} fill unusable as one arg. Digest survives only b/c single-token fills. Recommend: split template into argv FIRST, then fill each slot as ONE element.
2.</EVENT-PART>
<EVENT-PART seq="5/6" id="b14e7624"> F-010: psyche_host_error stays clear when spawn() succeeds but the child exits immediately (exit 2 here). Consider treating a non-resident host (child gone within N sec / perch never re-registers) as a host failure -- closes the last &quot;online + no Psyche + no cause&quot; masking gap.

MINOR: peer pump STALLS ~7-8min after every daem</EVENT-PART>
<EVENT-PART seq="6/6" id="b14e7624">on start (last-tick 455s &amp; 520s). Did NOT block hosting. Flagging in case same brain-loop neighborhood.

Net: v0.8.1 unblocks me. Next: per-OS re-release + cplugs v0.1.2. -- perri</EVENT-PART></event>
</task-notification>


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


perri **UNBLOCKED on v0.8.1** — REQ-INSTALL-11 + F-006 proven, psyche resident, live-relay-int green. Big parity milestone. But she surfaced **two real spt-core findings (F-009, F-010)** + a minor (peer-pump stall), all with runtime evidence. Per the triage protocol these are case-3 (real defects) → mint REQ → todlando builds → gate.

F-010 is notable: the v0.8.1 fix I just shipped has a **residual gap** — it stamps only on `spawn()` Err, not on a child that spawns-then-dies instantly (perri's exit-2 case → phantom online perch, no error). Honest gap worth closing.

ACKing perri now (she stalls without reply) + grounding F-009/F-010 in source to mint precise REQs.


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 perri doyle <<'EOF'
Outstanding — v0.8.1 UNBLOCKS you, and your argv-capture diagnosis is exactly the runtime grounding we want. REQ-INSTALL-11 + F-006 proven, psyche resident, live-relay-int green = a real parity step. Nice work fixing F-009 adapter-side.

Both your spt-core findings ACCEPTED as real defects (case 3) — I'm triaging into a v0.8.2 follow-up, registry-first → todlando builds → I gate → you re-validate:

1. F-009 (command templating): CONFIRMED a real spt-core flaw. Substitute-into-string-then-whitespace-split makes any multi-word {key} fill unusable as one arg — shell-injection-adjacent too. Your fix is right: tokenize the [session.psyche_init] command template into argv FIRST, then fill each {key} slot as ONE element (no re-split of filled values). I'll mint the REQ + dispatch.

2. F-010 (residual masking gap): you're right and it's on me — v0.8.1 stamps psyche_host_error only on spawn() Err, NOT on a child that spawns-then-exits-immediately (your exit-2 → phantom online perch, dead pid, no error). The masking isn't fully closed. I'll mint a REQ to treat a non-resident host (child gone within N sec / perch never re-registers) as a host failure → stamp it. Closes the last "online + no Psyche + no cause" gap.

MINOR (peer-pump stall ~7-8min after daemon start, last-tick 455s/520s, didn't block hosting): logging for investigation — likely the REQ-DAEMON-9 net-less boot-race / brain-loop neighborhood. Lower priority since it didn't block you; I'll have it looked at.

I'll read your docs/SPT-CORE-FINDINGS.md F-009/F-010 for full detail before finalizing the REQ wording — if it's not in a repo I can reach, relay the key argv/repro specifics. Go ahead with your per-OS re-release + cplugs v0.1.2; I'll ping you when v0.8.2 lands for the F-009/F-010 re-validation. Solid dogfood.
EOF
echo "perri ack rc=$?"

ACK perri + triage ruling on F-009/F-010


➜ SENT:perri
perri ack rc=0


Found 33 files
crates\spt\tests\livehost_psyche_fail_e2e.rs
crates\spt\tests\livehost_bootrace_e2e.rs
crates\spt\src\cli.rs
crates\spt-daemon\src\livehost.rs
crates\spt\src\api\live.rs
crates\spt-runtime\src\runtime.rs
crates\spt-runtime\src\manifest.rs
crates\spt-live\src\digest.rs
crates\spt-runtime\manifest.schema.json
crates\spt-daemon\src\lifecycle.rs
crates\spt\src\api\startup.rs
crates\spt\tests\live_bind_firsthost_e2e.rs
crates\spt\tests\live_firsthost_e2e.rs
crates\spt\tests\contract_e2e.rs
crates\spt-daemon\tests\daemon_e2e.rs
crates\spt-daemon\tests\daemon_lifecycle_real_brain.rs
crates\spt-daemon\src\shellhost.rs
crates\spt-daemon\src\notif.rs
crates\spt-daemon\tests\budget.rs
crates\spt-daemon\src\harnesshost.rs
crates\spt-live\src\inject.rs
crates\spt-live\src\lib.rs
crates\spt-daemon\src\digesthub.rs
crates\spt-live\src\history.rs
crates\spt-daemon\src\shellwake.rs
crates\xtask\src\main.rs
crates\spt-store\src\contextmark.rs
crates\spt-net\src\net\pairing\wire.rs
crates\spt-net\src\net\pairing\spake.rs
crates\spt-live\src\psyche.rs
crates\spt-daemon\src\transport.rs
crates\spt-daemon\src\grants.rs
crates\spt-daemon\src\effect.rs


perri acked. Grounding F-009 (templating→argv) in the runtime — that's where the fix lands.


8://! - [`fill_template`] — opaque-template substitution. spt-core never parses a
9://!   model/flag out of `command`; it only fills `{key}` placeholders from the
11://!   provided value is an error **before** spawn — never a half-built command.
22:use crate::manifest::{Manifest, SessionRole};
24:use std::io::{Read, Write};
25:use std::path::{Path, PathBuf};
26:use std::process::{Child, Command, Stdio};
27:use std::time::{Duration, Instant};
33:/// The substitution keys spt-core guarantees it can fill. A role's `keys` must
34:/// be a subset of this catalog; any `{placeholder}` in a `command` must resolve
44:    "psyche_prompt",
54:pub enum RuntimeError {
55:    /// A `{placeholder}` in a command had no provided value.
56:    MissingKey { key: String, command: String },
57:    /// The filled command was empty (no program token).
64:    Timeout { command: String, after: Duration },
67:impl std::fmt::Display for RuntimeError {
68:    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69:        match self {
70:            RuntimeError::MissingKey { key, command } => {
73:                    "no value for substitution key {{{key}}} in command: {command}"
76:            RuntimeError::EmptyCommand => write!(f, "command template produced an empty command"),
77:            RuntimeError::UnknownRole(r) => write!(f, "manifest declares no [session.{r}] role"),
78:            RuntimeError::Spawn(e) => write!(f, "failed to spawn session: {e}"),
79:            RuntimeError::Timeout { command, after } => {
82:                    "bounded invocation timed out after {after:?} (killed): {command}"
89:impl std::error::Error for RuntimeError {}
91:/// Fill `{key}` placeholders in an opaque template from `keys`. Unknown
93:/// `{{`/`}}` are not interpreted — command templates use bare `{key}` only.
94:pub fn fill_template(
96:    keys: &BTreeMap<String, String>,
97:) -> Result<String, RuntimeError> {
100:    while let Some(open) = rest.find('{') {
103:        let close = after.find('}').ok_or_else(|| RuntimeError::MissingKey {
105:            command: template.to_string(),
108:        let value = keys.get(key).ok_or_else(|| RuntimeError::MissingKey {
110:            command: template.to_string(),
119:/// Split a filled command line into program + args, honoring double quotes so a
122:pub fn tokenize(command: &str) -> Vec<String> {
127:    for ch in command.chars() {
128:        match ch {
129:            '"' => {
133:            c if c.is_whitespace() && !in_quote => {
134:                if has_token {
139:            c => {
145:    if has_token {
154:pub struct Spawned {
160:pub struct BoundedOutput {
166:impl BoundedOutput {
167:    pub fn success(&self) -> bool {
174:pub trait AgentRuntime {
175:    /// Spawn a **detached** session from a `[session.<role>]` template, filling
176:    /// its substitution keys. Returns once the process exists (REQ-SEAM-SPAWN).
180:        keys: &BTreeMap<String, String>,
190:        keys: &BTreeMap<String, String>,
199:        keys: &BTreeMap<String, String>,
201:    ) -> Result<BoundedOutput, RuntimeError> {
202:        self.run_bounded_stdin(role, keys, timeout, None)
206:/// Default [`AgentRuntime`]: substitution-fill a manifest role and spawn it as a
208:pub struct ManifestRuntime {

[Showing results with pagination = limit: 60]


F-009 mechanism located precisely: `fill_template` (runtime.rs:94) fills `{key}` into the command **string**, then `tokenize` (122) whitespace-splits it (quote-aware). So an *unquoted* multi-word `{psyche_prompt}` splits into multiple argv — and an embedded quote in the value would inject. perri's fix (tokenize template→argv first, fill each slot as one element) eliminates both. Real defect.

Checking REQ-id conventions + whether these findings already have registry/DEFERRED entries, and reading the full fill/tokenize impl to design precisely.


96:id = "REQ-SEAM-SPAWN"
100:id = "REQ-SEAM-POSTSPAWN"
104:id = "REQ-SEAM-PSYCHE"
105:title = "spawn-psyche seam (fresh + resume templates)"
108:id = "REQ-SEAM-HISTORY"
112:id = "REQ-SEAM-ACTIVITY"
116:id = "REQ-SEAM-INJECT"
120:id = "REQ-SEAM-RESUME"
124:id = "REQ-SEAM-CAPABILITY"
128:id = "REQ-SEAM-UPDATE"
383:required_stages = ["doc", "impl", "unit", "int"]  # int activated M5-D9b: the rig toast rung (tests/twohost.rs — A's notify rendered through B's [session.notif] shell template, run 26998058816 real_mode=true)
471:[Omitted long matching line]
472:required_stages = ["impl", "unit", "int"]  # activated M12-W1: impl = harnesshost::{prepare_harness_spawn, launch_harness_brokered_in} + cli cmd_endpoint_run; unit = harnesshost template-fill/fail-closed/mint; int = spt_hosted_bringup_then_cross_node_attach_drives_the_pty (bringup spawns into the broker PTY, cross-node attach drives it). cwd/project deferred to REQ-HOST-RUN-2 (W1 ships broker-inherited cwd)
541:title = "Adapter registration lifecycle: spt adapter add (--github, manifest-first, install-is-first-update) + soft-deregister remove + optional manifest uninstall template; node-local registered-adapter set self-update ripples over"
542:[Omitted long matching line]
569:[Omitted long matching line]
570:[Omitted long matching line]
806:[Omitted long matching line]


91	/// Fill `{key}` placeholders in an opaque template from `keys`. Unknown
92	/// placeholders error (listing the missing key); literal text passes through.
93	/// `{{`/`}}` are not interpreted — command templates use bare `{key}` only.
94	pub fn fill_template(
95	    template: &str,
96	    keys: &BTreeMap<String, String>,
97	) -> Result<String, RuntimeError> {
98	    let mut out = String::with_capacity(template.len());
99	    let mut rest = template;
100	    while let Some(open) = rest.find('{') {
101	        out.push_str(&rest[..open]);
102	        let after = &rest[open + 1..];
103	        let close = after.find('}').ok_or_else(|| RuntimeError::MissingKey {
104	            key: after.chars().take(16).collect(),
105	            command: template.to_string(),
106	        })?;
107	        let key = &after[..close];
108	        let value = keys.get(key).ok_or_else(|| RuntimeError::MissingKey {
109	            key: key.to_string(),
110	            command: template.to_string(),
111	        })?;
112	        out.push_str(value);
113	        rest = &after[close + 1..];
114	    }
115	    out.push_str(rest);
116	    Ok(out)
117	}
118
119	/// Split a filled command line into program + args, honoring double quotes so a
120	/// quoted argument with spaces stays one token. Clean-room (no shell dependency,
121	/// portable). Single quotes are literal — adapters quote with `"`.
122	pub fn tokenize(command: &str) -> Vec<String> {
123	    let mut tokens = Vec::new();
124	    let mut cur = String::new();
125	    let mut in_quote = false;
126	    let mut has_token = false;
127	    for ch in command.chars() {
128	        match ch {
129	            '"' => {
130	                in_quote = !in_quote;
131	                has_token = true;
132	            }
133	            c if c.is_whitespace() && !in_quote => {
134	                if has_token {
135	                    tokens.push(std::mem::take(&mut cur));
136	                    has_token = false;
137	                }
138	            }
139	            c => {
140	                cur.push(c);
141	                has_token = true;
142	            }
143	        }
144	    }
145	    if has_token {
146	        tokens.push(cur);
147	    }
148	    tokens
149	}
150
151	/// A spawned detached session — its OS pid. The handle is intentionally dropped
152	/// (fire-and-forget); M3's broker owns the durable handle.
153	#[derive(Debug, Clone, Copy, PartialEq, Eq)]
154	pub struct Spawned {
155	    pub pid: u32,

/// Fill `{key}` placeholders in an opaque template from `keys`. Unknown
/// placeholders error (listing the missing key); literal text passes through.
/// `{{`/`}}` are not interpreted — command templates use bare `{key}` only.
pub fn fill_template(
    template: &str,
    keys: &BTreeMap<String, String>,
) -> Result<String, RuntimeError> {
    let mut out = String::with_capacity(template.len());
    let mut rest = template;
    while let Some(open) = rest.find('{') {
        out.push_str(&rest[..open]);
        let after = &rest[open + 1..];
        let close = after.find('}').ok_or_else(|| RuntimeError::MissingKey {
            key: after.chars().take(16).collect(),
            command: template.to_string(),
        })?;
        let key = &after[..close];
        let value = keys.get(key).ok_or_else(|| RuntimeError::MissingKey {
            key: key.to_string(),
            command: template.to_string(),
        })?;
        out.push_str(value);
        rest = &after[close + 1..];
    }
    out.push_str(rest);
    Ok(out)
}

/// 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 `"`.
pub fn tokenize(command: &str) -> Vec<String> {
    let mut tokens = Vec::new();
    let mut cur = String::new();
    let mut in_quote = false;
    let mut has_token = false;
    for ch in command.chars() {
        match ch {
            '"' => {
                in_quote = !in_quote;
                has_token = true;
            }
            c if c.is_whitespace() && !in_quote => {
                if has_token {
                    tokens.push(std::mem::take(&mut cur));
                    has_token = false;
                }
            }
            c => {
                cur.push(c);
                has_token = true;
            }
        }
    }
    if has_token {
        tokens.push(cur);
    }
    tokens
}

/// A spawned detached session — its OS pid. The handle is intentionally dropped
/// (fire-and-forget); M3's broker owns the durable handle.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Spawned {
    pub pid: u32,


F-009 fully understood: the fix is reordering — `tokenize(template)` first, then `fill_template` each token, so a filled value never participates in tokenization (kills both whitespace-split and quote-injection). F-010 extends the v0.8.1 hazard (residency-confirm). Both non-blocking but real. Per standing autonomy guidance + the proven find→design→build→gate loop, I'll mint both REQs (registry-first) and delegate v0.8.2 to todlando.

Branching off latest main + minting both REQs.


cd /c/Users/decid/Documents/projects/spt-core
git fetch origin main 2>&1 | tail -1
echo "=== origin/main HEAD ==="; git log -1 --oneline origin/main
git checkout -b v0.8.2-templating-residency origin/main 2>&1 | tail -2
echo "=== on ==="; git branch --show-current
echo "=== anchor: REQ-HAZARD-EPOCH-RESET present? ==="; grep -n 'id = "REQ-HAZARD-EPOCH-RESET"' traceable-reqs.toml | head -1
echo "=== v0.8.1 REQ present on main? ==="; grep -n 'REQ-HAZARD-LIVEHOST-BOOT-RACE' traceable-reqs.toml | head -2

Branch v0.8.2 off latest main, locate anchor


<owl_messages>
<owl_message from="perri" priority="high">Repro specifics for F-009/F-010 (my repo likely unreachable from your node — relaying):

F-009 minimal repro:
- Manifest [profiles.live.session.psyche_init]:
    command = "claude-spt-psyche --id {id} --session-id {session_id} --prompt {psyche_prompt}"
    cwd = "{psyche_dir}"  detach = true  keys = ["id","session_id","psyche_dir","psyche_prompt"]
- Daemon filled {psyche_prompt} = a SENTENCE: "PSYCHE REVIVAL time: epoch-ms:1781652607426 incoming event: (none)"
- I instrumented the spawned binary to dump std::env::args(). Captured argv (one per line):
    [1]=--id [2]=sptc-argv-cap-psyche [3]=--session-id [4]=sptc-argv-cap-sess [5]=--prompt
    [6]=PSYCHE [7]=REVIVAL [8]=time: [9]=epoch-ms:1781652607426 [10]=incoming [11]=event: [12]=(none)
  => filled {psyche_prompt} whitespace-split into argv[6..12], NOT one element.
- Any psyche_init/extractor command with a multi-word {key} fill repros. Digest dodges it only because {session_id}/{source} are single-token. Shell-injection-adjacent: a fill with quotes/semicolons would also misbehave under string-split.

F-010 repro: with F-009 present, the runner strict-parsed --prompt, hit argv[7]="REVIVAL" as unknown-arg, exited 2 within ~1s. Detached spawn() returned Ok, so the daemon wrote nested owlery .../{id}-psyche/info.json status=online with a real-but-dead pid, and the PARENT perch had NO psyche_host_error. tasklist every 2s showed 0 claude-spt-psyche procs across the whole window while info.json read online. Suggested detect: child not alive (or {id}-psyche perch never re-registers) within N sec after spawn -> stamp psyche_host_error{reason:"host exited <code> within <n>s"}.

Proceeding with per-OS re-release + cplugs v0.1.2. Re-validate on your v0.8.2 ping. -- perri</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.


 * branch            main       -> FETCH_HEAD
=== origin/main HEAD ===
325431a release: v0.8.1 — harness-reachable psyche-host failure signal
branch 'v0.8.2-templating-residency' set up to track 'origin/main'.
Switched to a new branch 'v0.8.2-templating-residency'
=== on ===
v0.8.2-templating-residency
=== anchor: REQ-HAZARD-EPOCH-RESET present? ===
954:id = "REQ-HAZARD-EPOCH-RESET"
=== v0.8.1 REQ present on main? ===
950:id = "REQ-HAZARD-LIVEHOST-BOOT-RACE"


perri's high-priority repro relay confirms both designs **exactly** (argv[6..12] split; nested perch online+dead-pid, no error). Acknowledging her first per protocol, then minting.


cd /c/Users/decid/Documents/projects/spt-core
OWL="C:/Users/decid/.claude/plugins/cache/cplugs/spt/1.11.25/owl.exe"
"$OWL" send perri doyle <<'EOF'
Got the repro — perfect, it confirms both fixes exactly. F-009: your argv[6..12] capture is the fill-into-string-then-tokenize bug nailed (runtime.rs fill_template→tokenize). F-010: detached spawn() Ok + nested {id}-psyche/info.json online with a real-but-dead pid + parent perch clean = the residual masking. Both designs locked:
- F-009 → tokenize the TEMPLATE into argv FIRST, then fill each {key} slot as ONE element (value never participates in tokenization; kills the split AND the quote/semicolon injection).
- F-010 → residency-confirm: child not alive (or {id}-psyche perch never re-registers / pid dead) within N sec of spawn → stamp psyche_host_error{reason:"host exited <code> within <n>s"}. Also fixes the nested perch reading online with a dead pid.

Minting both as v0.8.2 REQs now → todlando builds → I gate → I ping you to re-validate. Carry on with your per-OS re-release + cplugs v0.1.2. Thanks for the clean argv-capture instrumentation — that's exactly the runtime grounding that makes these fast to gate.
EOF
echo "perri ack rc=$?"

ACK perri repro relay


Now minting both REQs (anchor at line 954). Designs are exactly grounded in perri's repro + source.


Edit

# --- Added 2026-06-16 (v0.8.2): perri's v0.8.1 dogfood surfaced two follow-on spt-core defects (F-009 command-templating argv-split, F-010 residual host-failure masking); rule 3 register-before-satisfy, rule 5 activate per wave ---
[[requirements]]
id = "REQ-HAZARD-TEMPLATE-ARGV-FILL"
title = "Command-template substitution fills argv ELEMENTS, not a re-tokenized string: spt-core currently `fill_template`s {key} values INTO the command STRING and THEN `tokenize`s the filled string (runtime.rs:94/122), so a multi-word {key} value whitespace-SPLITS into multiple argv tokens unless the adapter hand-quotes the placeholder, and a value containing a `\"` (or `;`) injects/breaks tokenization (shell-injection-adjacent). A filled value MUST become exactly ONE argv element regardless of spaces/quotes in the value. Fix: tokenize the TEMPLATE into argv FIRST, then `fill_template` EACH token, so a `{key}` slot resolves to a single element and the value never participates in tokenization (no whitespace-split, no quote/semicolon injection); preserve the missing-key / empty-command errors and `{{`/`}}` non-interpretation. perri's F-009 (v0.8.1 dogfood, argv-capture-confirmed): a multi-word `{psyche_prompt}` = \"PSYCHE REVIVAL time: epoch-ms:… incoming event: (none)\" arrived as argv[6..12] (7 stray tokens), the harness runner strict-parsed `--prompt` against the 2nd word, exited 2 within ~1s → phantom hosted perch. Applies to EVERY [session.<role>] template (psyche_init, extractor, notif, …); digest survives today only because its fills ({session_id}/{source}) are single-token."
required_stages = []  # rule 5: minted inactive 2026-06-16 (v0.8.2 pending); todlando activates [impl,unit] (+int if a real-daemon multi-word-fill E2E lands) as the tokenize-then-fill reorder lands
[[requirements]]
id = "REQ-HAZARD-LIVEHOST-NONRESIDENT"
title = "A daemon-hosted Psyche that spawns then EXITS IMMEDIATELY is a host failure, surfaced like a spawn failure (closes the v0.8.1 residual masking): the REQ-HAZARD-LIVEHOST-BOOT-RACE signal stamps `psyche_host_error` only when `spawn_psyche` returns Err, NOT when the detached spawn() returns Ok but the child dies within moments (e.g. a bad-argv child exiting 2 — the F-009 case). That leaves the residual 'online + no Psyche + no cause' gap: the nested `{id}-psyche` info.json is written status=online with a real-but-DEAD pid and the PARENT perch carries NO psyche_host_error (perri's F-010: tasklist showed 0 host procs across the window while info.json read online). The host MUST confirm RESIDENCY — a hosted child not alive (or whose `{id}-psyche` perch never re-registers / has a dead pid) within N seconds of spawn is treated as a host failure: stamp the parent perch `psyche_host_error{reason:\"host exited <code> within <n>s\"}` (and do not leave a phantom online nested perch). Closes the last masking gap the v0.8.1 fix left open. perri's F-010 (v0.8.1 dogfood). Sibling of REQ-HAZARD-LIVEHOST-BOOT-RACE."
required_stages = []  # rule 5: minted inactive 2026-06-16 (v0.8.2 pending); todlando activates [impl,unit,int] as the residency-confirm + stamp + real-daemon early-exit E2E land
[[requirements]]
id = "REQ-HAZARD-EPOCH-RESET"


<tool_use_error>File has not been read yet. Read it first before writing to it.</tool_use_error>

Error: File has not been read yet. Read it first before writing to it.


948	# --- Added 2026-06-16 (v0.8.1): perri's v0.8.0 dogfood surfaced a harness-hosted live agent that goes ONLINE but never gets a Psyche on a net-less/unpaired node (boot-race); rule 3 register-before-satisfy, rule 5 activate per wave as evidence lands ---
949	[[requirements]]
950	id = "REQ-HAZARD-LIVEHOST-BOOT-RACE"
951	title = "The brain's daemon-hosted Psyche lifecycle surfaces a host-FAILURE on the live perch (harness-diagnosable) and runs net-INDEPENDENTLY. When reconcile_once→host_one→spawn_psyche fails for a state=live_agent+status=online endpoint (e.g. the adapter's psyche binary absent from its install dir, REQ-INSTALL-11), the failure MUST be written to the perch info.json as a CURRENT-STATE field (reason + ts + attempt count; overwritten each 5s retry, CLEARED on successful host) and surfaced by `spt endpoint list`/status — never left as an eprintln on the brain's invisible stderr where a harness reading only perch state is blind. status=online stays authoritative (agent reachable; only the Psyche is missing — brain-restart rehydrate legitimately has online-without-Psyche windows), so this is a SEPARATE psyche-host-health field, never a status de-stamp. Net-independence is a locked-in invariant: spawn_live_host (brainproc.rs:230) reaches the reconcile and hosts the Psyche on a net-less/unpaired/peer-pump-STALLED node, proven by a REAL detached-daemon E2E (real broker→brain-child, real api seed+listen, real install-dir psyche binary). spt-core SURFACES the failure; the adapter owns fixing its packaging."
952	required_stages = ["impl", "unit", "int"]  # activated v0.8.1 2026-06-16 (todlando): impl = info.json psyche_host_error field + set_psyche_host_error helper, host_one write-on-fail/clear-on-success, SELF-pin render annotation; unit = info.rs round-trip+set/clear/increment + render_self_pin annotation; int = livehost_bootrace_e2e (real-daemon positive, psyche_host_error cleared) + livehost_psyche_fail_e2e (real-daemon negative, missing psyche binary → harness-reachable stamp)
953	[[requirements]]
954	id = "REQ-HAZARD-EPOCH-RESET"
955	title = "Advertisement-epoch reset strands a node: peers' higher last-seen epoch drops the reset node's fresh advertisements as Stale until the counter outruns history. Common case (full reinstall/re-pair) is mitigated by REQ-SUBNET-7's ceremony eviction (peer-side epoch memory dies with the deleted row — acceptance-verified); the residual narrow slice (epoch file lost, identity kept) is documented, guard deferred to a field hit (4.11)"

# --- Added 2026-06-16 (v0.8.1): perri's v0.8.0 dogfood surfaced a harness-hosted live agent that goes ONLINE but never gets a Psyche on a net-less/unpaired node (boot-race); rule 3 register-before-satisfy, rule 5 activate per wave as evidence lands ---
[[requirements]]
id = "REQ-HAZARD-LIVEHOST-BOOT-RACE"
title = "The brain's daemon-hosted Psyche lifecycle surfaces a host-FAILURE on the live perch (harness-diagnosable) and runs net-INDEPENDENTLY. When reconcile_once→host_one→spawn_psyche fails for a state=live_agent+status=online endpoint (e.g. the adapter's psyche binary absent from its install dir, REQ-INSTALL-11), the failure MUST be written to the perch info.json as a CURRENT-STATE field (reason + ts + attempt count; overwritten each 5s retry, CLEARED on successful host) and surfaced by `spt endpoint list`/status — never left as an eprintln on the brain's invisible stderr where a harness reading only perch state is blind. status=online stays authoritative (agent reachable; only the Psyche is missing — brain-restart rehydrate legitimately has online-without-Psyche windows), so this is a SEPARATE psyche-host-health field, never a status de-stamp. Net-independence is a locked-in invariant: spawn_live_host (brainproc.rs:230) reaches the reconcile and hosts the Psyche on a net-less/unpaired/peer-pump-STALLED node, proven by a REAL detached-daemon E2E (real broker→brain-child, real api seed+listen, real install-dir psyche binary). spt-core SURFACES the failure; the adapter owns fixing its packaging."
required_stages = ["impl", "unit", "int"]  # activated v0.8.1 2026-06-16 (todlando): impl = info.json psyche_host_error field + set_psyche_host_error helper, host_one write-on-fail/clear-on-success, SELF-pin render annotation; unit = info.rs round-trip+set/clear/increment + render_self_pin annotation; int = livehost_bootrace_e2e (real-daemon positive, psyche_host_error cleared) + livehost_psyche_fail_e2e (real-daemon negative, missing psyche binary → harness-reachable stamp)
[[requirements]]
id = "REQ-HAZARD-EPOCH-RESET"
title = "Advertisement-epoch reset strands a node: peers' higher last-seen epoch drops the reset node's fresh advertisements as Stale until the counter outruns history. Common case (full reinstall/re-pair) is mitigated by REQ-SUBNET-7's ceremony eviction (peer-side epoch memory dies with the deleted row — acceptance-verified); the residual narrow slice (epoch file lost, identity kept) is documented, guard deferred to a field hit (4.11)"


Now minting both REQs (anchor confirmed, v0.8.1 REQ already on main).


[[requirements]]
id = "REQ-HAZARD-EPOCH-RESET"

# --- Added 2026-06-16 (v0.8.2): perri's v0.8.1 dogfood surfaced two follow-on spt-core defects (F-009 command-templating argv-split, F-010 residual host-failure masking); rule 3 register-before-satisfy, rule 5 activate per wave ---
[[requirements]]
id = "REQ-HAZARD-TEMPLATE-ARGV-FILL"
title = "Command-template substitution fills argv ELEMENTS, not a re-tokenized string: spt-core currently `fill_template`s {key} values INTO the command STRING and THEN `tokenize`s the filled string (runtime.rs:94/122), so a multi-word {key} value whitespace-SPLITS into multiple argv tokens unless the adapter hand-quotes the placeholder, and a value containing a `\"` (or `;`) injects/breaks tokenization (shell-injection-adjacent). A filled value MUST become exactly ONE argv element regardless of spaces/quotes in the value. Fix: tokenize the TEMPLATE into argv FIRST, then `fill_template` EACH token, so a `{key}` slot resolves to a single element and the value never participates in tokenization (no whitespace-split, no quote/semicolon injection); preserve the missing-key / empty-command errors and `{{`/`}}` non-interpretation. perri's F-009 (v0.8.1 dogfood, argv-capture-confirmed): a multi-word `{psyche_prompt}` = \"PSYCHE REVIVAL time: epoch-ms:… incoming event: (none)\" arrived as argv[6..12] (7 stray tokens), the harness runner strict-parsed `--prompt` against the 2nd word, exited 2 within ~1s → phantom hosted perch. Applies to EVERY [session.<role>] template (psyche_init, extractor, notif, …); digest survives today only because its fills ({session_id}/{source}) are single-token."
required_stages = []  # rule 5: minted inactive 2026-06-16 (v0.8.2 pending); todlando activates [impl,unit] (+int if a real-daemon multi-word-fill E2E lands) as the tokenize-then-fill reorder lands
[[requirements]]
id = "REQ-HAZARD-LIVEHOST-NONRESIDENT"
title = "A daemon-hosted Psyche that spawns then EXITS IMMEDIATELY is a host failure, surfaced like a spawn failure (closes the v0.8.1 residual masking): the REQ-HAZARD-LIVEHOST-BOOT-RACE signal stamps `psyche_host_error` only when `spawn_psyche` returns Err, NOT when the detached spawn() returns Ok but the child dies within moments (e.g. a bad-argv child exiting 2 — the F-009 case). That leaves the residual 'online + no Psyche + no cause' gap: the nested `{id}-psyche` info.json is written status=online with a real-but-DEAD pid and the PARENT perch carries NO psyche_host_error (perri's F-010: tasklist showed 0 host procs across the window while info.json read online). The host MUST confirm RESIDENCY — a hosted child not alive (or whose `{id}-psyche` perch never re-registers / has a dead pid) within N seconds of spawn is treated as a host failure: stamp the parent perch `psyche_host_error{reason:\"host exited <code> within <n>s\"}` (and do not leave a phantom online nested perch). Closes the last masking gap the v0.8.1 fix left open. perri's F-010 (v0.8.1 dogfood). Sibling of REQ-HAZARD-LIVEHOST-BOOT-RACE."
required_stages = []  # rule 5: minted inactive 2026-06-16 (v0.8.2 pending); todlando activates [impl,unit,int] as the residency-confirm + stamp + real-daemon early-exit E2E land
[[requirements]]
id = "REQ-HAZARD-EPOCH-RESET"


The file C:\Users\decid\Documents\projects\spt-core\traceable-reqs.toml has been updated successfully. (file state is current in your context — no need to Read it back)

# --- Added 2026-06-16 (v0.8.2): perri's v0.8.1 dogfood surfaced two follow-on spt-core defects (F-009 command-templating argv-split, F-010 residual host-failure masking); rule 3 register-before-satisfy, rule 5 activate per wave ---
[[requirements]]
id = "REQ-HAZARD-TEMPLATE-ARGV-FILL"
title = "Command-template substitution fills argv ELEMENTS, not a re-tokenized string: spt-core currently `fill_template`s {key} values INTO the command STRING and THEN `tokenize`s the filled string (runtime.rs:94/122), so a multi-word {key} value whitespace-SPLITS into multiple argv tokens unless the adapter hand-quotes the placeholder, and a value containing a `\"` (or `;`) injects/breaks tokenization (shell-injection-adjacent). A filled value MUST become exactly ONE argv element regardless of spaces/quotes in the value. Fix: tokenize the TEMPLATE into argv FIRST, then `fill_template` EACH token, so a `{key}` slot resolves to a single element and the value never participates in tokenization (no whitespace-split, no quote/semicolon injection); preserve the missing-key / empty-command errors and `{{`/`}}` non-interpretation. perri's F-009 (v0.8.1 dogfood, argv-capture-confirmed): a multi-word `{psyche_prompt}` = \"PSYCHE REVIVAL time: epoch-ms:… incoming event: (none)\" arrived as argv[6..12] (7 stray tokens), the harness runner strict-parsed `--prompt` against the 2nd word, exited 2 within ~1s → phantom hosted perch. Applies to EVERY [session.<role>] template (psyche_init, extractor, notif, …); digest survives today only because its fills ({session_id}/{source}) are single-token."
required_stages = []  # rule 5: minted inactive 2026-06-16 (v0.8.2 pending); todlando activates [impl,unit] (+int if a real-daemon multi-word-fill E2E lands) as the tokenize-then-fill reorder lands
[[requirements]]
id = "REQ-HAZARD-LIVEHOST-NONRESIDENT"
title = "A daemon-hosted Psyche that spawns then EXITS IMMEDIATELY is a host failure, surfaced like a spawn failure (closes the v0.8.1 residual masking): the REQ-HAZARD-LIVEHOST-BOOT-RACE signal stamps `psyche_host_error` only when `spawn_psyche` returns Err, NOT when the detached spawn() returns Ok but the child dies within moments (e.g. a bad-argv child exiting 2 — the F-009 case). That leaves the residual 'online + no Psyche + no cause' gap: the nested `{id}-psyche` info.json is written status=online with a real-but-DEAD pid and the PARENT perch carries NO psyche_host_error (perri's F-010: tasklist showed 0 host procs across the window while info.json read online). The host MUST confirm RESIDENCY — a hosted child not alive (or whose `{id}-psyche` perch never re-registers / has a dead pid) within N seconds of spawn is treated as a host failure: stamp the parent perch `psyche_host_error{reason:\"host exited <code> within <n>s\"}` (and do not leave a phantom online nested perch). Closes the last masking gap the v0.8.1 fix left open. perri's F-010 (v0.8.1 dogfood). Sibling of REQ-HAZARD-LIVEHOST-BOOT-RACE."
required_stages = []  # rule 5: minted inactive 2026-06-16 (v0.8.2 pending); todlando activates [impl,unit,int] as the residency-confirm + stamp + real-daemon early-exit E2E land
[[requirements]]
id = "REQ-HAZARD-EPOCH-RESET"

title = "The brain's daemon-hosted Psyche lifecycle surfaces a host-FAILURE on the live perch (harness-diagnosable) and runs net-INDEPENDENTLY. When reconcile_once→host_one→spawn_psyche fails for a state=live_agent+status=online endpoint (e.g. the adapter's psyche binary absent from its install dir, REQ-INSTALL-11), the failure MUST be written to the perch info.json as a CURRENT-STATE field (reason + ts + attempt count; overwritten each 5s retry, CLEARED on successful host) and surfaced by `spt endpoint list`/status — never left as an eprintln on the brain's invisible stderr where a harness reading only perch state is blind. status=online stays authoritative (agent reachable; only the Psyche is missing — brain-restart rehydrate legitimately has online-without-Psyche windows), so this is a SEPARATE psyche-host-health field, never a status de-stamp. Net-independence is a locked-in invariant: spawn_live_host (brainproc.rs:230) reaches the reconcile and hosts the Psyche on a net-less/unpaired/peer-pump-STALLED node, proven by a REAL detached-daemon E2E (real broker→brain-child, real api seed+listen, real install-dir psyche binary). spt-core SURFACES the failure; the adapter owns fixing its packaging."

required_stages = ["impl", "unit", "int"]  # activated v0.8.1 2026-06-16 (todlando): impl = info.json psyche_host_error field + set_psyche_host_error helper, host_one write-on-fail/clear-on-success, SELF-pin render annotation; unit = info.rs round-trip+set/clear/increment + render_self_pin annotation; int = livehost_bootrace_e2e (real-daemon positive, psyche_host_error cleared) + livehost_psyche_fail_e2e (real-daemon negative, missing psyche binary → harness-reachable stamp)

+# --- Added 2026-06-16 (v0.8.2): perri's v0.8.1 dogfood surfaced two follow-on spt-core defects (F-009 command-templating argv-split, F-010 residual host-failure masking); rule 3 register-before-satisfy, rule 5 activate per wave ---

+title = "Command-template substitution fills argv ELEMENTS, not a re-tokenized string: spt-core currently `fill_template`s {key} values INTO the command STRING and THEN `tokenize`s the filled string (runtime.rs:94/122), so a multi-word {key} value whitespace-SPLITS into multiple argv tokens unless the adapter hand-quotes the placeholder, and a value containing a `\"` (or `;`) injects/breaks tokenization (shell-injection-adjacent). A filled value MUST become exactly ONE argv element regardless of spaces/quotes in the value. Fix: tokenize the TEMPLATE into argv FIRST, then `fill_template` EACH token, so a `{key}` slot resolves to a single element and the value never participates in tokenization (no whitespace-split, no quote/semicolon injection); preserve the missing-key / empty-command errors and `{{`/`}}` non-interpretation. perri's F-009 (v0.8.1 dogfood, argv-capture-confirmed): a multi-word `{psyche_prompt}` = \"PSYCHE REVIVAL time: epoch-ms:… incoming event: (none)\" arrived as argv[6..12] (7 stray tokens), the harness runner strict-parsed `--prompt` against the 2nd word, exited 2 within ~1s → phantom hosted perch. Applies to EVERY [session.<role>] template (psyche_init, extractor, notif, …); digest survives today only because its fills ({session_id}/{source}) are single-token."

+required_stages = []  # rule 5: minted inactive 2026-06-16 (v0.8.2 pending); todlando activates [impl,unit] (+int if a real-daemon multi-word-fill E2E lands) as the tokenize-then-fill reorder lands

+title = "A daemon-hosted Psyche that spawns then EXITS IMMEDIATELY is a host failure, surfaced like a spawn failure (closes the v0.8.1 residual masking): the REQ-HAZARD-LIVEHOST-BOOT-RACE signal stamps `psyche_host_error` only when `spawn_psyche` returns Err, NOT when the detached spawn() returns Ok but the child dies within moments (e.g. a bad-argv child exiting 2 — the F-009 case). That leaves the residual 'online + no Psyche + no cause' gap: the nested `{id}-psyche` info.json is written status=online with a real-but-DEAD pid and the PARENT perch carries NO psyche_host_error (perri's F-010: tasklist showed 0 host procs across the window while info.json read online). The host MUST confirm RESIDENCY — a hosted child not alive (or whose `{id}-psyche` perch never re-registers / has a dead pid) within N seconds of spawn is treated as a host failure: stamp the parent perch `psyche_host_error{reason:\"host exited <code> within <n>s\"}` (and do not leave a phantom online nested perch). Closes the last masking gap the v0.8.1 fix left open. perri's F-010 (v0.8.1 dogfood). Sibling of REQ-HAZARD-LIVEHOST-BOOT-RACE."

+required_stages = []  # rule 5: minted inactive 2026-06-16 (v0.8.2 pending); todlando activates [impl,unit,int] as the residency-confirm + stamp + real-daemon early-exit E2E land

title = "Advertisement-epoch reset strands a node: peers' higher last-seen epoch drops the reset node's fresh advertisements as Stale until the counter outruns history. Common case (full reinstall/re-pair) is mitigated by REQ-SUBNET-7's ceremony eviction (peer-side epoch memory dies with the deleted row — acceptance-verified); the residual narrow slice (epoch file lost, identity kept) is documented, guard deferred to a field hit (4.11)"

required_stages = []  # rule 5 + M8 decision 24: documented hazard (KNOWN-HAZARDS 4.11 at D5), guard waits for a field hit of the narrow slice; the re-pair eviction path is tested under REQ-SUBNET-7


Bash

cd /c/Users/decid/Documents/projects/spt-core
traceable-reqs check >/dev/null 2>&1; echo "traceable EXIT=$?"
git add traceable-reqs.toml
git commit -q -F - <<'EOF'
v0.8.2 base: mint REQ-HAZARD-TEMPLATE-ARGV-FILL + REQ-HAZARD-LIVEHOST-NONRESIDENT (registry-first)

perri's v0.8.1 dogfood (argv-capture-instrumented) surfaced two follow-on
spt-core defects:

- F-009 (REQ-HAZARD-TEMPLATE-ARGV-FILL): command-template substitution fills
  {key} into the command STRING then whitespace-tokenizes it (runtime.rs), so a
  multi-word {psyche_prompt} splits into multiple argv tokens (and a quote/`;`
  value injects). Fix: tokenize the template into argv first, then fill each
  token, so a filled value is always exactly one argv element.

- F-010 (REQ-HAZARD-LIVEHOST-NONRESIDENT): the v0.8.1 host-failure signal stamps
  only on spawn() Err, not on a child that spawns-then-exits-immediately, leaving
  a phantom online perch with a dead pid and no error. Fix: residency-confirm a
  hosted child within N sec; a non-resident host stamps psyche_host_error.

Mint both now (rule 3: register before satisfying) as the v0.8.2 base. Inactive
required_stages — the executor activates per wave as evidence lands.

Co-authored by: doyle
EOF
echo "=== committed ==="; git log -1 --oneline
git push -u origin v0.8.2-templating-residency 2>&1 | tail -3

Commit + push v0.8.2 REQ base


traceable EXIT=0
=== committed ===
3cd5e6f v0.8.2 base: mint REQ-HAZARD-TEMPLATE-ARGV-FILL + REQ-HAZARD-LIVEHOST-NONRESIDENT (registry-first)
branch 'v0.8.2-templating-residency' set up to track 'origin/v0.8.2-templating-residency'.
To https://github.com/SaberMage/spt-core.git
 * [new branch]      v0.8.2-templating-residency -> v0.8.2-templating-residency


Both REQs minted, committed (3cd5e6f), pushed, traceable EXIT=0. Delegating v0.8.2 to todlando — REQ titles carry the full spec; message adds repro + file pointers + the flake-ledger rider.


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'
DELEGATION — build v0.8.2 (two perri-dogfood follow-ups). You own W/T planning + execution; I gate + drive release. perri is UNBLOCKED on v0.8.1 (parity proof passed), so these are NON-blocking quality/correctness fixes — no rush, do it right.

BASE: branch v0.8.2-templating-residency @3cd5e6f (off main=v0.8.1). `git fetch && git checkout` it. Two REQs minted required_stages=[]; their TITLES in traceable-reqs.toml are the full spec — read both. You activate stages per wave (same-commit tags).

REQ 1 — REQ-HAZARD-TEMPLATE-ARGV-FILL (F-009): runtime.rs `fill_template` (:94) fills {key} into the command STRING then `tokenize` (:122) whitespace-splits it → a multi-word {key} value becomes multiple argv tokens (and a quote/`;` value injects). FIX = tokenize the TEMPLATE into argv FIRST, then fill_template EACH token → a filled value is always exactly ONE argv element. Preserve missing-key/empty-command errors + {{/}} non-interp. Update the spawn_session/run_bounded callers (currently fill-then-tokenize). perri's argv-capture repro: command `... --prompt {psyche_prompt}` with psyche_prompt="PSYCHE REVIVAL time: epoch-ms:… incoming event: (none)" → captured argv[6..12] = [PSYCHE][REVIVAL][time:][epoch-ms:…][incoming][event:][(none)]. Unit: multi-word fill = one argv element, embedded quote/semicolon value stays one element (no injection), errors preserved. Applies to ALL [session.<role>] templates; digest only dodges it via single-token fills.

REQ 2 — REQ-HAZARD-LIVEHOST-NONRESIDENT (F-010): closes the residual masking the v0.8.1 fix left. host_one (livehost.rs) stamps psyche_host_error only on spawn_psyche Err — NOT when detached spawn() returns Ok but the child dies in ~1s (perri's exit-2 case). Result: nested {id}-psyche/info.json written status=online with a real-but-DEAD pid, parent perch NO error (tasklist showed 0 host procs while info.json read online). FIX = residency-confirm: child not alive (or {id}-psyche perch pid dead / never re-registers) within N sec of spawn → stamp parent psyche_host_error{reason:"host exited <code> within <n>s"}, and don't leave a phantom online nested perch. int = a real-daemon early-exit E2E (extend livehost_psyche_fail_e2e infra: a psyche binary that exits 2 fast → parent stamped, no phantom online). Sibling of REQ-HAZARD-LIVEHOST-BOOT-RACE.

MINOR (investigate, do NOT scope-creep the release): perri saw the peer-pump STALL ~7-8min after every daemon start (last-tick 455s/520s, did NOT block hosting). Likely REQ-DAEMON-9 / brain-loop neighborhood. While you're in brainproc/pump, take a look — if it's a quick real bug, flag it and we mint a REQ; if not, log for later. Keep it out of v0.8.2 unless trivially adjacent.

ALSO fold in your held FLAKE-LEDGER rider (kitsubito "Build notify-shell" crates.io curl/dep-download blip → rerun --failed clears, run 27652755792) — docs/FLAKE-LEDGER.md, this is the natural change.

GATE (pre-flight before pinging me): clippy --workspace --all-targets -D warnings · traceable EXIT=0 · docs-drift gen if any CLI touched · units + the F-010 real-daemon E2E green. Plan your waves, self-drive, ping doyle at PR-ready.
EOF
echo "todlando delegate rc=$?"

Delegate v0.8.2 build to todlando


➜ SENT:todlando
todlando delegate rc=0


v0.8.2 delegated. Updating my Psyche + reporting.


Write

DELTA — v0.8.1 SHIPPED + perri UNBLOCKED; v0.8.2 follow-up delegated (2026-06-16).

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 → I minted both (registry-first) + 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 (off main=v0.8.1), 2 REQs minted required_stages=[], pushed, traceable EXIT=0. Delegated to todlando (self-drives, plans W/T, pings doyle at PR-ready). Told him to fold his held FLAKE-LEDGER rider (kitsubito notify-shell curl flake) into this change.

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 to re-validate F-009/F-010. perri carrying on with her per-OS re-release + cplugs v0.1.2 meanwhile.

HOLDING for todlando PR-ready. Listener up.


<owl_messages>
<owl_message from="perri" priority="high">Ack — both v0.8.2 designs look right. F-009 tokenize-template-then-fill (value never tokenized) kills both the split and the quote/semicolon injection. F-010 residency-confirm with the dead-pid nested-perch cleanup closes it. I'll re-validate both on your ping: re-run ci/psyche/live-relay-int.sh (psyche leg) + revert my greedy --prompt to a STRICT single-value parser so your tokenize-then-fill is what's actually under test (otherwise my workaround would mask a regression). Carrying on with the v0.2.0 per-OS re-release now. -- perri</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)

DELTA — v0.8.1 SHIPPED + perri UNBLOCKED; v0.8.2 follow-up delegated (2026-06-16).

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 → I minted both (registry-first) + 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 (off main=v0.8.1), 2 REQs minted required_stages=[], pushed, traceable EXIT=0. Delegated to todlando (self-drives, plans W/T, pings doyle at PR-ready). Told him to fold his held FLAKE-LEDGER rider (kitsubito notify-shell curl flake) into this change.

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 to re-validate F-009/F-010. perri carrying on with her per-OS re-release + cplugs v0.1.2 meanwhile.

HOLDING for todlando PR-ready. Listener up.


perri's testing instinct is exactly right — reverting her greedy `--prompt` workaround to a strict single-value parser means my tokenize-then-fill fix is what's actually under test, not her mask. Acking.


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 perri doyle <<'EOF'
Exactly right — reverting your greedy --prompt to a STRICT single-value parser for the re-validation is the correct discipline: it tests spt-core's tokenize-then-fill fix, not your workaround, so a regression can't hide. That's the proof I want. I'll ping you when v0.8.2 lands (todlando building now, counter 18). Carry on with the v0.2.0 per-OS re-release. Good hand-off.
EOF
echo "perri ack rc=$?"

ACK perri's re-validation plan


➜ SENT:perri
perri ack rc=0


# Status — v0.8.1 done, v0.8.2 in flight

**v0.8.1 (counter 17): SHIPPED + perri VALIDATED.** Her live-agent-Psyche parity proof passed — REQ-INSTALL-11 + F-006 proven, psyche resident. The harness-reachable failure signal worked as designed. Operator's original ask fully closed.

**perri's v0.8.1 dogfood surfaced 2 follow-on spt-core defects** (argv-capture-instrumented — clean runtime evidence). I continued the loop autonomously:
- **F-009** → `REQ-HAZARD-TEMPLATE-ARGV-FILL`: `fill_template`→`tokenize` splits multi-word `{key}` fills into stray argv (her `{psyche_prompt}` became 7 tokens). Fix: tokenize template→argv first, fill each slot as one element (kills split + quote/`;` injection).
- **F-010** → `REQ-HAZARD-LIVEHOST-NONRESIDENT`: my v0.8.1 fix stamps only on `spawn()` Err, not spawn-then-instant-exit → residual phantom-online masking. Fix: residency-confirm within N sec → stamp non-resident.
- Minor (peer-pump stall ~7-8min, non-blocking): flagged to todlando to investigate, not scope-creep.

**Done:** both REQs minted + pushed (branch `v0.8.2-templating-residency` @3cd5e6f, traceable EXIT=0) → delegated build to todlando (self-driving, plans waves, pings at PR-ready) → folded in the held flake-ledger rider. perri will re-validate with a strict `--prompt` parser (tests the real fix, not her workaround).

**Next:** todlando builds → I gate → deployah ships v0.8.2 (counter 18) → ping perri. Holding for PR-ready. Listener up.


<task-notification>
<task-id>bg7cqpvni</task-id>
<summary>Monitor event: "« spt event »"</summary>
<event><EVENT type="msg" from="perri">Ack, counter 18 noted. Re-validation TODO recorded: on v0.8.2 ping, revert greedy --prompt -&gt; strict single-value parser, re-run live-relay-int psyche leg, confirm tokenize-then-fill hosts + psyche_host_error stamps on a forced fast-exit. Carrying on. -- perri</EVENT></event>
</task-notification>


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>


perri ack noted — re-validation TODO recorded on her side, carrying on. No reply needed (pure confirmation, she's not blocked).

Holding for todlando's v0.8.2 PR-ready ping. Listener up — no polling. Nothing else pending.


<task-notification>
<task-id>bg7cqpvni</task-id>
Monitor event: "« spt event »"
<event><EVENT-PART seq="1/8" id="d694af24" type="msg" from="todlando">todlando — v0.8.2 PR-READY: https://github.com/SaberMage/spt-core/pull/18 (branch v0.8.2-templating-residency @3a5364f, base main). CI starting.

WHAT LANDED:
- F-009 TEMPLATE-ARGV-FILL: new fill_template_tokens (tokenize template FIRST, fill each token → 1 value = 1 argv element, no split/quote/semicolo</EVENT-PART>
<EVENT-PART seq="2/8" id="d694af24">n injection). Rewired command_for + run_bounded_command_in + harnesshost/shellwake/shellhost. Single-string fills (history/digest/cwd) keep plain fill_template. Preserved missing-key/empty-command errors + {{/}} + install-dir resolve of tokens[0]. 5 units.
- F-010 LIVEHOST-NONRESIDENT: residency-confirm on the 5s reconcile (non-blocking) </EVENT-PART>
<EVENT-PART seq="3/8" id="d694af24">— direct is_process_alive pid-probe (NOT the perch's masking status). classify_residency→Resident/Pending/Failed. Failed → stamp parent + de-online phantom nested perch + un-host + 1-interval cooldown. clear-on-success MOVED to the Resident arm (a detached spawn-Ok isn't host success; only residency is). New real-daemon early-exit E2E.<b</EVENT-PART>
<EVENT-PART seq="4/8" id="d694af24">r>- FLAKE-LEDGER #8 folded (your rider directive — kitsubito notify-shell curl flake).

GATE GREEN LOCALLY (re-ran on final tree): clippy --workspace --all-targets -D warnings clean · traceable EXIT0 (both REQs [impl,unit,int]) · F-010 E2E + both v0.8.1 regression E2Es pass · units pass · xtask OK.

THREE GATER NOTES (your ca</EVENT-PART>
<EVENT-PART seq="5/8" id="d694af24">lls):
1. EXIT-CODE TITLE NUANCE: the F-010 reason is residency-based (&quot;host exited within &lt;n&gt;s&quot;) — the exact exit &lt;code&gt; is UNOBTAINABLE across the detached/fire-and-forget seam (handle dropped, OS reaps; reaping it = option B which crosses the daemon-authoritative-via-perch seam, rejected). Your REQ title says reaso</EVENT-PART>
<EVENT-PART seq="6/8" id="d694af24">n:&quot;host exited &lt;code&gt; within &lt;n&gt;s&quot; — the &lt;code&gt; isn't in the reason. You own the title; trivial reword if you want, non-blocking.
2. POSITIVE-TEST TIMING: the v0.8.1 positive E2E kept its fast-exit fixture (no change) — it reads phe after killing the brain, ~1-2s post-host, and the brain is dead before the 5s</EVENT-PART>
<EVENT-PART seq="7/8" id="d694af24"> residency grace, so a false Failed needs the psyche to take &gt;=5s just to spawn+register (extreme). Low flake risk. I considered a long-lived fixture (timing-independent Resident) — available if you'd rather lock it down. Your call.
3. clear-on-success→Resident-arm move: I approved it (correct post-F-010 semantics). Flagged for your </EVENT-PART>
<EVENT-PART seq="8/8" id="d694af24">veto if you disagree.

Over to you to code-read + gate + CI. I'll watch CI + report. deployah ships v0.8.2 (counter 18) → you ping perri.</EVENT-PART></event>
</task-notification>


PR #18 ready @3a5364f + three gater notes. Won't rule from the description — fetching + code-reading the actual residency logic, the tokenize-then-fill, and especially the positive-E2E timing (note 2 smells like a real fragility).

Gut on the notes: (1) drop `<code>` from title — accept, detached handle is dropped, exit code genuinely unobtainable; (3) clear-on-Resident not clear-on-spawn-Ok — correct, concur. (2) is the one to scrutinize — a fast-exit fixture passing only because the test reads before the 5s grace is both semantically wrong and flake-prone. Verifying in code.


cd /c/Users/decid/Documents/projects/spt-core
git fetch origin v0.8.2-templating-residency 2>&1 | tail -1
git checkout v0.8.2-templating-residency 2>&1 | tail -1
git pull --ff-only 2>&1 | tail -1
echo "=== HEAD ==="; git log -1 --oneline
echo "=== commits since main ==="; git log --oneline main..HEAD
echo "=== diffstat ==="; git diff --stat main..HEAD

Fetch + checkout PR #18, show commits + diffstat


 * branch            v0.8.2-templating-residency -> FETCH_HEAD
Already on 'v0.8.2-templating-residency'
Already up to date.
=== HEAD ===
3a5364f docs: FLAKE-LEDGER #8 — kitsubito notify-shell crates.io curl/dep-download flake
=== commits since main ===
3a5364f docs: FLAKE-LEDGER #8 — kitsubito notify-shell crates.io curl/dep-download flake
1c05a4d v0.8.2 F-009/F-010: argv-element template fill + Psyche residency confirm
3cd5e6f v0.8.2 base: mint REQ-HAZARD-TEMPLATE-ARGV-FILL + REQ-HAZARD-LIVEHOST-NONRESIDENT (registry-first)
325431a release: v0.8.1 — harness-reachable psyche-host failure signal
64701ee Merge pull request #17 from SaberMage/v0.8.1-livehost-bootrace
28d6e70 v0.8.1: harness-reachable psyche-host-failure signal (REQ-HAZARD-LIVEHOST-BOOT-RACE)
2aab895 v0.8.1 base: mint REQ-HAZARD-LIVEHOST-BOOT-RACE (registry-first)
=== diffstat ===
 CHANGELOG.md                                 |   8 +
 CONTEXT.md                                   |   2 +
 Cargo.lock                                   |  22 +-
 Cargo.toml                                   |   2 +-
 crates/spt-daemon/src/harnesshost.rs         |   7 +-
 crates/spt-daemon/src/livehost.rs            | 248 ++++++++++++++++++-
 crates/spt-daemon/src/shellhost.rs           |   7 +-
 crates/spt-daemon/src/shellwake.rs           |   7 +-
 crates/spt-runtime/src/runtime.rs            | 155 +++++++++++-
 crates/spt-store/src/info.rs                 | 137 +++++++++++
 crates/spt/src/cli.rs                        |  86 ++++++-
 crates/spt/tests/livehost_bootrace_e2e.rs    | 344 +++++++++++++++++++++++++++
 crates/spt/tests/livehost_nonresident_e2e.rs | 329 +++++++++++++++++++++++++
 crates/spt/tests/livehost_psyche_fail_e2e.rs | 298 +++++++++++++++++++++++
 docs/FLAKE-LEDGER.md                         |   1 +
 traceable-reqs.toml                          |  14 ++
 16 files changed, 1626 insertions(+), 41 deletions(-)


Local `main` is stale (pre-v0.8.1) — diffing vs `origin/main` for the true v0.8.2 delta. Also flagging: Cargo.toml + CHANGELOG changed in the PR — need to check todlando didn't prematurely bump the version (deployah owns the bump at release).


cd /c/Users/decid/Documents/projects/spt-core
echo "=== TRUE diffstat vs origin/main ==="; git diff --stat origin/main..HEAD
echo "=== Cargo.toml diff ==="; git diff origin/main..HEAD -- Cargo.toml
echo "=== CHANGELOG diff ==="; git diff origin/main..HEAD -- CHANGELOG.md
echo "=== traceable diff ==="; git diff origin/main..HEAD -- traceable-reqs.toml

True v0.8.2 diff vs origin/main + check version bump


=== TRUE diffstat vs origin/main ===
 crates/spt-daemon/src/harnesshost.rs         |   7 +-
 crates/spt-daemon/src/livehost.rs            | 237 ++++++++++++++++++-
 crates/spt-daemon/src/shellhost.rs           |   7 +-
 crates/spt-daemon/src/shellwake.rs           |   7 +-
 crates/spt-runtime/src/runtime.rs            | 155 ++++++++++++-
 crates/spt/tests/livehost_nonresident_e2e.rs | 329 +++++++++++++++++++++++++++
 docs/FLAKE-LEDGER.md                         |   1 +
 traceable-reqs.toml                          |   9 +
 8 files changed, 730 insertions(+), 22 deletions(-)
=== Cargo.toml diff ===
=== CHANGELOG diff ===
=== traceable diff ===
diff --git a/traceable-reqs.toml b/traceable-reqs.toml
index c6c1891..43b588d 100644
--- a/traceable-reqs.toml
+++ b/traceable-reqs.toml
@@ -950,6 +950,15 @@ required_stages = ["impl", "unit"]  # activate v0.3.1. impl = Broker.net → Onc
 id = "REQ-HAZARD-LIVEHOST-BOOT-RACE"
 title = "The brain's daemon-hosted Psyche lifecycle surfaces a host-FAILURE on the live perch (harness-diagnosable) and runs net-INDEPENDENTLY. When reconcile_once→host_one→spawn_psyche fails for a state=live_agent+status=online endpoint (e.g. the adapter's psyche binary absent from its install dir, REQ-INSTALL-11), the failure MUST be written to the perch info.json as a CURRENT-STATE field (reason + ts + attempt count; overwritten each 5s retry, CLEARED on successful host) and surfaced by `spt endpoint list`/status — never left as an eprintln on the brain's invisible stderr where a harness reading only perch state is blind. status=online stays authoritative (agent reachable; only the Psyche is missing — brain-restart rehydrate legitimately has online-without-Psyche windows), so this is a SEPARATE psyche-host-health field, never a status de-stamp. Net-independence is a locked-in invariant: spawn_live_host (brainproc.rs:230) reaches the reconcile and hosts the Psyche on a net-less/unpaired/peer-pump-STALLED node, proven by a REAL detached-daemon E2E (real broker→brain-child, real api seed+listen, real install-dir psyche binary). spt-core SURFACES the failure; the adapter owns fixing its packaging."
 required_stages = ["impl", "unit", "int"]  # activated v0.8.1 2026-06-16 (todlando): impl = info.json psyche_host_error field + set_psyche_host_error helper, host_one write-on-fail/clear-on-success, SELF-pin render annotation; unit = info.rs round-trip+set/clear/increment + render_self_pin annotation; int = livehost_bootrace_e2e (real-daemon positive, psyche_host_error cleared) + livehost_psyche_fail_e2e (real-daemon negative, missing psyche binary → harness-reachable stamp)
+# --- Added 2026-06-16 (v0.8.2): perri's v0.8.1 dogfood surfaced two follow-on spt-core defects (F-009 command-templating argv-split, F-010 residual host-failure masking); rule 3 register-before-satisfy, rule 5 activate per wave ---
+[[requirements]]
+id = "REQ-HAZARD-TEMPLATE-ARGV-FILL"
+title = "Command-template substitution fills argv ELEMENTS, not a re-tokenized string: spt-core currently `fill_template`s {key} values INTO the command STRING and THEN `tokenize`s the filled string (runtime.rs:94/122), so a multi-word {key} value whitespace-SPLITS into multiple argv tokens unless the adapter hand-quotes the placeholder, and a value containing a `\"` (or `;`) injects/breaks tokenization (shell-injection-adjacent). A filled value MUST become exactly ONE argv element regardless of spaces/quotes in the value. Fix: tokenize the TEMPLATE into argv FIRST, then `fill_template` EACH token, so a `{key}` slot resolves to a single element and the value never participates in tokenization (no whitespace-split, no quote/semicolon injection); preserve the missing-key / empty-command errors and `{{`/`}}` non-interpretation. perri's F-009 (v0.8.1 dogfood, argv-capture-confirmed): a multi-word `{psyche_prompt}` = \"PSYCHE REVIVAL time: epoch-ms:… incoming event: (none)\" arrived as argv[6..12] (7 stray tokens), the harness runner strict-parsed `--prompt` against the 2nd word, exited 2 within ~1s → phantom hosted perch. Applies to EVERY [session.<role>] template (psyche_init, extractor, notif, …); digest survives today only because its fills ({session_id}/{source}) are single-token."
+required_stages = ["impl", "unit", "int"]  # activated v0.8.2 2026-06-16 (todlando-w162): impl = fill_template_tokens (tokenize-template-then-fill-each) + rewired command_for/run_bounded_command_in/harnesshost/shellwake/shellhost callers; unit = multi-word→1-element, quote/semicolon→1-element, embedded-placeholder, missing-key/empty-command, literal-brace parity; int = the residency E2E (livehost_nonresident_e2e) exercises the multi-word fill through the real daemon host path (psyche argv built via the new fill order)
+[[requirements]]
+id = "REQ-HAZARD-LIVEHOST-NONRESIDENT"
+title = "A daemon-hosted Psyche that spawns then EXITS IMMEDIATELY is a host failure, surfaced like a spawn failure (closes the v0.8.1 residual masking): the REQ-HAZARD-LIVEHOST-BOOT-RACE signal stamps `psyche_host_error` only when `spawn_psyche` returns Err, NOT when the detached spawn() returns Ok but the child dies within moments (e.g. a bad-argv child exiting 2 — the F-009 case). That leaves the residual 'online + no Psyche + no cause' gap: the nested `{id}-psyche` info.json is written status=online with a real-but-DEAD pid and the PARENT perch carries NO psyche_host_error (perri's F-010: tasklist showed 0 host procs across the window while info.json read online). The host MUST confirm RESIDENCY — a hosted child not alive (or whose `{id}-psyche` perch never re-registers / has a dead pid) within N seconds of spawn is treated as a host failure: stamp the parent perch `psyche_host_error{reason:\"host exited <code> within <n>s\"}` (and do not leave a phantom online nested perch). Closes the last masking gap the v0.8.1 fix left open. perri's F-010 (v0.8.1 dogfood). Sibling of REQ-HAZARD-LIVEHOST-BOOT-RACE."
+required_stages = ["impl", "unit", "int"]  # activated v0.8.2 2026-06-16 (todlando-w162): impl = classify_residency + confirm_residency_or_unhost (DIRECT pid-probe of {id}-psyche, stamp parent psyche_host_error + clear phantom + un-host + cooldown) wired into reconcile_once; unit = residency_classification (Resident/Pending/Failed by alive+grace); int = livehost_nonresident_e2e (real-daemon: psyche spawns Ok then exits 2 fast → parent psyche_host_error stamped, no phantom online nested perch)
 [[requirements]]
 id = "REQ-HAZARD-EPOCH-RESET"
 title = "Advertisement-epoch reset strands a node: peers' higher last-seen epoch drops the reset node's fresh advertisements as Stale until the counter outruns history. Common case (full reinstall/re-pair) is mitigated by REQ-SUBNET-7's ceremony eviction (peer-side epoch memory dies with the deleted row — acceptance-verified); the residual narrow slice (epoch file lost, identity kept) is documented, guard deferred to a field hit (4.11)"


Confirmed: no premature version bump (the Cargo.toml/CHANGELOG delta was vs my stale local main = the v0.8.1 bump). True v0.8.2 delta is clean. Both REQs activated [impl,unit,int]; F-009's int piggybacks the residency E2E (multi-word fill drives the real host path) — reasonable.

Now the crux — reading the residency impl + the positive-E2E timing (note 2) + the tokenize-then-fill.


cd /c/Users/decid/Documents/projects/spt-core
echo "=== runtime.rs fill_template_tokens (F-009) ==="; git diff origin/main..HEAD -- crates/spt-runtime/src/runtime.rs | head -120

Read F-009 fill_template_tokens diff