crates\spt-daemon\src\attach.rs:131:    // SYNCHRONOUS inline write, while VIEWERS use the async dedicated-writer-
crates\spt-daemon\src\attach.rs:132:    // thread + bounded-channel pattern (`add_viewer`/`viewer_writer`); unifying
crates\spt-daemon\src\attach.rs:184:                            // BROKER-owned (single writer — resolves the
crates\spt-daemon\src\attach.rs:270:                // marker (the clear-race the broker-single-writer model averts).
crates\spt-daemon\src\brainproc.rs:515:/// advertising and the [`spt_store::epoch::EpochSource`] single-writer assumption
crates\spt-daemon\src\brain.rs:449:    pub fn send_effect(&mut self, op_id: u64, bytes: &[u8]) -> io::Result<()> {
crates\spt-daemon\src\broker.rs:4://! would lose continuity on if the logic restarted: the PTY master+writer and
crates\spt-daemon\src\broker.rs:37:use std::sync::mpsc::{channel, sync_channel, Receiver, RecvTimeoutError, Sender, SyncSender, TrySendError};
crates\spt-daemon\src\broker.rs:86:/// not keeping up. Replay-at-attach does NOT ride this bound (the writer thread
crates\spt-daemon\src\broker.rs:92:/// bounds the writer-thread count). A `Viewer` attach beyond the cap is refused.
crates\spt-daemon\src\broker.rs:97:/// the live stream) or a `Disconnected` one (its writer thread ended after a
crates\spt-daemon\src\broker.rs:107:/// `writer` thread does the BLOCKING socket write, so a wedged viewer backs up
crates\spt-daemon\src\broker.rs:109:/// dropped (the `tx` closes → the writer ends) and removed from the map.
crates\spt-daemon\src\broker.rs:111:    /// The bounded live-frame queue to this viewer's writer thread.
crates\spt-daemon\src\broker.rs:116:    /// The writer thread handle (held for ownership; it exits when `tx` drops or
crates\spt-daemon\src\broker.rs:118:    _writer: JoinHandle<()>,
crates\spt-daemon\src\broker.rs:168:/// Symmetric with [`ViewerSink`] — a dedicated writer thread does the BLOCKING
crates\spt-daemon\src\broker.rs:171:/// holding `Mutex<OutputLog>`). Unlike a viewer it is AUTHORITATIVE: its writer
crates\spt-daemon\src\broker.rs:175:    /// The bounded live-frame queue to the controller's writer thread.
crates\spt-daemon\src\broker.rs:183:    /// The writer thread handle (held for ownership; exits when `tx` drops — the
crates\spt-daemon\src\broker.rs:186:    _writer: JoinHandle<()>,
crates\spt-daemon\src\broker.rs:195:/// an isolated bounded queue + writer thread, evicted on overflow). Every
crates\spt-daemon\src\broker.rs:203:    /// The single interactive controller's isolated sink (W1: a dedicated writer
crates\spt-daemon\src\broker.rs:220:    /// The broker is the SINGLE writer of `driven_by` + `viewer_count` on this
crates\spt-daemon\src\broker.rs:243:    /// W1: now an `Arc<AtomicU64>` shared with the controller's writer thread,
crates\spt-daemon\src\broker.rs:293:    /// drain does a bounded, off-lock send into the controller's writer-thread
crates\spt-daemon\src\broker.rs:322:        // into the controller writer thread's channel (which does the blocking
crates\spt-daemon\src\broker.rs:343:    /// writer thread (symmetric with [`add_viewer`]) — it is no longer written
crates\spt-daemon\src\broker.rs:344:    /// inline under the log lock. The writer thread advances the authoritative
crates\spt-daemon\src\broker.rs:347:    /// ends its writer; bumping `controller_epoch` makes a stale deadline-evict a
crates\spt-daemon\src\broker.rs:350:        // Drop the prior controller sink first (its writer ends when tx drops),
crates\spt-daemon\src\broker.rs:355:        // Ring replay = the writer thread's initial batch (owned by the thread,
crates\spt-daemon\src\broker.rs:363:        let (tx, rx) = sync_channel::<CtrlMsg>(CONTROLLER_CHANNEL_DEPTH);
crates\spt-daemon\src\broker.rs:364:        let writer_send = Arc::clone(&sub);
crates\spt-daemon\src\broker.rs:367:        let writer = thread::spawn(move || controller_writer(sid, writer_send, initial, rx, dt));
crates\spt-daemon\src\broker.rs:372:            _writer: writer,
crates\spt-daemon\src\broker.rs:380:    /// writer thread; clearing the slot re-stamps `driven_by` to `None` (so an
crates\spt-daemon\src\broker.rs:398:    /// Add a read-only viewer: snapshot the ring from `from_seq` into the writer
crates\spt-daemon\src\broker.rs:405:        // The size frame + the ring replay are the writer's initial batch (owned
crates\spt-daemon\src\broker.rs:415:        let (tx, rx) = sync_channel::<Envelope>(VIEWER_CHANNEL_DEPTH);
crates\spt-daemon\src\broker.rs:416:        let writer_send = Arc::clone(&sub);
crates\spt-daemon\src\broker.rs:417:        let writer = thread::spawn(move || viewer_writer(writer_send, initial, rx));
crates\spt-daemon\src\broker.rs:423:                _writer: writer,
crates\spt-daemon\src\broker.rs:484:                // writer thread (best-effort, off the dispatch thread) — the
crates\spt-daemon\src\broker.rs:534:            self.clear_controller(); // drops the sink (ends its writer) + unlatches
crates\spt-daemon\src\broker.rs:557:    /// broker is the single writer — resolves the clear-race). The remote-drive
crates\spt-daemon\src\broker.rs:572:    /// single writer). Best-effort, like [`OutputLog::stamp_driven_by`].
crates\spt-daemon\src\broker.rs:591:    /// atomic the controller writer thread advances.
crates\spt-daemon\src\broker.rs:597:/// A viewer's dedicated writer thread (REQ-HAZARD-VIEWER-ISOLATION): write the
crates\spt-daemon\src\broker.rs:602:fn viewer_writer(send: SharedSend, initial: Vec<Envelope>, rx: Receiver<Envelope>) {

[Showing results with pagination = limit: 50]


Established output-side writer-thread pattern (viewer_writer/controller_writer) is the template to mirror for input. Grabbing input-side anchors for the plan:


Grep

fn write_input|fn dispatch_input|fn flush_inject_floor|fn apply_once|inject_floor|struct .*\{.*pty|master|PtyMaster|fn stamp|health|status_extra|fn set_status


crates\spt-daemon\src\broker.rs:4://! would lose continuity on if the logic restarted: the PTY master+writer and
crates\spt-daemon\src\broker.rs:563:    fn stamp_driven_by(&self) {
crates\spt-daemon\src\broker.rs:574:    fn stamp_viewer_count(&self) {
crates\spt-daemon\src\broker.rs:767:    /// The PTY + child (PTY master/writer + spawned child process).
crates\spt-daemon\src\broker.rs:787:fn flush_inject_floor(floor: &Mutex<InjectFloor>, session: &PtySession) {
crates\spt-daemon\src\broker.rs:837:            flush_inject_floor(&floor, &session);
crates\spt-daemon\src\broker.rs:878:        flush_inject_floor(&floor, &session);
crates\spt-daemon\src\broker.rs:1459:    fn dispatch_input(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {


760	    /// session reverts to raw inject and `dispatch_input` stops pinging.
761	    faulted: Arc<AtomicBool>,
762	}
763
764	/// A child the broker hosts under a PTY — the §B-owned resources for one
765	/// session, plus its gapless-resume output log.
766	struct HostedSession {
767	    /// The PTY + child (PTY master/writer + spawned child process).
768	    session: Arc<PtySession>,
769	    /// The output drain pump (kept so teardown can stop it).
770	    #[allow(dead_code)] // held for ownership/teardown; not read directly
771	    drain: Drain,
772	    /// The sequenced, buffered output + current subscriber.
773	    log: Arc<Mutex<OutputLog>>,
774	    /// The owning endpoint (the spawn's label; empty = unlabeled). The
775	    /// inbound dispatcher's gate subject for remote-drive (D9-1).
776	    endpoint: String,
777	    /// The translation-binary attachment (Layer C), or `None` for a session with
778	    /// no `[message-idle-translation-binary]` — then inbound delivery raw-injects.
779	    translation: Option<Arc<Translation>>,
780	}
781
782	/// Drain the inject floor's buffered controller input to the PTY **after** an
783	/// injected sequence, then release the floor. Each step takes the buffered bytes
784	/// OFF the lock before writing (the W1 rule: never hold a lock across a PTY
785	/// write); input arriving mid-flush keeps buffering, so strict order is preserved
786	/// until the buffer drains empty and the floor frees.
787	fn flush_inject_floor(floor: &Mutex<InjectFloor>, session: &PtySession) {
788	    loop {
789	        let chunk = floor.lock().unwrap().take_or_release();
790	        match chunk {
791	            Some(bytes) => {
792	                let _ = session.write_input(&bytes);
793	            }
794	            None => break,
795	        }
796	    }
797	}
798
799	/// The inject worker (W2 Layer C): one thread per translation-bearing session. It
800	/// drives inbound `<EVENT>`s through the binary and applies the binary's keystroke
801	/// commands to the PTY ATOMICALLY w.r.t. the operator's live controller.
802	///
803	/// Per queued event: drop any stray pre-event commands, OPEN the floor (operator
804	/// input now buffers in `dispatch_input`), feed the binary the `{type:event}`,
805	/// then drain `{key}`/`{text}`/`{delay_ms}` into `session.write_input` until
806	/// `{commit}` — bounded by [`INJECT_COMMIT_DEADLINE`]. On commit: flush the
807	/// buffered controller bytes AFTER the injected sequence and release the floor. On
808	/// deadline-miss or binary death: flush+release anyway (operator input is never
809	/// stranded), then FAULT — terminate the binary, set `faulted`, and exit (the
810	/// session degrades to raw inject). The loop ends when `event_tx` is dropped
811	/// (session teardown), reaping the child.
812	///
813	/// The atomic open→drain→commit→flush is the W1 coexistence mechanism: an injected
814	/// sequence and the operator's live controller share one PTY without clobbering
815	/// each other (REQ-HAZARD-INJECT-CONTROL-COEXIST), and the deadline FAULT path is
816	/// the "never park forever" bound applied to injection.
817	// [impl->REQ-MSG-IDLE-TRANSLATION-BINARY]
818	// [impl->REQ-HAZARD-INJECT-CONTROL-COEXIST]
819	fn run_inject_worker(
820	    session: Arc<PtySession>,
821	    child: Arc<TranslationChild>,
822	    floor: Arc<Mutex<InjectFloor>>,
823	    event_rx: Receiver<String>,
824	    cmd_rx: Receiver<KeyCmd>,
825	    faulted: Arc<AtomicBool>,
826	) {
827	    // `event_rx` blocks here; events queue → one sequence at a time.
828	    for envelope in event_rx {
829	        // A well-behaved filter only emits in response to an event, but drop any
830	        // unsolicited commands left in the channel so they can't be mis-attributed
831	        // to this event's sequence.
832	        while cmd_rx.try_recv().is_ok() {}
833
834	        floor.lock().unwrap().open();
835	        if child.send(&ToBinary::Event { envelope }).is_err() {
836	            // stdin write failed → the binary is gone; release the floor + fault.
837	            flush_inject_floor(&floor, &session);
838	            fault_translation(&faulted, &child, "stdin write failed");
839	            return;
840	        }
841
842	        let deadline = Instant::now() + inject_commit_deadline();
843	        let mut committed = false;
844	        loop {
845	            let now = Instant::now();
846	            let remaining = deadline.saturating_duration_since(now);
847	            if remaining.is_zero() {
848	                break; // deadline → fault below
849	            }

    /// session reverts to raw inject and `dispatch_input` stops pinging.
    faulted: Arc<AtomicBool>,
}

/// A child the broker hosts under a PTY — the §B-owned resources for one
/// session, plus its gapless-resume output log.
struct HostedSession {
    /// The PTY + child (PTY master/writer + spawned child process).
    session: Arc<PtySession>,
    /// The output drain pump (kept so teardown can stop it).
    #[allow(dead_code)] // held for ownership/teardown; not read directly
    drain: Drain,
    /// The sequenced, buffered output + current subscriber.
    log: Arc<Mutex<OutputLog>>,
    /// The owning endpoint (the spawn's label; empty = unlabeled). The
    /// inbound dispatcher's gate subject for remote-drive (D9-1).
    endpoint: String,
    /// The translation-binary attachment (Layer C), or `None` for a session with
    /// no `[message-idle-translation-binary]` — then inbound delivery raw-injects.
    translation: Option<Arc<Translation>>,
}

/// Drain the inject floor's buffered controller input to the PTY **after** an
/// injected sequence, then release the floor. Each step takes the buffered bytes
/// OFF the lock before writing (the W1 rule: never hold a lock across a PTY
/// write); input arriving mid-flush keeps buffering, so strict order is preserved
/// until the buffer drains empty and the floor frees.
fn flush_inject_floor(floor: &Mutex<InjectFloor>, session: &PtySession) {
    loop {
        let chunk = floor.lock().unwrap().take_or_release();
        match chunk {
            Some(bytes) => {
                let _ = session.write_input(&bytes);
            }
            None => break,
        }
    }
}

/// The inject worker (W2 Layer C): one thread per translation-bearing session. It
/// drives inbound `<EVENT>`s through the binary and applies the binary's keystroke
/// commands to the PTY ATOMICALLY w.r.t. the operator's live controller.
///
/// Per queued event: drop any stray pre-event commands, OPEN the floor (operator
/// input now buffers in `dispatch_input`), feed the binary the `{type:event}`,
/// then drain `{key}`/`{text}`/`{delay_ms}` into `session.write_input` until
/// `{commit}` — bounded by [`INJECT_COMMIT_DEADLINE`]. On commit: flush the
/// buffered controller bytes AFTER the injected sequence and release the floor. On
/// deadline-miss or binary death: flush+release anyway (operator input is never
/// stranded), then FAULT — terminate the binary, set `faulted`, and exit (the
/// session degrades to raw inject). The loop ends when `event_tx` is dropped
/// (session teardown), reaping the child.
///
/// The atomic open→drain→commit→flush is the W1 coexistence mechanism: an injected
/// sequence and the operator's live controller share one PTY without clobbering
/// each other (REQ-HAZARD-INJECT-CONTROL-COEXIST), and the deadline FAULT path is
/// the "never park forever" bound applied to injection.
// [impl->REQ-MSG-IDLE-TRANSLATION-BINARY]
// [impl->REQ-HAZARD-INJECT-CONTROL-COEXIST]
fn run_inject_worker(
    session: Arc<PtySession>,
    child: Arc<TranslationChild>,
    floor: Arc<Mutex<InjectFloor>>,
    event_rx: Receiver<String>,
    cmd_rx: Receiver<KeyCmd>,
    faulted: Arc<AtomicBool>,
) {
    // `event_rx` blocks here; events queue → one sequence at a time.
    for envelope in event_rx {
        // A well-behaved filter only emits in response to an event, but drop any
        // unsolicited commands left in the channel so they can't be mis-attributed
        // to this event's sequence.
        while cmd_rx.try_recv().is_ok() {}

        floor.lock().unwrap().open();
        if child.send(&ToBinary::Event { envelope }).is_err() {
            // stdin write failed → the binary is gone; release the floor + fault.
            flush_inject_floor(&floor, &session);
            fault_translation(&faulted, &child, "stdin write failed");
            return;
        }

        let deadline = Instant::now() + inject_commit_deadline();
        let mut committed = false;
        loop {
            let now = Instant::now();
            let remaining = deadline.saturating_duration_since(now);
            if remaining.is_zero() {
                break; // deadline → fault below
            }


Enough grounding. Writing the P0 JIT plan with anchors for the fresh context:


Write

# V0.13.0 P0 — JIT build plan (PTY input single-writer thread; paste-wedge)

**For: the post-self-clear todlando context.** P2 (resume-blank) is DONE + gate-passed by doyle @153c9c9. This is the NEXT body of work: **P0, the active v0.13.0 ship-blocker.** Build it, surface green to doyle for gate.

**Design (authoritative):** `V0.13.0-P0-PTY-INPUT-WRITER-DESIGN.md` (repo root). Read it first.
**REQ:** `REQ-HAZARD-PTY-INPUT-WRITER-WEDGE` — already SEEDED in `traceable-reqs.toml` (`required_stages = []`, ~line 1249). **ACTIVATE to `["impl","unit","int"]` registry-first** before tagging evidence.

## The bug (one line)
Paste into `spt rc` → a burst fills the ConPTY input buffer → `session.write_input(&bytes)` runs SYNCHRONOUSLY on the broker dispatch thread and blocks → dispatch can't service the next frame (re-attach subscribe / become_controller restore-write / inject-floor flush) → broker WEDGES (`brain IPC read deadline`). Completes the W1b-DEFERRED fix (2) of `REQ-HAZARD-EFFECT-JOURNAL-PTY-WEDGE`.

## The fix
One dedicated **input-writer thread per session** = the SOLE caller of the blocking `write_input`, fed by a **bounded FIFO** (`sync_channel`). Every caller ENQUEUES (`try_send`) + returns immediately. Queue full ⇒ DROP excess + stamp session `INPUT_BACKPRESSURE` (never wedge; a slow harness self-heals as the writer drains). Order preserved (single FIFO + single writer); exactly-once preserved (PtyWrite is ephemeral — `apply_once` effect = the non-blocking enqueue → Applied).

## MIRROR THE EXISTING OUTPUT-SIDE PATTERN
The broker ALREADY has the dedicated-writer-thread + bounded-channel pattern for OUTPUT — copy its shape for INPUT:
- `broker.rs:602 viewer_writer` / `controller_writer` (~367) — writer thread draining a `sync_channel` to a blocking write.
- `ViewerSink` (~111) / `ControllerSink` (~175) — the `{ SyncSender, JoinHandle }` holder; thread exits when `tx` drops.
- `add_viewer` (~398) / `set_controller` (~350) — spawn-the-writer sites.
Apply the SAME to the PTY write side: a `{ SyncSender<InputRecord>, JoinHandle }` on the session, thread = `input_writer` draining to `write_input`.

## Anchors (input/write side)
- `PtySession` — owns PTY master/writer + `write_input`; held as `Arc<PtySession>` on `HostedSession` (`broker.rs:766-768`). Seam = `spt_term::SessionSurface` (CONTEXT L435). The writer thread owns the sole `write_input` access.
- `HostedSession` struct (`broker.rs:764-780`) — natural home for the new input-writer sink (`{ tx: SyncSender<InputRecord>, _writer: JoinHandle<()> }`), spawned where `HostedSession`/`PtySession` is created (find the `HostedSession { session, drain, log, .. }` construction site).
- `dispatch_input` (`broker.rs:1459`) — the broker-dispatch write caller; also where inject-floor BUFFERING of operator bytes happens. Replace its `write_input` with enqueue.
- `flush_inject_floor` (`broker.rs:787`) — calls `session.write_input(&bytes)`; the W2 inject-floor flush. Its writes move to the writer thread (enqueue). The open→drain→commit→flush choreography (`run_inject_worker` ~819) keeps working with the lone writer.
- `serve_attach` → `brain.send_effect` (`attach.rs:197`; `brain.rs:449 send_effect`) → KIND_INPUT → broker dispatch → `dispatch_input`. The operator-keystroke path; it reaches `write_input` via dispatch_input, so fixing dispatch_input covers it.
- `apply_once(key, PtyWrite, effect)` — make the `effect` the enqueue (reserve key under lock → `try_send` → Applied). PtyWrite already ephemeral (`effect.rs:189-199` invariant) so Applied-at-enqueue is sound. Find apply_once in spt-daemon (grep `apply_once`).
- `INPUT_BACKPRESSURE` — NEW health/status signal. Check how existing session health/status is stamped (`stamp_driven_by`/`stamp_viewer_count` ~563-574 write to the perch via OutputLog; or `spt_store::liveness` STATUS_*). Pick the established surface; a wedged-harness drop must be operator-visible.

## Build order
1. ACTIVATE `REQ-HAZARD-PTY-INPUT-WRITER-WEDGE` = `["impl","unit","int"]`; add a note in `REQ-HAZARD-EFFECT-JOURNAL-PTY-WEDGE` that fix (2) is delivered here.
2. Define `InputRecord` + the bounded `sync_channel` + `input_writer` thread fn (mirror viewer_writer). Capacity sized for a generous paste (few hundred KB of records — design §20).
3. Add the input-writer sink to `HostedSession`/`PtySession`; spawn at session creation; thread is the sole `write_input` caller.
4. Convert callers to enqueue (`try_send`): `dispatch_input`, `flush_inject_floor`, the send_effect→KIND_INPUT path. Full channel ⇒ DROP + stamp `INPUT_BACKPRESSURE`.
5. `apply_once` PtyWrite effect = enqueue.
6. Inject-floor choreography stays correct under the lone writer (verify run_inject_worker + flush still order-correct).
7. doc: KNOWN-HAZARDS 7.17 + CONTEXT note (PTY input single-writer; callers enqueue never block; drop+signal on wedged harness).
8. Tests (DELEGATE to `spt-test-engineer` subagent per memory `delegate-tests-to-spt-test-engineer`; run background to stay reachable):
   - unit: writer-thread drain ORDER; bounded-queue DROP/HEAL decision (INJECTED channel, no wall-clock); exactly-once enqueue (PtyWrite reserve→enqueue→Applied; dedup).
   - int: **REPRO-FIRST, non-vacuous** (W1b's int escaped this). Real broker+PTY+rc, NO mocks. A harness stand-in that STOPS draining stdin so `write_input` genuinely PARKS → RED pre-fix (concurrent rc attach gets `brain IPC read deadline`/no output) → GREEN post-fix (concurrent attach OPENS + RECEIVES output, operator keystroke still accepted, DROP+`INPUT_BACKPRESSURE` on saturation + HEAL on resume). cfg(unix) forkpty park leg covered (W1b gravity-linux follow-up folds in).
9. GATES (run independently, spt-verification-gates memory): `cargo clippy --workspace --all-targets -- -D warnings` = 0 · `traceable-reqs check` EXIT=0 (`+impl +unit +int`) · `cargo run -p xtask -- check` (docs-drift) · rebuild real bin · spt-daemon e2e needs `--test-threads=1`.
10. Commit (trailer `Co-authored by: todlando`) on branch `v0.13.0-delivery-control`; surface P0 green to doyle (`$OWL send doyle`). doyle gates; then operator HITL (paste a large block in a real CC rc session: lands, no wedge, can still attach).

## doyle gate criteria (from design §52)
Repro genuinely blocks write_input (RED pre-fix) · concurrent attach opens+gets output under a parked write · operator keystroke still accepted · DROP+`INPUT_BACKPRESSURE` on saturation, heal on resume · exactly-once + order preserved · inject-floor choreography intact · Unix forkpty leg · clippy/traceable/docs-drift.

## State
- Branch `v0.13.0-delivery-control`. P2 @153c9c9 gate-passed. P1 (`REQ-RC-WIN-PASTE`) DEPENDS ON P0 — build P0 first, then P1.
- TZ-form (P2 design D) left as `%z` offset pending operator HITL; doyle asking operator — DON'T rework unless doyle relays.
- Live as `todlando`, Psyche companion active, gate = doyle, adapter follow-on = perri/peri.


File created successfully at: C:\Users\decid\Documents\projects\spt-core\V0.13.0-P0-JIT-PLAN.md (file state is current in your context — no need to Read it back)

# V0.13.0 P0 — JIT build plan (PTY input single-writer thread; paste-wedge)

**For: the post-self-clear todlando context.** P2 (resume-blank) is DONE + gate-passed by doyle @153c9c9. This is the NEXT body of work: **P0, the active v0.13.0 ship-blocker.** Build it, surface green to doyle for gate.

**Design (authoritative):** `V0.13.0-P0-PTY-INPUT-WRITER-DESIGN.md` (repo root). Read it first.
**REQ:** `REQ-HAZARD-PTY-INPUT-WRITER-WEDGE` — already SEEDED in `traceable-reqs.toml` (`required_stages = []`, ~line 1249). **ACTIVATE to `["impl","unit","int"]` registry-first** before tagging evidence.

## The bug (one line)
Paste into `spt rc` → a burst fills the ConPTY input buffer → `session.write_input(&bytes)` runs SYNCHRONOUSLY on the broker dispatch thread and blocks → dispatch can't service the next frame (re-attach subscribe / become_controller restore-write / inject-floor flush) → broker WEDGES (`brain IPC read deadline`). Completes the W1b-DEFERRED fix (2) of `REQ-HAZARD-EFFECT-JOURNAL-PTY-WEDGE`.

## The fix
One dedicated **input-writer thread per session** = the SOLE caller of the blocking `write_input`, fed by a **bounded FIFO** (`sync_channel`). Every caller ENQUEUES (`try_send`) + returns immediately. Queue full ⇒ DROP excess + stamp session `INPUT_BACKPRESSURE` (never wedge; a slow harness self-heals as the writer drains). Order preserved (single FIFO + single writer); exactly-once preserved (PtyWrite is ephemeral — `apply_once` effect = the non-blocking enqueue → Applied).

## MIRROR THE EXISTING OUTPUT-SIDE PATTERN
The broker ALREADY has the dedicated-writer-thread + bounded-channel pattern for OUTPUT — copy its shape for INPUT:
- `broker.rs:602 viewer_writer` / `controller_writer` (~367) — writer thread draining a `sync_channel` to a blocking write.
- `ViewerSink` (~111) / `ControllerSink` (~175) — the `{ SyncSender, JoinHandle }` holder; thread exits when `tx` drops.
- `add_viewer` (~398) / `set_controller` (~350) — spawn-the-writer sites.
Apply the SAME to the PTY write side: a `{ SyncSender<InputRecord>, JoinHandle }` on the session, thread = `input_writer` draining to `write_input`.

## Anchors (input/write side)
- `PtySession` — owns PTY master/writer + `write_input`; held as `Arc<PtySession>` on `HostedSession` (`broker.rs:766-768`). Seam = `spt_term::SessionSurface` (CONTEXT L435). The writer thread owns the sole `write_input` access.
- `HostedSession` struct (`broker.rs:764-780`) — natural home for the new input-writer sink (`{ tx: SyncSender<InputRecord>, _writer: JoinHandle<()> }`), spawned where `HostedSession`/`PtySession` is created (find the `HostedSession { session, drain, log, .. }` construction site).
- `dispatch_input` (`broker.rs:1459`) — the broker-dispatch write caller; also where inject-floor BUFFERING of operator bytes happens. Replace its `write_input` with enqueue.
- `flush_inject_floor` (`broker.rs:787`) — calls `session.write_input(&bytes)`; the W2 inject-floor flush. Its writes move to the writer thread (enqueue). The open→drain→commit→flush choreography (`run_inject_worker` ~819) keeps working with the lone writer.
- `serve_attach` → `brain.send_effect` (`attach.rs:197`; `brain.rs:449 send_effect`) → KIND_INPUT → broker dispatch → `dispatch_input`. The operator-keystroke path; it reaches `write_input` via dispatch_input, so fixing dispatch_input covers it.
- `apply_once(key, PtyWrite, effect)` — make the `effect` the enqueue (reserve key under lock → `try_send` → Applied). PtyWrite already ephemeral (`effect.rs:189-199` invariant) so Applied-at-enqueue is sound. Find apply_once in spt-daemon (grep `apply_once`).
- `INPUT_BACKPRESSURE` — NEW health/status signal. Check how existing session health/status is stamped (`stamp_driven_by`/`stamp_viewer_count` ~563-574 write to the perch via OutputLog; or `spt_store::liveness` STATUS_*). Pick the established surface; a wedged-harness drop must be operator-visible.

## Build order
1. ACTIVATE `REQ-HAZARD-PTY-INPUT-WRITER-WEDGE` = `["impl","unit","int"]`; add a note in `REQ-HAZARD-EFFECT-JOURNAL-PTY-WEDGE` that fix (2) is delivered here.
2. Define `InputRecord` + the bounded `sync_channel` + `input_writer` thread fn (mirror viewer_writer). Capacity sized for a generous paste (few hundred KB of records — design §20).
3. Add the input-writer sink to `HostedSession`/`PtySession`; spawn at session creation; thread is the sole `write_input` caller.
4. Convert callers to enqueue (`try_send`): `dispatch_input`, `flush_inject_floor`, the send_effect→KIND_INPUT path. Full channel ⇒ DROP + stamp `INPUT_BACKPRESSURE`.
5. `apply_once` PtyWrite effect = enqueue.
6. Inject-floor choreography stays correct under the lone writer (verify run_inject_worker + flush still order-correct).
7. doc: KNOWN-HAZARDS 7.17 + CONTEXT note (PTY input single-writer; callers enqueue never block; drop+signal on wedged harness).
8. Tests (DELEGATE to `spt-test-engineer` subagent per memory `delegate-tests-to-spt-test-engineer`; run background to stay reachable):
   - unit: writer-thread drain ORDER; bounded-queue DROP/HEAL decision (INJECTED channel, no wall-clock); exactly-once enqueue (PtyWrite reserve→enqueue→Applied; dedup).
   - int: **REPRO-FIRST, non-vacuous** (W1b's int escaped this). Real broker+PTY+rc, NO mocks. A harness stand-in that STOPS draining stdin so `write_input` genuinely PARKS → RED pre-fix (concurrent rc attach gets `brain IPC read deadline`/no output) → GREEN post-fix (concurrent attach OPENS + RECEIVES output, operator keystroke still accepted, DROP+`INPUT_BACKPRESSURE` on saturation + HEAL on resume). cfg(unix) forkpty park leg covered (W1b gravity-linux follow-up folds in).
9. GATES (run independently, spt-verification-gates memory): `cargo clippy --workspace --all-targets -- -D warnings` = 0 · `traceable-reqs check` EXIT=0 (`+impl +unit +int`) · `cargo run -p xtask -- check` (docs-drift) · rebuild real bin · spt-daemon e2e needs `--test-threads=1`.
10. Commit (trailer `Co-authored by: todlando`) on branch `v0.13.0-delivery-control`; surface P0 green to doyle (`$OWL send doyle`). doyle gates; then operator HITL (paste a large block in a real CC rc session: lands, no wedge, can still attach).

## doyle gate criteria (from design §52)
Repro genuinely blocks write_input (RED pre-fix) · concurrent attach opens+gets output under a parked write · operator keystroke still accepted · DROP+`INPUT_BACKPRESSURE` on saturation, heal on resume · exactly-once + order preserved · inject-floor choreography intact · Unix forkpty leg · clippy/traceable/docs-drift.

## State
- Branch `v0.13.0-delivery-control`. P2 @153c9c9 gate-passed. P1 (`REQ-RC-WIN-PASTE`) DEPENDS ON P0 — build P0 first, then P1.
- TZ-form (P2 design D) left as `%z` offset pending operator HITL; doyle asking operator — DON'T rework unless doyle relays.
- Live as `todlando`, Psyche companion active, gate = doyle, adapter follow-on = perri/peri.


JIT plan written. Now commune (Psyche sync) + self-clear per the self-management protocol.


Skill

P2 (resume-blank ship-blocker, REQ-SESSION-RESUME-TEMPLATE) DONE + doyle GATE-PASS @153c9c9 on branch v0.13.0-delivery-control. Delivered: [session.resume] native-resume manifest role (mirrors psyche_resume) + prepare_harness_spawn(is_resume) selection w/ self fallback; per-row ledger cwd (additive serde-default) + cmd_endpoint_run cwd fallback chain (picker row->info.cwd->current_dir); picker threads row cwd thru Outcome::Run; ResumeRow::title() local-time reformat (%z offset, design-D fallback — left as-is pending operator HITL, doyle asking operator, DON'T rework unless doyle relays); public docs MANIFEST.md + harness-contract teach [session.resume] (perri-buildable). Tests unit+int (real daemon+broker+PTY) via spt-test-engineer, mutation-proven. Gates green: clippy 0, traceable +doc+impl+unit+int, docs-drift OK.

IMMEDIATE NEXT: P0 = REQ-HAZARD-PTY-INPUT-WRITER-WEDGE, the ACTIVE v0.13.0 ship-blocker (paste into spt rc wedges broker; write_input blocks the dispatch thread). doyle said PROCEED. Full grounded build plan written to V0.13.0-P0-JIT-PLAN.md (repo root) with all broker.rs anchors + the mirror-the-output-writer-thread approach. Design: V0.13.0-P0-PTY-INPUT-WRITER-DESIGN.md. Fix = per-session input-writer thread (sole write_input caller) + bounded FIFO + try-enqueue at all callers + DROP+INPUT_BACKPRESSURE on saturation. REPRO-FIRST int (W1b's escaped). REQ already SEEDED, activate [impl,unit,int]. Then P1 (REQ-RC-WIN-PASTE) which DEPENDS on P0.

PROJECT END GOAL: spt-core = harness-independent agent ecosystem core (messaging, live-agent lifecycle, terminal hosting, P2P, runtime-manifest harness contract); clean-room rebuild of claude_skill_owl. Currently closing out v0.13.0 delivery-control round-2 (doyle /diagnose-found ship-blockers from post-deploy operator dogfood): P2 done, P0 next (blocks ship), P1 after. Gate=doyle, adapter follow-on=perri. About to self-clear to a fresh context to build P0.


Launching skill: spt:commune


Base directory for this skill: C:\Users\decid\.claude\plugins\cache\cplugs\spt\1.11.25\skills\commune

# /spt:commune

All commands use `$LIVE` env var, auto-injected by the plugin's SessionStart hook. If commands fail with "command not found", restart the Claude Code session so the SessionStart hook re-runs.

> **Identity auto-detection:** Your identity is auto-detected from your session. Pass your ID explicitly only if auto-detection fails.

## Flow

1. **Download Psyche's *current* context** — it shifts during the session as background echo communes fire. Re-download right before composing; the gen-N snapshot you read at session start is already stale:
   ```bash
   $LIVE psyche-download <your-id>
   ```

   > **Output truncation.** If the Bash tool reports `Output too large (N KB)` and saves the full result to a `tool-results/<id>.txt` file, use the `Read` tool on that saved path to access the full content. Do NOT re-run `psyche-download` to grep/awk out sections — repeated invocations re-download the same context, waste tokens, and may duplicate `## Pending Commune` sections in `live_context.md`.

2. **Diff your knowledge against this fresh download** — only what's missing from *it* is the real delta (new work, decisions, intentions). Anything already absorbed is not. If the download includes a `<memformat>` section, use it as a guide for what topics to cover. A `## Pending Commune (uncommitted)` section means a previous commune is still queued — do NOT re-send it.

3. **Commune only the delta** -- use the **Write tool** to create
   `.claude/{your-id}-commune.md` with the two-slice body as its contents
   (see `## Two-slice body shape (Phase 25 D-10/D-11)` below for the required
   envelope wrapping). Do not use Bash/heredoc -- Write is the canonical path:
   no shell escaping, no `EOF` collision risk, and Write auto-creates the
   `.claude/` parent directory.

   Your Self listener detects the file on its next poll iteration, notifies
   your Psyche wrapper, and the wrapper ingests the content via the existing
   Psyche session. On success (subprocess exit code 0) the wrapper deletes the
   file. On error (token rate limit, etc) the file is retained and retried on
   the next consume cycle. If you write again before the wrapper consumes, the
   latest write wins (last-write-wins overwrite semantics).

If Psyche's context is already current, no commune is needed. Say so and move on.

Read `commune.md` (in this skill directory) for the full commune protocol -- when to send, what to include, and diligence triggers.

**Agent-type guard:** Only works on live agents. If target is not live, refuses with: "Communes require a live agent with Psyche."

## Two-slice body shape (Phase 25 D-10/D-11)

The body you write in `.claude/{your-id}-commune.md` MUST itself be wrapped in two-slice envelopes per Phase 25 D-10/D-11. The taxonomy — what belongs in `<live-context>` vs `<project-context>` — is defined canonically in `psyche.md` §`<output_envelope>` (lines 282–323). Read that section as the source of truth; this skill doc teaches only when and how to apply it.

**In-project detection rule (D-25.1-04).** Before composing your commune body, run `$LIVE psyche-download <your-id>` (Step 1 of `## Flow` above) and inspect the `<current ... />` tag at the top of the output. The `<current/>` tag carries a `project="..."` attribute whenever any payload is produced — its presence means you are inside a tracked project (inside a git repo, `project` is the repo name; outside a git repo per D-11, `project` is the cwd-basename fallback, which still counts as a project context for routing purposes). A `psyche-download` invocation that returns no payload at all (NO-CONTEXT exit) means no project resolved. So: `project="..."` populated on `<current/>` → emit BOTH envelopes (apply rule 1 or 2 below); no payload at all → emit ONLY `<live-context>` (rule 3). Secondary signal: the `<project-context-resolved name="..."/>` sentinel further down the output indicates the project file already has prior content — useful for spotting first-time-in-project pairings, but NOT the routing rule.

**Three rules (D-25.1-01..03):**

- **In-project, both slices populated** (marker present, you have project-specific content this cycle): emit `<live-context>...</live-context>` AND `<project-context>...</project-context>`, both with non-empty bodies.
- **In-project, no project-specific update this cycle** (marker present, but nothing project-bound to report): emit `<live-context>...</live-context>` AND `<project-context></project-context>` (empty body — the deliberate "in-project but quiet" signal per D-25.1-02). Distinguishable from "no project resolved".
- **Outside any tracked project** (marker absent): emit ONLY `<live-context>...</live-context>`. Do NOT emit an empty `<project-context>` envelope (missing tag means "no project resolved" per D-25.1-03).

**Nested envelopes.** The Phase 23 `<EVENT type="commune">` envelope (see `## Envelope shape (Phase 23 v1.8)` below) is composed by `$LIVE commune` automatically — you NEVER write `<EVENT>` tags. You write ONLY the inner two-slice body. The two envelope layers nest: outer EVENT wraps inner two-slice body.

(See `psyche.md` §`<output_envelope>` for the canonical taxonomy and per-slice content rules.)

## Envelope shape (Phase 23 v1.8)

As of v1.8 Phase 23, commune deliveries are wrapped in a typed EVENT envelope carrying a 5-field stamp (`machine`, `project`, `branch`, `head_sha`, `head_subject`); the prose `COMMUNE (timestamp): body` form is gone.

```xml
<EVENT type="commune" timestamp="2026-05-20T06:00:00-07:00" machine="hostname" project="claude_skill_owl" branch="main" head_sha="abc123..." head_subject="subject line capped at 72 chars">body</EVENT>
```

Outside a git repo (D-11), `branch`, `head_sha`, and `head_subject` are omitted entirely (not empty-string-set):

```xml
<EVENT type="commune" timestamp="2026-05-20T06:00:00-07:00" machine="hostname" project="cwd-basename">body</EVENT>
```

`$LIVE commune` composes this envelope automatically — Self does NOT manually construct EVENT XML. The `body` placeholder in the example above is the two-slice body (see `## Two-slice body shape (Phase 25 D-10/D-11)` above); the two envelope layers nest. The wrapper-side parser ignores unknown attrs, so the stamp fields are forward-compatible with older receivers. For drift detection on resume, see the `live` skill.

ARGUMENTS: P2 (resume-blank ship-blocker, REQ-SESSION-RESUME-TEMPLATE) DONE + doyle GATE-PASS @153c9c9 on branch v0.13.0-delivery-control. Delivered: [session.resume] native-resume manifest role (mirrors psyche_resume) + prepare_harness_spawn(is_resume) selection w/ self fallback; per-row ledger cwd (additive serde-default) + cmd_endpoint_run cwd fallback chain (picker row->info.cwd->current_dir); picker threads row cwd thru Outcome::Run; ResumeRow::title() local-time reformat (%z offset, design-D fallback — left as-is pending operator HITL, doyle asking operator, DON'T rework unless doyle relays); public docs MANIFEST.md + harness-contract teach [session.resume] (perri-buildable). Tests unit+int (real daemon+broker+PTY) via spt-test-engineer, mutation-proven. Gates green: clippy 0, traceable +doc+impl+unit+int, docs-drift OK.

IMMEDIATE NEXT: P0 = REQ-HAZARD-PTY-INPUT-WRITER-WEDGE, the ACTIVE v0.13.0 ship-blocker (paste into spt rc wedges broker; write_input blocks the dispatch thread). doyle said PROCEED. Full grounded build plan written to V0.13.0-P0-JIT-PLAN.md (repo root) with all broker.rs anchors + the mirror-the-output-writer-thread approach. Design: V0.13.0-P0-PTY-INPUT-WRITER-DESIGN.md. Fix = per-session input-writer thread (sole write_input caller) + bounded FIFO + try-enqueue at all callers + DROP+INPUT_BACKPRESSURE on saturation. REPRO-FIRST int (W1b's escaped). REQ already SEEDED, activate [impl,unit,int]. Then P1 (REQ-RC-WIN-PASTE) which DEPENDS on P0.

PROJECT END GOAL: spt-core = harness-independent agent ecosystem core (messaging, live-agent lifecycle, terminal hosting, P2P, runtime-manifest harness contract); clean-room rebuild of claude_skill_owl. Currently closing out v0.13.0 delivery-control round-2 (doyle /diagnose-found ship-blockers from post-deploy operator dogfood): P2 done, P0 next (blocks ship), P1 after. Gate=doyle, adapter follow-on=perri. About to self-clear to a fresh context to build P0.


Bash

$LIVE psyche-download todlando 2>&1 | head -40

Fresh Psyche download before commune


<memformat>
<?xml version="1.0"?>
<memformat version="1">
<!-- Changelog: v1 - initial seed -->
<topics>
  <topic name="events">Notable or unusual events and requests</topic>
  <topic name="interactions">Agent interactions -- who, roles, contributions</topic>
  <topic name="reflections">Thoughts on recent work -- what went well, what was tricky</topic>
  <topic name="ideas">Ideas to raise with the user</topic>
  <topic name="improvements">Further improvement ideas for recent problem spaces</topic>
  <topic name="timeline">What happened when, timing, sequencing</topic>
  <topic name="blockers">Current blockers or concerns</topic>
  <topic name="focus">Current focus and immediate next steps</topic>
</topics>
</memformat>
</memformat>

<current machine="HFENDULEAM" project="spt-core" branch="v0.13.0-delivery-control" head_sha="153c9c90d03d9a23bf00f0e0ef645cc36235d2ed" head_subject="feat(session): [session.resume] native-resume role + per-row ledger cwd …" commits_since="0" commits_unpulled="0"/>
v0.13.0 build + unit delivery-control refactor: **tasks 4–6 complete**. Implementation (callsites + dispatch/CLI edits) + docs done + pre-verified. Manifest role `[session.resume]` added; all four `cwd` keys fit existing `SUBSTITUTION_KEYS` (no schema expansion needed). Picker threads `Outcome::Run::cwd` + `ResumeRow::cwd` through resume flow. Fallback chain: picker row → resume `info.cwd` → `current_dir`. Schema blessed, `xtask check` OK, workspace exit 0. **Pending**: tests green (delegated to spt-test-engineer, background) + clippy + traceable before gate handoff to doyle. Standing by.<project-context-resolved name="spt-core"/>
**Resume-blank fix architecture locked**: `SessionEntry.cwd` additive + serde-default; populated at boundary + bind + ready appends. Resume-vs-self template selection: `prepare_harness_spawn(is_resume)` → resume role, else self fallback. `ResumeRow::title()` local-time reformat uses numeric UTC offset `%z` (chrono `%Z` cross-platform unreliable — **flagged for HITL operator confirm**). Test authoring delegated background. Docs: MANIFEST + harness-contract page teach `[session.resume]` perri-buildable.


Psyche current through "tasks 4-6, pending tests". Delta = tests green + doyle gate-pass + P0 next. In-project → both slices:


Write

<live-context>
DELTA since last commune (which ended at "P2 tasks 4-6 done, tests pending"):

P2 (REQ-SESSION-RESUME-TEMPLATE, resume-blank ship-blocker) is now FULLY DONE and **doyle GATE-PASSED @153c9c9**. Tests (unit + int) came back green from spt-test-engineer — mutation-proven, real daemon+broker+PTY int (dummy-harness resume cmd != self, marker-file proof of template selection + recorded cwd). I independently re-ran gates: clippy=0, traceable +doc+impl+unit+int, docs-drift OK, real bin built. Committed @153c9c9, surfaced to doyle, doyle re-verified independently and gated clean ("Excellent work. PROCEED TO P0").

TZ-form note: the %z numeric-offset choice for ResumeRow::title() is doyle-confirmed as the design-D acceptable fallback — LEAVE AS-IS pending operator HITL (doyle is asking the operator whether they want a short abbrev; DON'T rework unless doyle relays back).

IMMEDIATE NEXT WORK = P0 (REQ-HAZARD-PTY-INPUT-WRITER-WEDGE), the ACTIVE v0.13.0 ship-blocker — doyle said PROCEED, no changes wanted on P2. About to SELF-CLEAR to a fresh context and build P0 from the JIT plan I just wrote. P1 (REQ-RC-WIN-PASTE) is queued AFTER P0 (depends on it).

Reflection: P2 went clean end-to-end — design was code-grounded so impl was mechanical; delegating tests to spt-test-engineer (background, stayed reachable) worked well; the marker-file int design dodged PTY-output capture races by using --start detached. Mirroring the existing psyche_resume precedent meant zero novel design risk.
</live-context>
<project-context>
v0.13.0 delivery-control round-2 status (branch v0.13.0-delivery-control, HEAD @153c9c9):
- P2 REQ-SESSION-RESUME-TEMPLATE: DONE, doyle gate-passed @153c9c9. [session.resume] native-resume manifest role (Session.resume, mirrors psyche_resume) + prepare_harness_spawn(is_resume) selection w/ self fallback; SessionEntry.cwd additive serde-default (written at boundary/bind/ready appends); cmd_endpoint_run cwd fallback chain (picker row→resume info.cwd→current_dir); picker threads row cwd via Outcome::Run.cwd + ResumeRow.cwd; ResumeRow::title() local-time reformat (%z offset). Public docs MANIFEST.md + docs-site harness-contract/manifest.md teach [session.resume] (perri-buildable; perri's adapter follow-on declares `claude -r {session_id} ...` AFTER spt-core ships). Mock gained additive --marker-file for the int.
- P0 REQ-HAZARD-PTY-INPUT-WRITER-WEDGE: NEXT (active ship-blocker). REQ already SEEDED in traceable-reqs.toml — activate ["impl","unit","int"]. Full grounded build plan = V0.13.0-P0-JIT-PLAN.md (repo root); design = V0.13.0-P0-PTY-INPUT-WRITER-DESIGN.md. Fix: per-session input-writer thread = sole write_input caller, fed by bounded sync_channel; all callers (dispatch_input broker.rs:1459, flush_inject_floor broker.rs:787, serve_attach send_effect path) try_send + return; full queue ⇒ DROP + stamp INPUT_BACKPRESSURE. MIRROR the existing OUTPUT-side writer pattern (viewer_writer/controller_writer ~broker.rs:602/367, ViewerSink/ControllerSink). apply_once PtyWrite effect = enqueue. REPRO-FIRST int (W1b's escaped — must genuinely park write_input). Completes the W1b-deferred fix (2) of REQ-HAZARD-EFFECT-JOURNAL-PTY-WEDGE.
- P1 REQ-RC-WIN-PASTE: queued after P0 (depends on it). Design V0.13.0-P1-RC-PASTE-DESIGN.md, SEEDED, activate [impl,unit] (no int — live clipboard/console mouse = HITL).
- Gate = doyle (independently re-verifies). Adapter follow-on = perri. Build pattern: per-item REQ activate → impl+tag → delegate tests to spt-test-engineer (background) → gates (clippy/traceable/docs-drift/real-bin, spt-daemon e2e needs --test-threads=1) → commit (trailer `Co-authored by: todlando`) → surface green to doyle.
</project-context>


File created successfully at: C:\Users\decid\Documents\projects\spt-core\.claude\todlando-commune.md (file state is current in your context — no need to Read it back)

<live-context>
DELTA since last commune (which ended at "P2 tasks 4-6 done, tests pending"):

P2 (REQ-SESSION-RESUME-TEMPLATE, resume-blank ship-blocker) is now FULLY DONE and **doyle GATE-PASSED @153c9c9**. Tests (unit + int) came back green from spt-test-engineer — mutation-proven, real daemon+broker+PTY int (dummy-harness resume cmd != self, marker-file proof of template selection + recorded cwd). I independently re-ran gates: clippy=0, traceable +doc+impl+unit+int, docs-drift OK, real bin built. Committed @153c9c9, surfaced to doyle, doyle re-verified independently and gated clean ("Excellent work. PROCEED TO P0").

TZ-form note: the %z numeric-offset choice for ResumeRow::title() is doyle-confirmed as the design-D acceptable fallback — LEAVE AS-IS pending operator HITL (doyle is asking the operator whether they want a short abbrev; DON'T rework unless doyle relays back).

IMMEDIATE NEXT WORK = P0 (REQ-HAZARD-PTY-INPUT-WRITER-WEDGE), the ACTIVE v0.13.0 ship-blocker — doyle said PROCEED, no changes wanted on P2. About to SELF-CLEAR to a fresh context and build P0 from the JIT plan I just wrote. P1 (REQ-RC-WIN-PASTE) is queued AFTER P0 (depends on it).

Reflection: P2 went clean end-to-end — design was code-grounded so impl was mechanical; delegating tests to spt-test-engineer (background, stayed reachable) worked well; the marker-file int design dodged PTY-output capture races by using --start detached. Mirroring the existing psyche_resume precedent meant zero novel design risk.
</live-context>
<project-context>
v0.13.0 delivery-control round-2 status (branch v0.13.0-delivery-control, HEAD @153c9c9):
- P2 REQ-SESSION-RESUME-TEMPLATE: DONE, doyle gate-passed @153c9c9. [session.resume] native-resume manifest role (Session.resume, mirrors psyche_resume) + prepare_harness_spawn(is_resume) selection w/ self fallback; SessionEntry.cwd additive serde-default (written at boundary/bind/ready appends); cmd_endpoint_run cwd fallback chain (picker row→resume info.cwd→current_dir); picker threads row cwd via Outcome::Run.cwd + ResumeRow.cwd; ResumeRow::title() local-time reformat (%z offset). Public docs MANIFEST.md + docs-site harness-contract/manifest.md teach [session.resume] (perri-buildable; perri's adapter follow-on declares `claude -r {session_id} ...` AFTER spt-core ships). Mock gained additive --marker-file for the int.
- P0 REQ-HAZARD-PTY-INPUT-WRITER-WEDGE: NEXT (active ship-blocker). REQ already SEEDED in traceable-reqs.toml — activate ["impl","unit","int"]. Full grounded build plan = V0.13.0-P0-JIT-PLAN.md (repo root); design = V0.13.0-P0-PTY-INPUT-WRITER-DESIGN.md. Fix: per-session input-writer thread = sole write_input caller, fed by bounded sync_channel; all callers (dispatch_input broker.rs:1459, flush_inject_floor broker.rs:787, serve_attach send_effect path) try_send + return; full queue ⇒ DROP + stamp INPUT_BACKPRESSURE. MIRROR the existing OUTPUT-side writer pattern (viewer_writer/controller_writer ~broker.rs:602/367, ViewerSink/ControllerSink). apply_once PtyWrite effect = enqueue. REPRO-FIRST int (W1b's escaped — must genuinely park write_input). Completes the W1b-deferred fix (2) of REQ-HAZARD-EFFECT-JOURNAL-PTY-WEDGE.
- P1 REQ-RC-WIN-PASTE: queued after P0 (depends on it). Design V0.13.0-P1-RC-PASTE-DESIGN.md, SEEDED, activate [impl,unit] (no int — live clipboard/console mouse = HITL).
- Gate = doyle (independently re-verifies). Adapter follow-on = perri. Build pattern: per-item REQ activate → impl+tag → delegate tests to spt-test-engineer (background) → gates (clippy/traceable/docs-drift/real-bin, spt-daemon e2e needs --test-threads=1) → commit (trailer `Co-authored by: todlando`) → surface green to doyle.
</project-context>


Commune queued (Self listener will ingest + delete). Now self-clearing to a fresh context to build P0.


Skill

Resume P0 build (v0.13.0 ship-blocker). You are live agent todlando on branch v0.13.0-delivery-control. P2 (resume-blank) DONE + doyle gate-passed @153c9c9. NOW BUILD P0 = REQ-HAZARD-PTY-INPUT-WRITER-WEDGE. Read V0.13.0-P0-JIT-PLAN.md (repo root) FIRST — it has the full grounded plan + all broker.rs anchors + the mirror-the-output-writer-thread approach; design is V0.13.0-P0-PTY-INPUT-WRITER-DESIGN.md. Fix: per-session input-writer thread (sole write_input caller) + bounded sync_channel + try-enqueue at all callers + DROP+INPUT_BACKPRESSURE on saturation; REPRO-FIRST int. REQ already SEEDED — activate [impl,unit,int]. Delegate tests to spt-test-engineer (background, stay reachable). Gates: clippy 0 / traceable / docs-drift / real bin, spt-daemon e2e --test-threads=1. Commit (trailer `Co-authored by: todlando`), surface green to doyle. doyle is reachable + gates. P1 (REQ-RC-WIN-PASTE) after P0. TZ %z form left as-is pending operator HITL — don't rework unless doyle relays.


Launching skill: self-clear


Base directory for this skill: C:\Users\decid\.claude\skills\self-clear

# /self-clear

Wipes THIS agent's conversation (`/clear`), then ~200ms later TYPES a self-note
as a fresh user prompt so the agent wakes on a clean context and proceeds.

## Run it

One script does everything. Just run:

```powershell
& "C:\Users\decid\.claude\skills\self-clear\scripts\self-clear.ps1"
```

Expect: `SELF-CLEAR armed: id=<agent_id> winpid=<n> hwnd=<n> — /clear queued; self-note typed in ~200ms`

Then stop — end your turn promptly so the queued `/clear` fires.

## What the script does

1. `owl whoami` → resolves this session's `agent_id` (context-independent, survives the wipe). No perch → it aborts WITHOUT clearing and tells you to `/spt:ready` or `/spt:live` first.
2. Walks the parent chain (`pwsh → claude.exe → … → WindowsTerminal`) to the ancestor **pid that owns a visible top-level window** (enumerated by owner pid — not the program's default "main window", which is wrong when a host serves several windows). Since each WT window is its own process, that pid uniquely identifies this session's window. **Reliably focuses it** via `AttachThreadInput` (a background process can't otherwise hold the foreground) and **hard-verifies it genuinely holds the foreground** — aborts WITHOUT clearing and WITHOUT sending any keys if it can't.
3. Re-verifies foreground with **zero gap**, then types `/clear{ENTER}` in one SendKeys — **queued**; only fires when this turn ends.
4. Spawns a **detached** waker: waits 200ms (past turn-end + wipe), reliably re-focuses the captured `hwnd`, and — **only if that exact HWND genuinely holds the foreground** — **types** `Proceed with next steps` + Enter as a fresh user prompt. Otherwise it aborts the SendKeys (HWND identity is the guard; no title-match — Claude Code's terminal title animates and changes across `/clear`).

Why this matters: `SendKeys` hits whatever window is foreground *at send time*. The fix attaches our input queue to the foreground thread so the focus steal actually holds, confirms the target is truly foreground, and aborts rather than type blind — so the note can never leak into the window you're working in.

The delay + detachment are deliberate: the prompt must be typed **after**
`/clear` takes effect, so it lands on the clean context and starts a new turn.

Why typed, not `owl send`: after `/clear` the session sits idle awaiting user
input. A spooled owl message can't self-start a turn — it surfaces only during an
already-active turn (PreToolUse hook) or in a live Monitor stream (whose buffer
`/clear` wipes). So a self-addressed owl note never woke the agent. A typed prompt
deterministically starts a turn AND is the re-seed.

## Caveats

- Focus targets the window owned by this session's ancestor pid (enumerated by owner pid). It still can't pick a specific tab when one window hosts multiple tabs; clean when the agent's tab is active.
- The waker types only when our exact window is foreground. If you switch to a different window/tab during the ~200ms gap, the wake-note SendKeys is aborted (the agent stays cleared but unwoken) — stay in the agent window for the wake.
- The note is typed via SendKeys, so the terminal must stay the foreground-able window of `winpid`. Don't close/replace the terminal in the gap.
- SendKeys reserves `+ ^ % ~ ( ) { } [ ]`; the default note text uses none. If you customize the note, escape those chars.
- The waker waits only ~200ms after the script returns, so stop promptly — if your turn runs longer, the prompt could be typed before the wipe completes.

ARGUMENTS: Resume P0 build (v0.13.0 ship-blocker). You are live agent todlando on branch v0.13.0-delivery-control. P2 (resume-blank) DONE + doyle gate-passed @153c9c9. NOW BUILD P0 = REQ-HAZARD-PTY-INPUT-WRITER-WEDGE. Read V0.13.0-P0-JIT-PLAN.md (repo root) FIRST — it has the full grounded plan + all broker.rs anchors + the mirror-the-output-writer-thread approach; design is V0.13.0-P0-PTY-INPUT-WRITER-DESIGN.md. Fix: per-session input-writer thread (sole write_input caller) + bounded sync_channel + try-enqueue at all callers + DROP+INPUT_BACKPRESSURE on saturation; REPRO-FIRST int. REQ already SEEDED — activate [impl,unit,int]. Delegate tests to spt-test-engineer (background, stay reachable). Gates: clippy 0 / traceable / docs-drift / real bin, spt-daemon e2e --test-threads=1. Commit (trailer `Co-authored by: todlando`), surface green to doyle. doyle is reachable + gates. P1 (REQ-RC-WIN-PASTE) after P0. TZ %z form left as-is pending operator HITL — don't rework unless doyle relays.


Running the self-clear script. (The detailed resume substance is carried by the queued commune + the persisted `V0.13.0-P0-JIT-PLAN.md`; the script's wake-note just restarts a turn on the clean context.)


PowerShell

Arm self-clear: queue /clear + wake note


SELF-CLEAR armed: id=todlando winpid=11076 hwnd=65976 title="✳ SessionStart identity injection for bind topology" — focus held + /clear queued; self-note typed in ~200ms (waker aborts unless our window holds foreground)


Self-clear armed. P2 closed and gate-passed @`153c9c9`; P0 plan persisted to `V0.13.0-P0-JIT-PLAN.md`; commune queued for the echo-brief. Ending turn now so the queued `/clear` fires and the fresh context wakes on P0.


<local-command-caveat>Caveat: The messages below were generated by the user while running local commands. DO NOT respond to these messages or otherwise consider them in your response unless the user explicitly asks you to.</local-command-caveat>


<command-name>/reload-skills</command-name>
            <command-message>reload-skills</command-message>
            <command-args></command-args>


<local-command-stdout>Reloaded skills: 166 skills available (1 removed)</local-command-stdout>