---
name: lfo-handoff-watcher
description: Invoke when Rocky says "check handoff" / "continue handoff" / on session start with an active bid / at any phase transition that requires worker handoff. Reads pricer output from `C:/Users/mclou/lfcs-handoff/pricer-to-orch.md`, parses the latest message, advances state.json to the next phase, drafts the next outbound prompt for pricer (or the next Rocky-input gate). Writes orch's outbound messages to `orch-to-pricer.md` with timestamped + bid-id-headed blocks. Atomic temp+rename writes; append-only.
model: claude-sonnet-4-6
phase: all (active during any handoff-driven bid lifecycle)
invoked_by: orchestrator on session start, after each Rocky prompt, and at key phase transitions
preconditions:
  - handoff folder exists (`C:/Users/mclou/lfcs-handoff/`)
  - state.json present and valid
  - active_bid_id matches the bid being processed (or null if starting fresh)
inputs:
  - direction: "read" (parse latest pricer output) | "write" (draft outbound prompt) | "both"
  - bid_id (string)
  - phase (one of: phase-1-intake, phase-2-exclusions, phase-3-worker, phase-3.5-envelope, phase-4-sanity, phase-5-blind, phase-6-submit, phase-7-close)
  - message_body (when writing): the full prompt text to send
  - rocky_gate_question (optional): if entering a Rocky-input gate, the question text
outputs:
  - latest_pricer_message (when reading): parsed message text + metadata
  - state.json updated with new mtimes / phase / Rocky-gate status
  - orch-to-pricer.md appended with new outbound block (when writing)
side_effects:
  - writes to handoff folder (state.json + orch-to-pricer.md)
  - daemon (if running) detects writes and notifies pricer side
authored_by: claude-code-orchestrator
created: 2026-05-08
---

# lfo-handoff-watcher

Orchestrator-side handoff I/O. The orchestrator does NOT poll itself (Claude Code sessions are interactive, not daemons); instead the daemon polls and notifies. When Rocky comes back to the orchestrator terminal and prompts "check handoff," this skill reads the latest pricer output from `pricer-to-orch.md`, advances state, and either drafts the next outbound prompt or surfaces a Rocky-input gate.

## Procedure

### Read mode

1. **Parse `state.json`.** Pull `active_bid_id`, `current_phase`, `last_orch_seen_pricer_output_index`.
2. **Read `pricer-to-orch.md`.** Find all message blocks bracketed by `<!-- pricer-msg-start bid={id} ts={iso} -->` and `<!-- pricer-msg-end -->`. Filter to messages where `bid == active_bid_id` AND block index > `last_orch_seen_pricer_output_index`.
3. **If unread messages exist:** for each, parse the body. Update `last_orch_seen_pricer_output_index`. Apply phase advancement logic per `lfo-bid-lifecycle` skill.
4. **If no unread messages:** return `status=no-new-messages`. Orchestrator may surface to Rocky: "No new pricer output since last check."

### Write mode

1. **Compose outbound block.** Format:
   ```
   <!-- orch-msg-start bid={id} ts={iso} phase={phase} msg-index={N} -->
   ## Bid {id} — Phase {phase} — {iso}

   {message_body}
   <!-- orch-msg-end -->
   ```
2. **Append to `orch-to-pricer.md`.** Atomic write: read current contents, add block, write to `.tmp`, rename. Daemon picks up the mtime change within 30s and notifies pricer terminal.
3. **Update `state.json`.** Stamp `last_orch_to_pricer_mtime` to file's new mtime; bump `last_pricer_seen_orch_input_index` is the pricer's responsibility.
4. **If entering Rocky-input gate:** set `rocky_gate_active=true`, `rocky_gate_question=<question text>`, `rocky_gate_opened_at=<iso now>`, `rocky_gate_escalations_sent=0`. Daemon's escalation timer kicks in.
5. **If exiting Rocky-input gate:** set `rocky_gate_active=false`, clear `rocky_gate_question`/`rocky_gate_opened_at`/`rocky_gate_escalations_sent`.

### Both mode

Read first, then write next outbound. Most common pattern when Rocky says "check handoff and continue."

## Hard rules (per orchestrator + pricer CLAUDE.md handoff rules)

- **One active bid at a time.** If `state.json.active_bid_id` is set and a different bid_id arrives, append the new bid request to `queue.md` (do NOT overwrite the active bid). Process queue entries when current bid hits Phase 7.
- **Atomic writes only.** Use temp-file + rename pattern. Never write directly to the live handoff file (daemon may catch a half-written file).
- **Append-only on `orch-to-pricer.md` and `pricer-to-orch.md`.** Never edit prior blocks. Index increments monotonically.
- **state.json is the single source of truth for current phase + Rocky-gate.** If state and handoff messages disagree, state wins.
- **No external network calls from this skill.** Notifications go through the daemon, not the skill. Skill is local-FS-only.

## Failure modes

- **Daemon not running:** the skill still works (reads + writes succeed). Rocky just has to manually shepherd between terminals (no phone notifications). Log "daemon-detected: false" in session log.
- **Handoff folder missing or unreadable:** stop and surface to Rocky per §6 hard case 1 (hard rule risk: handoff folder is locked infrastructure).
- **state.json corrupted (JSON parse error):** stop, surface, ask Rocky whether to re-initialise from `state.json.tmp` if present, or rebuild from scratch.
- **Multiple unread pricer messages from prior bid:** flag in session log; usually means a prior bid wasn't closed cleanly. Rocky decides whether to ignore, archive to `_Internal/handoff-archive/`, or process.

## Output schema

Read-mode output:
```yaml
latest_pricer_messages:
  - bid_id: "2631"
    phase: "phase-3-worker"
    timestamp: "2026-05-08T15:30:00+00:00"
    msg_index: 4
    body: |
      <full message body>
status: "messages-found" | "no-new-messages"
```

Write-mode output:
```yaml
written_to: "orch-to-pricer.md"
new_mtime: "2026-05-08T15:35:00+00:00"
msg_index: 7
state_updated: true
rocky_gate_set: false  # or true if entering a gate
```
