---
phase: 35-psyche-sync-cross-machine-context-backup-via-private-gh-repo
plan: 10
subsystem: live
tags: [psyche-sync, owl-message, askuserquestion, session-start, doctor, spool, gh]

# Dependency graph
requires:
  - phase: 35 (plans 01-09)
    provides: sync settings.json data layer (SyncSettings/SyncState), gh_present probe, doctor sync surface, version-change active-delivery reference pattern
provides:
  - Active owl-message delivery of the cross-machine sync-setup AskUserQuestion (replacing the passive SessionStart emitter that Claude ignored)
  - src/owl/sync_prompt.rs module (build_sync_prompt_block + deliver_sync_prompt_to_perch + relocated should_emit_sync_prompt predicate)
  - Always-loaded <spt-psyche-sync-prompt> SKILL handler in live/SKILL.md
  - Render-path integration test asserting the spool-landing + <instructions> envelope
  - doctor PASS-on-failing-row fix (per-branch sync rows render WARN/FAIL not green PASS)
affects: [psyche-sync, live-agent, doctor, operator-uat]

# Tech tracking
tech-stack:
  added: []
  patterns:
    - "Active owl-message delivery for hook-originated prompts (mirror version_changelog.rs): self-contained <instructions> element IS the handler, routed via send::deliver_body (TCP-first, spool fallback)"
    - "Promote helpers to pub for integration-test access (Phase 18.7.1 / 18.8.1 precedent)"

key-files:
  created:
    - src/owl/sync_prompt.rs
    - tests/sync_prompt_render.rs
    - .planning/phases/35-psyche-sync-cross-machine-context-backup-via-private-gh-repo/deferred-items.md
  modified:
    - src/owl/mod.rs
    - src/owl/plugin_session_start.rs
    - src/live/start.rs
    - src/common/sync.rs
    - src/owl/doctor.rs
    - plugin/spt/skills/live/SKILL.md
    - .planning/phases/35-psyche-sync-cross-machine-context-backup-via-private-gh-repo/35-OPERATOR-UAT.md

key-decisions:
  - "Replaced passive SessionStart additionalContext emit with active owl-message delivery to the live perch — the only way the offer actually renders in-session"
  - "Deleted the .sync-prompt-due sentinel cross-session-deferral mechanism entirely (it could never pair within one session)"
  - "doctor per-branch sync row: FAIL on active backoff OR hard-stop reason (remote-404/user-disabled), WARN on transient elapsed-backoff failure, PASS only when truly clean"

patterns-established:
  - "Hook-originated prompts deliver as owl messages with a self-contained <instructions> element + an always-loaded SKILL handler (belt-and-suspenders), never as passive additionalContext"

requirements-completed: [SYNC-AUTO-01]

# Metrics
duration: ~35min
completed: 2026-05-27
---

# Phase 35 Plan 10: Active Sync-Prompt Delivery (SC1 Fix) Summary

**Replaced the void-emitting passive SessionStart sync-prompt with active owl-message delivery of a self-contained `<spt-psyche-sync-prompt>`/`<instructions>` AskUserQuestion to the live perch (mirroring the version-change pattern), wired from `$LIVE start`/`revive` and the `/clear`|`/compact` boundary, plus an always-loaded SKILL handler, a render-path test, corrected operator-UAT wording, and a doctor PASS-on-failing-row fix.**

## Performance

- **Duration:** ~35 min
- **Completed:** 2026-05-27
- **Tasks:** 4
- **Files created:** 3 (sync_prompt.rs, sync_prompt_render.rs, deferred-items.md)
- **Files modified:** 7

## Accomplishments

- **Fixed the Phase 35 SC1 defect** (`.planning/debug/sync-prompt-emits-into-void.md`): the sync-setup offer now renders. Built `src/owl/sync_prompt.rs` with `build_sync_prompt_block()` (a `<spt-psyche-sync-prompt>` owl-message body wrapping a self-contained `<instructions>` element carrying the locked 3-option AskUserQuestion) and `deliver_sync_prompt_to_perch()` (gated by the relocated `should_emit_sync_prompt` predicate; routes via `send::deliver_body`).
- **Rewired both triggers** to active delivery: `$LIVE start` + `$LIVE revive` (start.rs, the live agent's own `id`) and the `/clear`|`/compact` `is_live` branch of `inject_reorientation_if_needed` (plugin_session_start.rs). **Deleted** the `.sync-prompt-due` sentinel-queue mechanism (`queue_sync_prompt_if_due` + Step 6.6 + the passive emitter fns).
- **Added a render-path integration test** (`tests/sync_prompt_render.rs`) asserting the delivery MECHANISM — a `psyche-sync-prompt` spool row lands carrying both `<spt-psyche-sync-prompt>` and `<instructions>` — closing the test gap that let the defect ship.
- **Added an always-loaded `<spt-psyche-sync-prompt>` handler** to `live/SKILL.md`, framing the embedded `<instructions>` as authoritative.
- **Corrected the operator UAT runbook** (Sections 1+2) to the achievable active-delivery model.
- **Fixed the doctor PASS-on-failing secondary bug**: per-branch sync rows no longer render green PASS when `consecutive_failures > 0`.

## Task Commits

1. **Task 1: Create src/owl/sync_prompt.rs (active-delivery module)** — `286fe40` (feat)
2. **Task 2: Rewire both triggers; remove sentinel queue** — `e8c78ee` (feat)
3. **Task 3: Render-path integration test** — `2c17cd9` (test)
4. **Task 4: SKILL handler + UAT wording + doctor fix** — `d0fcfb6` (fix)

_TDD note: Tasks 1-3 were authored as code+tests together (the lib is compilation-coupled across the passive-emitter removal, so the three plan tasks were committed as logical units once the whole lib compiled and each task's tests passed). Task 1's unit tests (11) and Task 3's render-path tests (3) constitute the RED→GREEN coverage for the new module._

## Files Created/Modified

- `src/owl/sync_prompt.rs` (created) - `build_sync_prompt_block()`, `deliver_sync_prompt_to_perch()`, relocated `should_emit_sync_prompt` predicate, `SyncPromptOutcome` enum, 11 unit tests.
- `tests/sync_prompt_render.rs` (created) - 3 render-path cases (Unset→one row+envelope; Declined→zero; Enabled→zero).
- `.planning/.../deferred-items.md` (created) - logged the pre-existing broken `native_wrapper_state_retry.rs` test + known parallel-contention failures.
- `src/owl/mod.rs` (modified) - `pub mod sync_prompt;`.
- `src/owl/plugin_session_start.rs` (modified) - deleted passive emitter fns + Step 6.6 sentinel block + obsolete test module; added clear/compact `deliver_sync_prompt_to_perch` call in the `is_live` branch.
- `src/live/start.rs` (modified) - both `queue_sync_prompt_if_due()` call sites → `deliver_sync_prompt_to_perch(id)`.
- `src/common/sync.rs` (modified) - removed `queue_sync_prompt_if_due` fn + its sentinel tests.
- `src/owl/doctor.rs` (modified) - per-branch sync row status fix + 2 new regression tests + updated Test 3.
- `plugin/spt/skills/live/SKILL.md` (modified) - `<spt-psyche-sync-prompt>` handler section.
- `.planning/.../35-OPERATOR-UAT.md` (modified) - Sections 1+2 active-delivery wording.

## Decisions Made

- **Active owl-message delivery over passive additionalContext** — the root cause of SC1 was that passive SessionStart `additionalContext` with no `<instructions>` element and no handler was silently ignored by Claude. Mirroring the proven version-change path is the fix.
- **Delete the sentinel-queue entirely** — the cross-session `.sync-prompt-due` write→next-boot-consume could never pair within one session; active in-session delivery makes it obsolete.
- **doctor status semantics** (per plan acceptance criteria): active backoff (retry pending) → FAIL; hard-stop reason → FAIL; transient elapsed-backoff failure → WARN; clean → PASS. This required updating existing Test 3 (which asserted Warn for active backoff) to expect Fail.

## Deviations from Plan

### Auto-fixed Issues

**1. [Rule 1 - Bug] Existing doctor Test 3 contradicted the new status spec**
- **Found during:** Task 4 (doctor fix)
- **Issue:** `check_sync_status_enabled_failing_branch_expands` asserted `DiagStatus::Warn` for an active-backoff (future `next_retry_after_ts`) row. The plan's locked spec says active backoff → FAIL, so the existing assertion was now incorrect.
- **Fix:** Updated Test 3 to assert `DiagStatus::Fail` for active backoff, and added two new regression tests (elapsed-backoff failure → Warn-not-Pass; hard-stop reason → Fail).
- **Files modified:** src/owl/doctor.rs
- **Verification:** `cargo test --lib owl::doctor` → 22 passed.
- **Committed in:** `d0fcfb6`

**2. [Rule 3 - Blocking] sync_prompt module/symbol visibility for integration test**
- **Found during:** Tasks 1-3 (module + render test)
- **Issue:** The module started as `mod sync_prompt` (private); the `live` module needs `crate::owl::sync_prompt::...` (required `pub(crate) mod`), and the integration crate needs `owl::owl::sync_prompt::...` (required `pub mod` + `pub` on `build_sync_prompt_block` / `deliver_sync_prompt_to_perch` / `SyncPromptOutcome`).
- **Fix:** `pub mod sync_prompt;` + promoted the helpers/enum to `pub` (Phase 18.7.1 / 18.8.1 precedent, noted in doc-comments). The plan explicitly sanctioned this option.
- **Files modified:** src/owl/mod.rs, src/owl/sync_prompt.rs
- **Verification:** `cargo build` clean (no new warnings); `cargo test --test sync_prompt_render` → 3 passed.
- **Committed in:** `2c17cd9`

---

**Total deviations:** 2 auto-fixed (1 bug, 1 blocking)
**Impact on plan:** Both necessary for correctness/buildability. No scope creep — the doctor test change aligns with the plan's locked status spec; the visibility change was a plan-sanctioned option.

## Issues Encountered

- **Pre-existing broken test (`tests/native_wrapper_state_retry.rs`)** — fails to compile under full `cargo test` with `missing field pulse_psyche`. Confirmed NOT caused by this plan (zero `pulse_psyche` refs at pre-work commit `3d479c8` while the struct already required it; the field landed in commit 260523-648 without updating this test). Out of scope per SCOPE BOUNDARY; logged to `deferred-items.md`.
- **Known parallel-test contention** — env-global SPT_HOME tests (`common::spool`/`owlery`/`sync`/`listener`) fail under parallel `cargo test` but ALL PASS single-threaded (`--test-threads=1`: 949 passed, 0 failed). This is the documented "21 unrelated parallel-test failures ... pass in isolation" backlog item in PROJECT.md.

## User Setup Required

None - no external service configuration required. (gh CLI presence gates the prompt at runtime, but that is end-user environment, not a setup step for this plan.)

## Next Phase Readiness

- SC1 (auto-detect sync prompt) is now functionally fixed and render-path-tested. Ready for the operator UAT (Sections 1+2 reworded to the active-delivery model).
- Deploy is still milestone-cadence (single `DEPLOY.ps1 -Bump` at end-of-milestone, NOT per-phase) — the SKILL.md change requires a rebuild+deploy to reach the plugin cache.
- Pre-existing `native_wrapper_state_retry.rs` compile break is a candidate for a quick cleanup task (does not block this phase's verification, which passes single-threaded).

## Self-Check: PASSED

- Created files verified on disk: `src/owl/sync_prompt.rs`, `tests/sync_prompt_render.rs`, `35-10-SUMMARY.md`, `deferred-items.md`.
- Task commits verified in git log: `286fe40`, `e8c78ee`, `2c17cd9`, `d0fcfb6`.

---
*Phase: 35-psyche-sync-cross-machine-context-backup-via-private-gh-repo*
*Completed: 2026-05-27*
