---
name: m11-w0-daemon-lifecycle
description: M11-W0 daemon-lifecycle consolidation COMPLETE 2026-06-15 — REQ-DAEMON-1 honestly done; Psyche/pulse moved from the interim api-listen process into the daemon brain
metadata: 
  node_type: memory
  type: project
  originSessionId: bd40094b-1564-4163-b721-5b6eb951f011
---

**M11-W0 (daemon-lifecycle consolidation) COMPLETE 2026-06-15** on branch `m11-shell-substrate` (PR #16). doyle gated wave-by-wave, todlando built, CI-green both runners each wave.

**The finding (operator escalated):** the LiveAgent Psyche/pulse loop was running in the INTERIM `api listen` relay PROCESS, not the daemon brain where the design puts it (CONTEXT:30/34/194 — "all Psyche/pulse loops" in the brain; Psyche = "a loop inside the daemon", liveness daemon-authoritative). REQ-DAEMON-1's `int` was a TYPE-LEVEL false-green: `daemon_e2e` drove `BrainLifecycle` directly in-test; `brainproc::run_brain` had a no-op ("forward-correct when daemon-hosted sessions land (the live-agent adapter)" = perri's claude-spt). Consequences: spt-hosted (bind, no listen) live agents got NO Psyche; liveness was relay-process-bound. METHOD WIN: grepping `REQ-DAEMON-1` tags (see [[grep-req-tags-to-find-impl]]) showed the machinery built+E2E-tested but production-activation staged — faster + truer than the agent trace I burned first.

**The fix (W0.1-W0.4, plan = DAEMON-LIFECYCLE-COMPLETION-PLAN.md on main):**
- W0.1: `crate::livehost::reconcile_once` (shellwake mirror) — the brain hosts the lifecycle; `run_pulse_loop` production caller wired into `brainproc::run_brain`. Full set-diff: host (online∧¬tabled) / un-host (tabled∧¬online → stop+join+de-table) / leave-alone. ONLINE-ONLY, daemon-authoritative.
- W0.2: cutover — retire the interim `cmd_listen` spawn_psyche/pulse, `cmd_listen` becomes a pure dumb-pipe `Relay`, brain is sole first-host (establish-marks-online → reconcile hosts ≤1 tick). Parity exact-by-construction (LiveHost = forwarding newtype over BrainLifecycle).
- W0.3: spt-hosted `cmd_bind` marks online for live-capable → reconcile hosts. Closes the bind-no-Psyche gap.
- W0.4: (iii) lost-update — `mutate_info` funnel over all 6 info.json RMW setters, `fs2` `lock_exclusive` on a STABLE `<perch>/.info.lock` sentinel (NOT info.json — tmp+rename swaps the inode = fake lock); readers lock-free; hammer test. (ii) read-error un-host (perch-dir-gone vs transient-read-None). (i) int RE-POINT — MOVED `[int->REQ-DAEMON-1]` off `daemon_e2e` onto `daemon_lifecycle_real_brain.rs` (real Broker + `run_brain` thread hosts the Psyche) — the M3 false-green is dead.

**LESSON (mine, feedback-worthy):** the (iii) A-vs-B fix oscillated A→B+→A→B+→A across crossing async messages — I re-ruled each time new field-enumeration data arrived (set_rest freq, then 6-writer count) when BOTH lock(A) and field-split(B+) were correct, complete fixes. Whipsawed todlando. Should have locked the FIRST correct answer + treated new data as confirm-or-genuinely-blocking, not re-open — especially across async crossings. Converged on A (mutate_info + fs2 sentinel) = what todlando was building.

**NEXT:** W0.5 = perri cutover (claude-spt :live onto the decoupled path, ZERO adapter change, when perri's :live build is ready — she's gated on operator self-clear). M11 shell waves resume: W2 (drive REQ-SHELL-3) → W3 (tunnel) → W4 (Gateway capstone) → W5 (rig+docs). W0 not yet released — rides the next M11/W0 merge to main + tag.
