---
status: root_cause_found
trigger: |
  Two recurring post-/clear misbehaviors by live agents, both suspected instructional:
  (1) When the Psyche is busy as a live agent resumes after /clear, the agent perceives the
      Psyche as offline and tries to "fix" a non-bug.
  (2) A live agent tries to restart its own poll after /clear. The poll listener PROCESS
      survives /clear, so the re-arm fails (DUPLICATE). The agent reads that as a "stale"
      listener, runs `$LIVE stop` (which tears down the Psyche), then revives with bare
      `$OWL poll` (no --live) — leaving the Psyche permanently out of commission.
created: 2026-06-02
updated: 2026-06-02
---

# Debug Session: post-clear-poll-psyche-misread

## Symptoms

- **Expected**: After /clear, a live agent re-orients without misreading its own poll
  listener or its Psyche's liveness, and without tearing down the Psyche.
- **Actual**:
  - (1) Agent sees Psyche as "offline" while it is busy → troubleshoots a non-bug.
  - (2) Agent re-arms poll → DUPLICATE → concludes "stale" → `$LIVE stop` (kills Psyche)
        → revives with `$OWL poll` (no --live) → Psyche stays dead.
- **Errors**: DUPLICATE/COLLISION on re-arm; no crash. User-visible symptom is a dead Psyche.
- **Timeline**: Recurring; surfaces on every self-clear / post-/clear wake.
- **Reproduction**: Live agent runs /clear, resumes work, inspects `$OWL list`, acts on it.

## CORRECTION (user pushback, 2026-06-02 — supersedes first-pass diagnosis below)

First pass mis-framed BOTH. User corrected:
- **P1 is a REAL code bug, not "psyches hidden by default".** `$OWL list` DOES show
  psyches (as a child under Self). The Psyche goes MISSING specifically while its wrapper is
  blocked ingesting a commune. Mechanism: wrapper-owned psyche perch carries a numeric `pid`
  (the inner poll subprocess) and NO `parent_pid` (types.rs:182-186). During ingest the inner
  poll has exited → pid dead, no parent fallback → liveness fails → perch drops from
  `$OWL list` (collect) and, worse, `$LIVE list-psyches` soft-cleans its `ready` sentinel →
  wrapper exits on next poll ("ready file gone before poll, exiting", wrapper/mod.rs:1173) →
  Psyche dead for good.
- **P2: listener survives /clear INTACT, not orphaned.** Agent should do NOTHING — no kill,
  no re-arm, no new listener. The first-pass "kill-orphan + re-arm" protocol was WRONG.

## ACTUAL FIXES APPLIED

1. **P1 (code) — `src/common/list_filter.rs::collect`**: psyche perches are liveness-
   authoritative on their WRAPPER. Added `wrapper_backed_alive` (is_wrapper_alive on the
   `-psyche`-stripped id). When true → force ONLINE and SKIP the stale soft-clean branch, so
   a read command can never remove `ready` under a live wrapper. Regression test
   `live_wrapper_keeps_busy_psyche_online_and_ready_intact`.
2. **P1 (code) — `src/live/list_psyches.rs`**: same wrapper-alive guard around the
   destructive `fs::remove_file(ready)` soft-clean — `$LIVE list-psyches` no longer kills a
   busy Psyche.
3. **P2 + P1 (instructional) — `src/owl/resume.rs` reorientation text**: corrected
   `pid:"BUSY"` = ONLINE (was "dead"); replaced the wrong orphan/kill protocol with "after
   /clear DO NOTHING — listener survives intact; DUPLICATE on re-arm = proof it's alive";
   live-aware re-arm via `{poll_cmd}` (`listen --live` for live agents); Psyche-liveness note
   (appears as child in `$OWL list`, may briefly drop during commune ingest = transient,
   never `$LIVE stop`).

## ROOT CAUSE (confirmed via code read)

Single source: the SessionStart reorientation block emitted by
`src/owl/resume.rs::inject_reorientation` (the `<spacetime-reorientation>` text injected
into every agent after /clear). It carries two instructional defects:

### Defect A — "BUSY = dead" (drives problem 2)
`resume.rs:368` tells the agent:
> Check status: `$OWL list` — if your entry for `{id}` shows `pid:"BUSY"`, is STALE, or is
> missing, the poll is dead.

This contradicts every liveness check in the binary, all of which treat BUSY as ONLINE:
- `src/common/owlery.rs:736` — `PidValue::Busy(_) => true,  // BUSY = mid-poll, conservatively online`
- `src/owl/list.rs:284` — `// info.json on disk, PID is BUSY → online.`
- `src/common/list_filter.rs:209` — `// info.json on disk but PID is BUSY ... — online.`
- `src/live/list_psyches.rs:97` — `// Ready + (PID alive or BUSY or parent alive) -- show as active`
- `src/owl/ring.rs:44`, `src/owl/send.rs:49` — BUSY treated as alive.

So the agent reads a healthy, mid-delivery listener as "dead", tries to re-arm, hits
DUPLICATE (the surviving process still holds registry+socket), escalates to `$LIVE stop`
(tears down the Psyche), then revives with bare `$OWL poll` instead of the live-preserving
`$OWL poll {id} listen --live`. The reorientation re-arm commands also hand a LIVE agent the
bare `$OWL poll {id}` form, which decouples the Psyche.

### Defect B — no Psyche-liveness guidance (drives problem 1)
The reorientation block teaches the agent about its OWN listener but says nothing about how
to read PSYCHE liveness. Two facts the agent never learns:
- `$OWL list` HIDES psyche entries by default (predicate excludes `PerchState::Psyche`,
  `src/owl/list.rs:28`). The correct surface is `$LIVE list-psyches`.
- The Psyche wrapper is a detached, self-healing process. While mid-resume (busy) its inner
  poll is transiently down, so any liveness glance can read OFFLINE for that window.
With no guidance, the agent interprets "not shown / transiently offline" as a Psyche failure
and troubleshoots (or restarts) a non-bug.

## Eliminated

- **Real liveness bug in `$OWL list` / list_psyches**: code correctly classes BUSY + parent
  fallback as online. The misread is instructional, not a display bug.
- **Wrong text duplicated in SKILL.md**: grep of `plugin/spt/skills` shows the BUSY-is-dead
  phrasing exists ONLY in resume.rs; SKILL.md files already use `listen --live`. Fix is
  confined to one file.

## Resolution

status: root_cause_found → fix_applied
specialist: rust (binary-embedded instruction strings)
fix:
  - resume.rs Edit A: replace the "Re-arm a stale poll" block. Correct BUSY semantics
    (BUSY = ONLINE), add an explicit post-/clear protocol (listener PROCESS survives /clear
    → orphaned → DUPLICATE on bare re-arm is expected, not "stale" → kill-by-pid then
    re-arm), and make the re-arm command live-aware via `{poll_cmd}` (`listen --live` for
    live agents, bare for ready agents).
  - resume.rs Edit B: extend the live-only "Talking to your Psyche" section — Psyche is
    detached/self-healing, `$OWL list` hides it (use `$LIVE list-psyches`), transient
    OFFLINE/BUSY is normal, NEVER `$LIVE stop` to clear a "stale" listener (it kills the
    Psyche), recover own listener with `$OWL poll {id} listen --live`.
files_changed:
  - src/owl/resume.rs
verification: cargo build --release + cargo test (resume reorientation tests). Deploy via
  docs/DEPLOY.ps1 (user runs /reload-plugins) so the new SessionStart text reaches agents.
