---
phase: 260523-648
plan: 01
type: quick
tags: [live-start, pulse-period, pulse-psyche, default, sentinel-0, wrapper-state, changelog]
dependency_graph:
  requires:
    - quick-260521-oyi (CHANGELOG [1.10.26]) — the entry this task corrects;
      sentinel-0 mechanics from that task are preserved verbatim
  provides:
    - default `--period 480` (8-min echo-gate cadence) for `$LIVE start` /
      `revive` / `fork`
    - opt-in `--pulse-psyche` flag (Psyche LLM resume turn on cadence)
    - wrapper outer-loop PULSE_TRIGGER intercept that `continue`s past
      `resume_session_checked` when `pulse_psyche == false`
    - `WrapperHandoffState.pulse_psyche` field with `#[serde(default)]`
      backward-compat against v1.11.8 on-disk state files
  affects:
    - any caller of `live_start_result` (one in-crate caller — exported via
      `pub use` from `src/live/mod.rs`; no external crate callers)
    - any user who relied on the [1.10.x] 20-minute Psyche-evaluating cadence
      (migration recipe: `--period 1200 --pulse-psyche`)
tech_stack:
  added: []
  patterns:
    - "clap derive `bool` flag-presence switch (`#[arg(long)] pulse_psyche: bool`)"
    - "argv-as-wire transport for hidden subcommand positional slot (`\"1\"`/`\"0\"`)"
    - "`#[serde(default)]` for backward-compat schema extensions"
    - "wrapper-state.json belt-and-braces channel (argv + state-file)"
key_files:
  created: []
  modified:
    - src/cli.rs
    - src/common/wrapper_state.rs
    - src/live/fork.rs
    - src/live/mod.rs
    - src/live/signoff.rs
    - src/live/start.rs
    - src/live/stop.rs
    - src/live/wrapper/claude.rs
    - src/live/wrapper/lifecycle.rs
    - src/live/wrapper/mod.rs
    - src/owl/mod.rs
    - tests/cli_parse.rs
    - tests/skill_hints.rs
    - plugin/spt/skills/live/SKILL.md
    - plugin/spt/skills/revive/SKILL.md
    - CHANGELOG.md
decisions:
  - "Default `--period` set to 480 (8 min); `--period 0` preserved as
    explicit no-cadence opt-out (sentinel-0 contract intact)"
  - "Wrapper-side intercept (`continue` past `resume_session_checked` when
    !self.pulse_psyche) — smallest blast radius; no inner-poll / PULSE_TRIGGER
    wire change"
  - "`pulse_psyche` threaded via BOTH argv (positional slot 5) AND
    wrapper-state.json `#[serde(default)]` field — symmetric with `period`"
  - "On handoff rehydration, state-file `pulse_psyche` wins over argv
    (operator's explicit choice survives mid-life argv churn)"
  - "Sentinel-0 mechanics from 260521-oyi preserved verbatim — only the
    `unwrap_or(...)` numeric default flipped"
metrics:
  duration_minutes: ~25
  completed_date: 2026-05-23
---

# Quick 260523-648: Default `--period 8m` + `--pulse-psyche` Flag Summary

## One-Liner

Three deltas in 16 files: default `--period` flips from `0` (260521-oyi
over-correction that silently disabled echo-commune cadence) to `480` (8
min); new opt-in `--pulse-psyche` flag gates the Psyche LLM resume turn on
cadence wakes; wrapper outer loop intercepts PULSE_TRIGGER and `continue`s
past `resume_session_checked` when the flag is off, so the echo-gate
cadence fires while the LLM poke is opt-in. Corrects CHANGELOG entry
`[1.10.26]`; CHANGELOG bumped to `[1.11.9]` (plugin.json bump deferred to
user-run DEPLOY.ps1).

## Per-Task Commits

| Task | Name | Commit | Files |
|------|------|--------|-------|
| 1 | Plumbing + wrapper intercept (CLI flag, signatures, argv, WrapperState, PULSE_TRIGGER skip) | `3616ed1` | src/cli.rs, src/common/wrapper_state.rs, src/live/fork.rs, src/live/mod.rs, src/live/signoff.rs, src/live/start.rs, src/live/stop.rs, src/live/wrapper/claude.rs, src/live/wrapper/lifecycle.rs, src/live/wrapper/mod.rs, src/owl/mod.rs |
| 2 | Tests + skill docs + CHANGELOG `[1.11.9]` | `733ccd7` | CHANGELOG.md, plugin/spt/skills/live/SKILL.md, plugin/spt/skills/revive/SKILL.md, tests/cli_parse.rs, tests/skill_hints.rs |

## Deltas

1. **One-number flip × 2 sites** (`src/live/start.rs:327` + `:748`):
   `unwrap_or(0)` → `unwrap_or(480)`. The min-60 guard wording
   (`"Minimum pulse period is 60 seconds (or 0 to disable)"`) and the
   `period > 0 && period < 60` guard shape stayed verbatim from 260521-oyi.
   `--period 0` still parses and behaves as the explicit no-cadence
   opt-out.

2. **`--pulse-psyche` flag plumbing** end-to-end:
   - `src/cli.rs`: three independent `#[arg(long)] pulse_psyche: bool`
     fields on `LiveCommands::{Start, Revive, Fork}`; new 5th positional
     slot (`pulse_psyche: String`) on hidden `Commands::PsycheWrapper`
     parsed as `"1"`/`"0"` inside `src/owl/mod.rs` dispatch
   - `src/live/mod.rs`: dispatch destructures and threads to handlers
   - `src/live/start.rs::{run, live_start_result, run_wrapper}`,
     `src/live/stop.rs::run_revive`, `src/live/fork.rs::run`: trailing
     `pulse_psyche: bool` parameter
   - Argv `[&str; 5]` → `[&str; 6]` at three sites:
     `src/live/start.rs::run` (cold start),
     `src/live/start.rs::live_start_result` (cold start),
     `src/live/wrapper/mod.rs::perform_wrapper_handoff` (handoff re-emit).
     `pulse_psyche_str` binding declared at outer scope so its lifetime
     covers the `&pulse_psyche_str` borrow in the array literal (mirrors
     the pattern 260521-oyi used for `period_str`).

3. **Wrapper outer-loop PULSE_TRIGGER intercept**
   (`src/live/wrapper/mod.rs`):
   ```rust
   let is_pulse_trigger = msg.lines().any(|line| line.trim().starts_with("PULSE_TRIGGER"));

   // 260523-648: pulse-psyche gate. ...
   if is_pulse_trigger && !self.pulse_psyche {
       self.log("[PULSE] pulse-psyche=off — skipping resume; echo gate fires next iteration");
       continue;
   }

   // Feed to claude --resume
   let response = self.resume_session_checked(&msg);
   ```
   Skip is `continue` (not `break`, not None-assign): the loop returns to
   the top, re-runs orphan check, `fire_echo_commune_if_due`, handoff
   detection, ready-file check, 24h-refresh, then `poll_psyche`. Echo
   gate, marker parsing, `next_pulse_override`, and recovery paths
   unaffected (analysis: RESEARCH Finding 2 + Pitfalls P1–P5).

4. **Handoff durability** (`src/common/wrapper_state.rs` +
   `src/live/wrapper/lifecycle.rs` + `src/live/wrapper/mod.rs` +
   `src/live/wrapper/claude.rs`):
   - `WrapperHandoffState` gains `#[serde(default)] pub pulse_psyche: bool`
     — REQUIRED for backward-compat with v1.11.8 on-disk state files
     (missing field → `false`, semantically correct: the operator never
     opted in)
   - `WrapperState::new` accepts a trailing `pulse_psyche: bool` argv-side
     parameter; on handoff rehydration prefers the state-file value, on
     cold start uses the argv value
   - `perform_wrapper_handoff` populates the field before `write_atomic`;
     `init_session` re-publish (`src/live/wrapper/claude.rs:182`) also
     populates it so external readers see the operator's choice

## Sentinel-0 Preservation (Verbatim from 260521-oyi)

The following files were NOT modified (per RESEARCH "Files NOT to touch"
list):

- `src/live/wrapper/mod.rs:1482-1502` (`poll_psyche` conditional argv:
  `Vec<&str>`, omits `--pulse-interval` when `period == 0`) — UNCHANGED.
- `src/live/wrapper/claude.rs:16-38` (`build_agents_json` whole-segment
  `{{period}} seconds` substitution) — UNCHANGED.
- `src/live/wrapper/claude.rs:44-53` (`init_session` conditional
  pulse-phrase banner) — UNCHANGED.
- `psyche.md` — UNCHANGED (the rebuild IS needed because Rust sources
  changed, but the embedded `include_str!` content stays put).

The relaxed `period > 0 && period < 60` guard shape from 260521-oyi
survives; explicit `--period 0` flows through both `start::run` and
`live_start_result` exactly as before.

## Pre-Flight Grep Findings (Audit Completeness)

### `live_start_result(` callers

```
src\live\mod.rs:14:pub use start::live_start_result;
src\live\start.rs:737:pub fn live_start_result(id: &str, period: Option<u64>, pulse_psyche: bool) -> Result<...>
```

Only the definition. No external crate calls in `src/` or `tests/`. The
`pub use` re-export in `src/live/mod.rs` is the public-API edge — every
function-call site is intra-crate and was updated in Task 1. (Tests
`tests/cli_parse.rs` and `tests/handoff_integration.rs` reference the
symbol only in comments / doc-strings; no call sites.)

### `WrapperHandoffState {` construction sites

| File:line | Updated |
|-----------|---------|
| `src/live/wrapper/mod.rs:1633` (perform_wrapper_handoff) | ✓ pulse_psyche: self.pulse_psyche |
| `src/live/wrapper/lifecycle.rs:74` (init re-publish) | ✓ pulse_psyche: initial_pulse_psyche |
| `src/live/wrapper/claude.rs:182` (init_session re-publish) | ✓ pulse_psyche: self.pulse_psyche |
| `src/live/start.rs:1026` (test) | ✓ pulse_psyche: false |
| `src/live/signoff.rs:506` (test) | ✓ pulse_psyche: false |
| `src/live/signoff.rs:645` (test) | ✓ pulse_psyche: false |
| `src/common/wrapper_state.rs:325` (test `roundtrip`) | ✓ pulse_psyche: false |
| `src/common/wrapper_state.rs:370` (test `wrapper_state_write_atomic_roundtrip`) | ✓ pulse_psyche: false |
| `src/common/wrapper_state.rs:389` (test `wrapper_state_read_is_non_destructive`) | ✓ pulse_psyche: false |
| `src/common/wrapper_state.rs:466` (test `write_atomic_no_stray_tmp`) | ✓ pulse_psyche: false |

All 10 construction sites updated. Two NEW test constructions added in
`src/common/wrapper_state.rs` (`pulse_psyche_true_roundtrips` +
`missing_pulse_psyche_field_defaults_false`).

### `WrapperState::new` callers

One production call site at `src/live/start.rs:914` (now passes the new
`pulse_psyche` arg). No test sites use the constructor — tests build
`WrapperState` via struct-literal `mk_wrapper_state` / `mk_state` /
`mk_state_for_passive_ctx` helpers and ad-hoc literals (5 such sites in
`src/live/wrapper/mod.rs` + 1 in `src/live/wrapper/lifecycle.rs::tests`).
All 6 struct-literal sites updated with `pulse_psyche: false`.

## Test Results

```
cargo test --test cli_parse -- --test-threads=1
test result: ok. 48 passed; 0 failed; 0 ignored; 0 measured

cargo test --lib common::wrapper_state
test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 841 filtered out
  (Tests of note:
    - pulse_psyche_true_roundtrips — true survives write_atomic + load_and_delete
    - missing_pulse_psyche_field_defaults_false — v1.11.8 schema parses to false)

cargo test --test skill_hints
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured

cargo build --release
Finished `release` profile [optimized] target(s) (6 pre-existing dead-code warnings)
```

## Deviations from Plan

### Auto-fixed Issues

**1. [Rule 3 - Blocking Issue] tests/skill_hints.rs pinned-value
regression on `/spt:live` argument-hint**

- **Found during:** Task 2 (after the SKILL.md argument-hint update)
- **Issue:** `argument_hint_keys_known_set` test pinned the prior literal
  `"<id> [--period <seconds>] | [--auto]"` for `/spt:live`. The Task 2
  SKILL.md edit (adding `[--pulse-psyche]`) was a direct cause of the
  pinned-value regression — the very purpose of the pinned test is to
  catch unannounced argument-hint changes.
- **Fix:** updated the pinned expected value in `tests/skill_hints.rs:167`
  to the new advertised string `"<id> [--period <seconds>] [--pulse-psyche] | [--auto]"`
  with a `// Quick 260523-648:` comment noting the rationale.
- **Files modified:** `tests/skill_hints.rs`
- **Commit:** `733ccd7` (Task 2 commit; the fix sits with the SKILL.md
  changes that necessitated it).
- **Why Rule 3 not Rule 4:** the test is a structural regression guard for
  *intentional* SKILL.md edits, not a behavior contract. Updating the
  pinned value is the documented response shape (see `tests/skill_hints.rs`
  D-12/D-13/D-14/D-15 docstring); no architectural decision.

No other deviations.

## Deploy Guidance

This task ships the binary + skill + CHANGELOG changes. To land
`[1.11.9]` on `plugin/spt/.claude-plugin/plugin.json`, `Cargo.toml`,
`Cargo.lock`, and the cplugs marketplace, the user runs:

```powershell
powershell -ExecutionPolicy Bypass -File docs/DEPLOY.ps1 -Bump patch
```

Per CLAUDE.md mandate, this executor did NOT manually edit `plugin.json`
or `Cargo.toml` — `DEPLOY.ps1` handles the version bump and marketplace
sync. Verification: `git diff --stat plugin/spt/.claude-plugin/plugin.json`
returns empty after both task commits.

## Smoke-Test Recipes (User-Driven, NOT part of this plan)

```bash
# Default: 8m cadence, echo-gate only, init banner mentions period=480
$LIVE start <fresh-id>

# Opt-in: 8m cadence, Psyche LLM evaluates on every wake (legacy behavior)
$LIVE start <id> --pulse-psyche

# Migration recipe for the [1.10.x] 20-min Psyche-evaluating cadence
$LIVE start <id> --period 1200 --pulse-psyche

# Sentinel-0 from 260521-oyi: no cadence wake at all
$LIVE start <id> --period 0

# Rejected: <60s pulse period
$LIVE start <id> --period 30
# stderr: Minimum pulse period is 60 seconds (or 0 to disable)
```

## Self-Check: PASSED

- ✓ `src/cli.rs`: `--pulse-psyche` on `LiveCommands::{Start, Revive, Fork}` (lines 257, 322, 357)
- ✓ `src/cli.rs:194`: `Commands::PsycheWrapper.pulse_psyche: String` positional
- ✓ `src/owl/mod.rs:143`: dispatch parses `pulse_psyche == "1"` and threads to `run_wrapper`
- ✓ `src/live/start.rs:327`: `unwrap_or(480)` in `run`
- ✓ `src/live/start.rs:748`: `unwrap_or(480)` in `live_start_result`
- ✓ `src/live/start.rs`: 0 hits of `unwrap_or(0)` for `period` (other unrelated hits ignored)
- ✓ `src/live/wrapper/mod.rs`: 13 `pulse_psyche` hits (≥4 expected; covers struct field, skip block, handoff state, handoff argv, and 5 test constructors)
- ✓ `src/common/wrapper_state.rs`: `pulse_psyche` field with `#[serde(default)]` + 2 new focused tests
- ✓ `CHANGELOG.md:7`: `## [1.11.9] - 2026-05-23` with `### Changed` + `### BTS` referencing `[1.10.26]`
- ✓ `plugin/spt/skills/live/SKILL.md`: argument-hint includes `[--pulse-psyche]`; callout rewritten
- ✓ `plugin/spt/skills/revive/SKILL.md`: argument-hint includes `[--pulse-psyche]`; callout rewritten
- ✓ `plugin/spt/.claude-plugin/plugin.json`: UNTOUCHED (planner constraint; user runs DEPLOY.ps1)
- ✓ `psyche.md`: UNTOUCHED (sentinel-0 mechanics handle both modes)
- ✓ Two atomic commits: `3616ed1` (feat) + `733ccd7` (test)
- ✓ All cli_parse + wrapper_state + skill_hints tests green
- ✓ `cargo build --release` exits 0
