---
name: v082-templating-residency
description: "v0.8.2 task — two perri-dogfood follow-ups (non-blocking quality): F-009 REQ-HAZARD-TEMPLATE-ARGV-FILL (tokenize-then-fill argv) + F-010 REQ-HAZARD-LIVEHOST-NONRESIDENT (residency-confirm). doyle delegation, I own waves+exec, doyle gates+release. Branch v0.8.2-templating-residency @3cd5e6f."
metadata: 
  node_type: memory
  type: project
  originSessionId: 8350d43e-fe2d-4e24-9999-eecbcc6d3a50
---

**v0.8.2 templating+residency** — doyle delegation (operator-directed), I own W/T + execution, doyle gates + drives release. NON-blocking quality fixes (perri UNBLOCKED on v0.8.1, parity proof passed — no rush, do it right). Branch `v0.8.2-templating-residency` off main (v0.8.1), base @3cd5e6f. Two REQs minted required_stages=[] (titles = full spec); I activate [impl,unit,int] per wave, same-commit tags. Sibling of [[v081-livehost-bootrace]]. doyle drove v0.8.1 publish (counter 17).

**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 → a multi-word {key} value becomes MULTIPLE argv tokens (+ a `"`/`;` value injects/breaks tokenization). FIX = tokenize the TEMPLATE into argv FIRST, then `fill_template` EACH token → a filled value is ALWAYS exactly ONE argv element (no whitespace-split, no quote/semicolon injection). PRESERVE: missing-key / empty-command errors + `{{`/`}}` non-interp + the install-dir program resolution (resolve_program_in_dir on the program token still works). Update spawn_session/run_bounded callers (currently fill-then-tokenize). Applies to ALL [session.<role>] templates (psyche_init, extractor, notif…); digest only dodges today via single-token fills ({session_id}/{source}). UNIT: multi-word fill = 1 argv element; embedded quote/semicolon value stays 1 element (no injection); errors preserved. perri repro: `--prompt {psyche_prompt}` w/ "PSYCHE REVIVAL time: epoch-ms:… incoming event: (none)" → argv[6..12] = 7 stray tokens → harness strict-parse `--prompt`=2nd word → exit 2 ~1s → phantom hosted perch.

**REQ 2 — REQ-HAZARD-LIVEHOST-NONRESIDENT (F-010):** closes the residual masking v0.8.1 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, the F-009 consequence). Result: nested {id}-psyche/info.json written status=online with a real-but-DEAD pid; PARENT perch has NO psyche_host_error (perri: tasklist 0 host procs while info.json reads online). FIX = RESIDENCY-CONFIRM: a hosted child not alive (or whose {id}-psyche perch never re-registers / has a dead pid) within N sec of spawn → host failure → stamp PARENT psyche_host_error{reason:"host exited <code> within <n>s"} + do NOT leave a phantom online nested perch (clean it). int = real-daemon early-exit E2E (extend livehost_psyche_fail_e2e infra: a psyche binary that exits 2 fast → parent stamped, no phantom online).
DESIGN STEER: integrate the residency check into the existing 5s reconcile loop (it already iterates perches + has stop-side logic) — non-blocking; don't block the sweep with a long wait. spawn_psyche detaches (fire-and-forget, handle dropped, liveness via the {id}-psyche perch), so residency = the {id}-psyche perch alive within N sec, checked on a subsequent tick. host_one knows the spawned pid (h.pid).

**MINOR (investigate, do NOT scope-creep the release):** perri saw peer-pump STALL ~7-8min after every daemon start (last-tick 455s/520s, did NOT block hosting). Likely REQ-DAEMON-9 / brain-loop [truncated at msg part 8/10 — owl cap; doyle's tail = gate criteria I know + this MINOR]. → investigate, log/defer, don't block v0.8.2.

**PLAN:** delegate the build (both REQs, one agent, full budget — my context deep). FINALIZE (on me): activate both REQs [impl,unit,int] (agent does, I verify); FOLD the PENDING FLAKE-LEDGER entry into this PR (doyle: rider on next natural change — v0.8.2 IS it): docs/FLAKE-LEDGER.md += kitsubito "Build notify-shell" crates.io curl/dep-download blip → `gh run rerun --failed` clears (run 27652755792, v0.8.1 PR #17). Then FULL gate (clippy --workspace --all-targets -D warnings, traceable EXIT0, the E2Es, docs-drift xtask check) → push + PR → ping doyle PR-ready → doyle gates → deployah ships v0.8.2 (counter 18) → doyle pings perri.

**DESIGN RULING (F-010 residency, build-agent asked):** the v0.8.1 positive E2E fixture (`psychebin --version`, exit-0-FAST) would be marked non-resident by residency=live-pid → break its `phe.is_none()` assert. RULED **(A): change the positive fixture to a LONG-LIVED psyche** (sleeps, killed at teardown) — a real Psyche companion IS long-lived (CONTEXT: owns its perch, communes, exits at session-end); `--version` was an unrealistic stand-in. residency=live-pid is the right production invariant; (A) preserves+improves v0.8.1 coverage (touch fixture, not asserts). **REJECTED (B)** (un-detach to reap exit code) — crosses the detached/fire-and-forget seam; liveness is daemon-authoritative VIA THE PERCH not the child handle ([[gate-against-documented-design]] — don't let impl contradict locked design). EXIT-CODE CAVEAT: exact `<code>` UNOBTAINABLE across the detach seam (handle dropped, OS reaps) → reason is residency-based "host not resident within <n>s (pid dead)", NOT the literal code in the title — FLAG to doyle at PR-ready (he owns the title; non-blocking). N grace ≈ 5s (one reconcile tick). On non-residency: stamp PARENT + CLEAN phantom nested {id}-psyche perch + un-host; PARENT status=online stays authoritative. E2E = 3 cases (positive long-lived / negative-missing-binary / negative-early-exit-2).

**PR-READY @3a5364f → PR #18 (base main), pinged doyle.** Build by subagent (1c05a4d: F-009 fill_template_tokens + F-010 residency/classify_residency/cooldown/clear-on-Resident-arm; agent resolved the F-010-vs-positive-regression conflict via timing-margin NOT fixture-change — positive test reads phe AFTER killing brain ~1-2s post-host, brain dead before the 5s grace, so a false-Failed needs the --version psyche to take >=5s to spawn+register = extreme → low flake risk; my long-lived-fixture hardening steer arrived after it finished, didn't take — I ACCEPTED timing-margin on review after the brain-killed-before-grace analysis, flagged to doyle as his call). I added FLAKE-LEDGER #8 (3a5364f, doyle rider). Verified runtime seam myself (fill_template_tokens correct; command_for split_first→resolve_program_in_dir on tokens[0] intact; cwd keeps plain fill). Re-ran gate: clippy clean · traceable EXIT0 (both [impl,unit,int]) · F-010 + both v0.8.1 regression E2Es pass · xtask OK. THREE GATER NOTES sent: (1) exit <code> NOT in F-010 reason (unobtainable across detach seam — title says "<code>", reason is residency-based; doyle owns title, non-blocking); (2) positive-test timing-dependence (low risk, long-lived fixture available if doyle wants); (3) clear-on-success→Resident-arm move (I approved, correct semantics). **doyle GATE = CONDITIONAL PASS, 1 REQUIRED re-spin.** Code-read strong (F-009 ✓, F-010 residency/direct-probe/clear-on-Resident/de-phantom/cooldown ✓, negative E2E ✓). NOTE 3 (clear-on-Resident) ACCEPTED. NOTE 1 (title <code>): doyle ratified reword `host exited <code> within <n>s` → `host not resident within <n>s (psyche perch missing/dead pid)` — I apply in re-spin (title + impl reason + nonresident-E2E assertion, all matching). NOTE 2 (REQUIRED BLOCKER): the positive E2E's fast-exit fixture is a hollow positive (post-F-010 it's a FAILED host that only passes by killing the brain before grace) + flake-prone on kitsubito + RESIDENT path has ZERO E2E coverage. FIX: long-lived resident psyche fixture (sleeps ~20s past grace), wait past grace+1 tick (~7-8s), assert {id}-psyche pid ALIVE (Resident, is_process_alive) + parent phe None. (= my original hardening instinct; I wrongly ACCEPTED the timing-margin on review — doyle's deeper rationale = Resident-path coverage gap. LESSON: a "passing" positive test that passes for the WRONG reason (Pending not Resident) proves nothing — verify a positive asserts the RIGHT state.) RE-SPIN delegated to fresh agent aac341f2 (long-lived fixture candidate: psychebin=spt-copy invoked `ready <throwaway>` blocks, OR sleeper; keep install-dir resolution fidelity). The @3a5364f CI watch (b062fn3uf) is MOOT (re-spin supersedes). **RE-SPIN DONE + PUSHED @0aad7b9 (PR #18), pinged doyle re-gate.** Agent aac341f2 did both: Note1 reason reword across livehost.rs (Failed reason="host not resident within 5s (psyche perch missing/dead pid)") + title + nonresident-E2E assertion; Note2 positive E2E reworked → fixture `psychebin ready <throwaway>` (spt-copy `ready`=persistent blocking listener, bare-token install-dir resolved, namespaced id), residency-confirm phase waits ~13s (grace 5s + 1 tick) → read_pid→is_process_alive asserts pid ALIVE (Resident) + phe None. I verified the residency phase + re-ran FULL gate green (clippy clean, traceable EXIT0, reworked positive pass psyche_alive=true/phe=None/21s, nonresident pass reason=ratified, v0.8.1 regression pass, units pass, xtask OK). RE-SPIN CI (@0aad7b9) RED on Windows: daemon_lifecycle_real_brain.rs:111 (1 failed/1112) — F-010 SIBLING REGRESSION I missed. That M11-W0.4 real-brain int used a NOOP fast-exit psyche (cmd /C rem / true) + runs the CONTINUOUS real-brain reconcile; post-F-010 confirm_residency_or_unhost classifies it Failed ~grace+tick → stop_host tears down the pulse driver → the commune-ingest assert (line 111) times out. Linux won the ingest-vs-teardown race + passed, Windows lost → cross-platform race. doyle + I independently diagnosed identical. **FIX @3547a17:** swap NOOP → long-lived resident (`ping -n 99999 127.0.0.1` / `sleep 99999`) so host stays Resident → pulse loop lives → commune ingests deterministically BOTH runners; reap psyche pid scoped (read_pid from nested perch). spt-daemon tests have NO CARGO_BIN_EXE_spt so a shell blocker (not psychebin) is right; no install-dir fidelity needed here (bootrace's job). CI confirmed SOLE breakage (1/1112) → single fix closes it. Local gate green (clippy/traceable/daemon_lifecycle_real_brain 6.2s/both E2Es). **LESSON: targeted preflight (livehost E2Es only) missed a spt-daemon SIBLING test running the same continuous reconcile — the test-analog of [[ci-clippy-preflight-workspace]]. When a behavior change touches a shared seam (residency in reconcile), grep ALL tests exercising that seam (run_brain/spawn_live_host/real daemon) + run them, not just the new ones.** **FIX CI GREEN both runners** (run 27658850682, @3547a17): test+n1-gate kitsubito/hfenduleam + traceability all success (the Windows test that failed @0aad7b9 now green); twohost skipped. PR #18 @3547a17 FULLY GATE-READY (full local gate + CI green both runners; F-009 + F-010 + resident-positive E2E + sibling fix all land). Reported to doyle. **doyle RE-GATE PASS @3547a17 → deployah RELEASE GO (counter 18, PATCH).** Both regressions closed; doyle pings perri on publish+hashes. I STOOD DOWN. **PENDING RIDER (doyle non-blocking nit, fold into next change touching this test):** daemon_lifecycle_real_brain `kill_pid` lacks `.no_window()` → momentary console flash on Win CI (harmless). The other spt tests use CommandNoWindowExt via `mod common`; this spt-daemon test doesn't import it. Add `.no_window()` (or the common ext) next time. NEXT: deployah publishes v0.8.2 → doyle pings perri. Nothing pending on my side. (Session arc: how-to-live PR#16 → v0.8.0 merge → v0.8.1 → v0.8.2 PR#18.) **CLOSED 2026-06-16: perri v0.8.2 RE-VALIDATION BOTH PASS (clean, no new gaps).** F-009 PROVEN via argv-capture (raw argv = exactly 7 elements, --prompt value = full multi-line prompt as ONE element w/ newlines intact; 0.8.1 split same into 7 word-tokens) — perri deployed STRICT single-value --prompt parser so the fix itself is under test. F-010 PROVEN: spawn-then-exit(2) psyche → daemon stamped parent psyche_host_error{reason:"host not resident within 5s (psyche perch missing/dead pid)", attempts:2}; endpoint list+whoami render psyche-host: FAILED; status stays live_agent/online (liveness authoritative, error additive+harness-reachable); no phantom. perri keeps greedy --prompt defensive across 0.8.1/0.8.2 (commit b6d599e, restored published v0.2.0 greedy binary). I ACKed + told her keep going. v0.8.2 DONE end-to-end.

**GATE (pre-flight before pinging doyle):** clippy --workspace --all-targets -D warnings · traceable EXIT0 · docs-drift xtask check (gen if CLI touched — likely NOT) · the new/extended E2Es + units green.
