1	//! B1 integration: a [`Broker`] hosts one real PTY child and relays its output
2	//! and injected input over the versioned local IPC, on both OSes.
3	//!
4	//! The child is an echo program (`cat` on Unix, `findstr "."` on Windows — the
5	//! same cross-OS shape spt-term's injection tests use): bytes the brain injects
6	//! round-trip through the real PTY and come back as `output` events. We then
7	//! kill it and observe the `exit` event. This exercises the whole B1 contract —
8	//! spawn under a broker PTY, stream output, accept input, report exit — through
9	//! the actual named-pipe / UDS transport.
10	//!
11	//! The brain is driven **single-threaded**: send a command, then read frames
12	//! until the expected reply/event arrives (accumulating any interleaved output).
13	//! interprocess `Stream::split()` shares the underlying fd, so a split client
14	//! would only signal EOF once *both* halves drop; keeping one `Stream` means
15	//! dropping it closes the fd, the broker's command loop hits EOF, and it tears
16	//! the session down deterministically — which we then assert.
17	// [int->REQ-DAEMON-2]
18
19	use std::sync::atomic::{AtomicU32, Ordering};
20	use std::sync::Arc;
21	use std::thread;
22	use std::time::Duration;
23
24	use interprocess::local_socket::Stream;
25	use spt_daemon::codec::{read_frame, write_frame};
26	use spt_daemon::frame::{Envelope, Role};
27	use spt_daemon::msg::{
28	    decode_bytes, encode_bytes, AppliedEvent, EndpointInjected, EndpointInputReq, ExitEvent,
29	    InputReq, KillReq, OutputEvent, SpawnReq, Spawned, SubscribeReq, KIND_APPLIED,
30	    KIND_ENDPOINT_INJECTED, KIND_ENDPOINT_INPUT, KIND_ERROR, KIND_EXIT, KIND_INPUT, KIND_KILL,
31	    KIND_OUTPUT, KIND_SPAWN, KIND_SPAWNED, KIND_SUBSCRIBE,
32	};
33	use spt_daemon::transport::{send_hello, LocalSocketTransport};
34	use spt_daemon::Broker;
35
36	static SEQ: AtomicU32 = AtomicU32::new(0);
37	fn unique_name() -> String {
38	    let n = SEQ.fetch_add(1, Ordering::Relaxed);
39	    format!("spt-daemon-b1-{}-{}.sock", std::process::id(), n)
40	}
41
42	/// The echo child: reads stdin, writes each line to stdout.
43	fn echo_spawn_req() -> SpawnReq {
44	    #[cfg(unix)]
45	    let (program, args) = ("cat".to_string(), Vec::<String>::new());
46	    #[cfg(windows)]
47	    let (program, args) = ("findstr".to_string(), vec![".".to_string()]);
48	    SpawnReq {
49	        program,
50	        args,
51	        rows: 24,
52	        cols: 80,
53	        endpoint: String::new(),
54	        cwd: None,
55	        env: Default::default(),
56	        translation_binary: None,
57	    }
58	}
59
60	fn send(conn: &mut Stream, kind: &str, payload: serde_json::Value) {
61	    write_frame(conn, &Envelope::new(kind, payload)).expect("send frame");
62	}
63
64	fn main_test() {
65	    let name = unique_name();
66	    let broker = Broker::bind(&name).expect("bind broker");
67
68	    // The broker accepts one brain and serves it on a handler thread.
69	    let server_broker = Arc::clone(&broker);
70	    let server = thread::spawn(move || {
71	        let handle = server_broker.serve_once().expect("accept brain");
72	        handle.join().expect("handler thread");
73	    });
74
75	    // Brain side: connect (retry briefly in case accept lags the bind).
76	    let mut conn = None;
77	    for _ in 0..200 {
78	        match LocalSocketTransport::connect(&name) {
79	            Ok(c) => {
80	                conn = Some(c);
81	                break;
82	            }
83	            Err(_) => thread::sleep(Duration::from_millis(10)),
84	        }
85	    }
86	    let mut conn = conn.expect("connect to broker");
87	    // (No per-read timeout: Windows named pipes reject I/O timeouts. The broker
88	    // always pushes `spawned`/`output`/`exit` frames, so the blocking reads
89	    // below return; a genuine stall is caught by the test runner's own timeout.)
90
91	    send_hello(&mut conn, Role::Brain).expect("send hello");
92
93	    // Spawn the echo child under a broker PTY; read until the `spawned` reply,
94	    // accumulating any output that interleaves.
95	    send(
96	        &mut conn,
97	        KIND_SPAWN,
98	        serde_json::to_value(echo_spawn_req()).unwrap(),
99	    );
100	    let mut out = Vec::<u8>::new();
101	    let sid = loop {
102	        let f = read_frame(&mut conn).expect("frame before spawned");
103	        match f.kind.as_str() {
104	            KIND_SPAWNED => {
105	                break serde_json::from_value::<Spawned>(f.payload)
106	                    .unwrap()
107	                    .session_id;
108	            }
109	            KIND_OUTPUT => accumulate_output(&f, &mut out),
110	            _ => {}
111	        }
112	    };
113
114	    // Inject a line; it must round-trip through the real PTY back as output.
115	    send(
116	        &mut conn,
117	        KIND_INPUT,
118	        serde_json::to_value(InputReq {
119	            session_id: sid,
120	            data_b64: encode_bytes(b"INJECT-ALPHA\r"),
121	            op_id: None,
122	            ack: true,
123	        })
124	        .unwrap(),
125	    );
126	    let echoed = loop {
127	        if String::from_utf8_lossy(&out).contains("INJECT-ALPHA") {
128	            break true;
129	        }
130	        match read_frame(&mut conn) {
131	            Ok(f) if f.kind == KIND_OUTPUT => accumulate_output(&f, &mut out),
132	            Ok(_) => {}
133	            Err(_) => break false, // recv timeout → echo never arrived
134	        }
135	    };
136
137	    // Kill the child and read until the `exit` event.
138	    send(
139	        &mut conn,
140	        KIND_KILL,
141	        serde_json::to_value(KillReq { session_id: sid }).unwrap(),
142	    );
143	    let mut exit: Option<ExitEvent> = None;
144	    loop {
145	        match read_frame(&mut conn) {
146	            Ok(f) if f.kind == KIND_EXIT => {
147	                exit = Some(serde_json::from_value(f.payload).unwrap());
148	                break;
149	            }
150	            Ok(f) if f.kind == KIND_OUTPUT => accumulate_output(&f, &mut out),
151	            Ok(_) => {}
152	            Err(_) => break, // timeout
153	        }
154	    }
155
156	    // Disconnect: dropping the single Stream closes the fd, so the broker's
157	    // command loop hits EOF and tears the session down. Joining the handler
158	    // (via the server thread) lets us assert the teardown happened.
159	    drop(conn);
160	    server.join().expect("server thread");
161
162	    assert!(
163	        echoed,
164	        "injected input must round-trip through the PTY: {:?}",
165	        String::from_utf8_lossy(&out)
166	    );
167	    let exit = exit.expect("exit event after kill");
168	    assert_eq!(exit.session_id, sid);
169	    assert_eq!(broker.session_count(), 0, "session torn down on disconnect");
170	}
171
172	fn accumulate_output(frame: &Envelope, out: &mut Vec<u8>) {
173	    let ev: OutputEvent = serde_json::from_value(frame.payload.clone()).unwrap();
174	    out.extend_from_slice(&decode_bytes(&ev.data_b64).unwrap());
175	}
176
177	#[test]
178	fn broker_hosts_pty_child_streams_output_and_accepts_input() {
179	    main_test();
180	}
181
182	/// REQ-SEND-SPT-HOSTED: an inbound message addressed by ENDPOINT ID
183	/// (`KIND_ENDPOINT_INPUT`) is injected into the PTY of the session hosting that
184	/// endpoint — the spt-hosted delivery path a `spt send` rides when there is no
185	/// `api listen` relay. Spawn an echo child LABELLED with an endpoint id; an
186	/// endpoint-keyed inject round-trips through the real PTY and the broker replies
187	/// `delivered=true`. An inject for an UNKNOWN endpoint replies `delivered=false`
188	/// and writes nothing (the caller then falls back to the spool).
189	// [int->REQ-SEND-SPT-HOSTED]
190	fn endpoint_input_test() {
191	    let name = unique_name();
192	    let broker = Broker::bind(&name).expect("bind broker");
193	    let server_broker = Arc::clone(&broker);
194	    let server = thread::spawn(move || {
195	        let handle = server_broker.serve_once().expect("accept brain");
196	        handle.join().expect("handler thread");
197	    });
198
199	    let mut conn = None;
200	    for _ in 0..200 {

... [720 lines truncated] ...


CAVEMAN MODE ACTIVE (full). Drop articles/filler/pleasantries/hedging. Fragments OK. Code/commits/security: write normal.


<owl_messages>
<owl_message from="todlando-w183" priority="info">[WORKING_PERCH_NOTICE] Subagent working perch created.
Perch: todlando-w183
Agent type: spt-test-engineer
To message it: $OWL send todlando-w183 todlando</owl_message>
</owl_messages>

[OWL SYSTEM] Informational owl messages above — no acknowledgment needed. Continue your current task.

<owl_reachability_notice>
You have an active owl perch. When spawning subagents or doing work, use run_in_background: true so you stay reachable for incoming messages.
</owl_reachability_notice>


Tests green (2 serde + 2 broker + keystone int; non-vacuousness empirically verified). Running my independent gate battery.


Bash

cargo clippy --workspace --all-targets -- -D warnings 2>&1 | tail -5

Workspace clippy


    Blocking waiting for file lock on package cache
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.98s


traceable-reqs check 2>&1 | grep -iE "INPUT-ACK-BACKPRESSURE|RC-MOUSE-FORWARD|RC-WIN-PASTE"; echo "=== exit ==="; traceable-reqs check >/dev/null 2>&1; echo "exit:$?"; echo "=== drift ==="; cargo run -p xtask -- check 2>&1 | tail -2

Traceable + docs-drift


  [OK] REQ-HAZARD-INPUT-ACK-BACKPRESSURE  required: [doc, impl, unit, int]  stages: +doc +impl +unit +int
  [OK] REQ-RC-MOUSE-FORWARD  required: [doc, impl, unit]  stages: +doc +impl +unit -int
       On Windows, `spt rc` must FORWARD scroll-wheel events to the harness when the harness has mouse reporting on. ROOT (operator HITL): P1's RawGuard EnableMouseCapture (added for right-click paste, REQ-RC-WIN-PASTE) makes Windows Terminal forward ALL mouse — including the scroll wheel — to rc instead of scrolling its own buffer, but the rc mouse handler dropped everything except right-button-down → scroll DIED (and WT's native scrollback is stolen by the capture). Operator ruling: keep mouse capture + right-click bracketed paste AND forward scroll to the harness. FIX (doyle design, cfg(windows), folds into the rc mouse handler): TRACK the harness's mouse-reporting mode by scanning its OUTPUT stream for the DECSET set/reset — ESC[?1000h/1002h/1003h (mouse on) + ESC[?1006h (SGR ext) and their ...l (off) — into a shared MouseMode{enabled,sgr} (pump writes from output, stdin reader reads); the scan survives a sequence SPLIT across output chunks (a bounded carry buffer). The mouse handler: right-button-DOWN -> bracketed clipboard paste (unchanged, REQ-RC-WIN-PASTE); ScrollUp/Down -> translate to an xterm SGR mouse report (ESC[<64;col+1;row+1M up / ESC[<65;..M down, 0-based crossterm -> 1-based xterm) and forward ONLY when enabled && sgr (else DROP — a legacy X10 report the harness may not parse is garbage); Moved/drag/left/middle -> DROP (scroll is the operator's need; click-forward risks garbage, no click-to-position). Unix UNCHANGED (no capture; the terminal scrolls natively). (v0.13.0)
  [OK] REQ-RC-WIN-PASTE  required: [doc, impl, unit]  stages: +doc +impl +unit -int
=== exit ===
exit:0
=== drift ===
     Running `target\debug\xtask.exe check`
xtask check: OK


All reqs OK, traceable EXIT=0, docs-drift OK. Rebuild real bin + run the new daemon tests single-threaded.


Rebuild real spt bin


   Compiling spt-daemon v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon)
   Compiling spt v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 31.13s


cargo test -p spt-daemon --test broker --test input_ack_deadlock --lib input_req -- --test-threads=1 2>&1 | tail -25

Run new daemon tests single-threaded


    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.50s
     Running unittests src\lib.rs (target\debug\deps\spt_daemon-6c41d016386d7ce3.exe)

running 2 tests
test msg::tests::input_req_absent_ack_defaults_true_n_minus_1_safe ... ok
test msg::tests::input_req_ack_false_round_trips_faithfully ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 346 filtered out; finished in 0.00s

     Running tests\broker.rs (target\debug\deps\broker-b5bda5d0294bc2ba.exe)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 7 filtered out; finished in 0.00s

     Running tests\input_ack_deadlock.rs (target\debug\deps\input_ack_deadlock-4107311aa8225a57.exe)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in 0.00s


Run broker + keystone int (no name filter).


cargo test -p spt-daemon --test broker --test input_ack_deadlock -- --test-threads=1 2>&1 | tail -22

Run broker + input_ack_deadlock int


    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.43s
     Running tests\broker.rs (target\debug\deps\broker-b5bda5d0294bc2ba.exe)

running 7 tests
test ack_false_input_suppresses_applied_frame ... ok
test broker_hosts_pty_child_streams_output_and_accepts_input ... ok
test dead_session_subscribe_does_not_hang ... ok
test endpoint_keyed_inject_reaches_hosted_pty ... ok
test exactly_once_preserved_on_no_ack_path ... ok
test spawn_env_reaches_child ... ok
test wall_b_endpoint_run_env_then_send_reaches_pty ... ok

test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.40s

     Running tests\input_ack_deadlock.rs (target\debug\deps\input_ack_deadlock-4107311aa8225a57.exe)

running 1 test
test input_flood_through_serve_attach_does_not_deadlock_broker ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 32.19s


P1 ctrl+V wedge — diagnose then fix

HITL re-open: ctrl+V (~10+ chars) wedges broker; right-click fine. Diagnostic build @b39e8a0 (branch v0.13.0-p1-ctrlv-diag) surfaced to doyle with env matrix (SPT_RC_CTRLV isolation + SPT_RC_DEBUG_PASTE + SPT_BROKER_DEBUG_INPUT/OUTPUT). Awaiting operator stderr capture (via doyle) to pin input-dispatch vs controller-output-write block. Then design+build the real fix (doyle suspects symmetric-P0-for-output async controller writer) on a gated branch off f7cf380.

Mint REQ-HAZARD-INPUT-ACK-BACKPRESSURE + amend REQ-RC-WIN-PASTE

Registry-first: new hazard REQ [impl,unit,int] for the input-ack IPC deadlock. Amend REQ-RC-WIN-PASTE to right-click-only scope (ctrl+V = WT-native).

Primary fix: conditional ack

InputReq.ack serde-default=true; brain send_effect_no_ack for operator path (serve_attach); dispatch_input only send_frame(applied) when req.ack; shellchan keeps ack=true.

Secondary: remove dead ctrl+V interception

Remove is_ctrl_v arm + fn + unit; keep right-click bracketed paste + EnableMouseCapture. ctrl+V rides WT-native paste.

Docs: KNOWN-HAZARDS deadlock + amend 7.18

New KNOWN-HAZARDS entry for input-ack backpressure deadlock (+N-1 broker-resident caveat). Amend 7.18: ctrl+V WT-native, right-click bracketed only.

Tests (spt-test-engineer) + gates + commit + doyle

Delegate repro-first int (N=64 flood deadlock) + units (ack=true emits/false doesn't, exactly-once no-ack dedup). Gates clippy/traceable/docs-drift/real-bin; commit; surface green to doyle.

Scroll-forward: mouse_mode scan + scroll→SGR

REQ-RC-MOUSE-FORWARD: track CC mouse-mode via DECSET scan on output stream (Arc<AtomicBool> enabled+sgr); forward ScrollUp/Down as SGR reports when on; keep right-click paste; drop moved/clicks. Units: exact-byte SGR + DECSET scan.


All green: broker 7/7 · keystone int 1/1 · serde 2/2 · rc units 15/15 · clippy 0 · traceable EXIT=0 · docs-drift OK · bin built. Commit the bundle.


Git status before bundle commit


 M CONTEXT.md
 M crates/spt-daemon/src/attach.rs
 M crates/spt-daemon/src/brain.rs
 M crates/spt-daemon/src/broker.rs
 M crates/spt-daemon/src/msg.rs
 M crates/spt-daemon/tests/broker.rs
 M crates/spt/src/rc.rs
 M crates/spt/tests/brain_survive.rs
 M docs/KNOWN-HAZARDS.md
 M traceable-reqs.toml
?? crates/spt-daemon/tests/input_ack_deadlock.rs


git add -A && git commit -F - <<'EOF'
fix(rc,broker): kill the input-flood ack deadlock; ctrl+V→WT-native; forward scroll (REQ-HAZARD-INPUT-ACK-BACKPRESSURE, REQ-RC-MOUSE-FORWARD)

The P1 ctrl+V wedge (HITL re-open) root-caused to a deeper, generic bug: an
operator INPUT FLOOD on one brain↔broker conn deadlocks the whole broker.
serve_attach sends N KIND_INPUT frames in its inner batch loop without reading;
the broker acks each (applied_envelope) on the SAME conn; the brain not reading
fills the return direction (~10 frames = IPC pipe buffer) -> send_frame blocks
the per-conn handler -> mutual full-duplex deadlock (permanent broker wedge,
controller latched). WT's ctrl+V paste-accelerator key-flood was the trigger;
the deadlock is generic to any input flood. (Capture: write_input 11/11 — P0
holds; ack send START=11/END=10 — frame #11's ack never returns.)

PRIMARY — conditional ack (cures any input flood):
- InputReq gains `ack: bool` (serde default true, N-1-safe: an old brain's input
  still acks). The fire-and-forward operator/rc path calls Brain::send_effect_no_ack
  (ack=false); dispatch_input writes NO applied frame when !req.ack, so the per-conn
  handler never writes back while draining the flood. shellchan keeps send_effect
  (ack=true) + its Applied-wait. Exactly-once preserved (dedup at the journal
  applied-set regardless of the ack). N-1 caveat (KH 7.9 class): an old resident
  broker still acks until restart.

SECONDARY — remove the dead ctrl+V interception:
- WT consumes ctrl+V (delivers only a Key RELEASE, never a Press, so the
  Press-guarded is_ctrl_v arm never fired) AND injects the clipboard as a
  char-by-char key flood we cannot intercept. ctrl+V now rides WT's native paste
  as keystrokes (the flood no longer wedges). Right-click bracketed paste kept.

SCROLL-FORWARD (REQ-RC-MOUSE-FORWARD) — our mouse capture (for right-click paste)
steals WT's native scroll, so forward it:
- Track the harness mouse-reporting mode from its OUTPUT (DECSET 1000/1002/1003 +
  1006 SGR, set/reset; scan survives a split across output chunks) into a shared
  MouseMode{enabled,sgr}. ScrollUp/Down -> xterm SGR report
  (ESC[<64/65;col+1;row+1M) forwarded only when enabled && sgr; moved/drag/clicks
  dropped. Unix unchanged.

Tests:
- unit: InputReq.ack serde default-true (N-1) + ack=false round-trip; ack-gate
  (real broker: ack=true emits KIND_APPLIED, ack=false suppresses it) +
  exactly-once on the no-ack path (replays apply once); rc scroll_dir/scroll_sgr
  exact xterm bytes + DECSET scan incl split-across-chunks.
- int (keystone, repro-first): input_flood_through_serve_attach_does_not_deadlock_broker
  — flood N=64 controller Input through real serve_attach -> broker stays
  serviceable (KIND_SESSIONS/NET_STATUS on a fresh conn); revert-to-ack RED
  (forkpty-deterministic at 64; cross-platform at N~2000).

Docs: KNOWN-HAZARDS 7.19 (deadlock + N-1 caveat) + 7.20 (scroll) + 7.18 amended
(right-click only); CONTEXT input-injection note. REQ-RC-WIN-PASTE amended to
right-click-only scope.

Gates: clippy --workspace -D warnings = 0; traceable EXIT=0 (+doc+impl+unit+int
INPUT-ACK, +doc+impl+unit RC-MOUSE-FORWARD/RC-WIN-PASTE); xtask docs-drift OK;
real bin rebuilt; spt-daemon broker 7/7 + keystone int 1/1 + serde 2/2; rc 15/15.

Co-authored by: todlando
EOF

Commit the P1b bundle


warning: in the working copy of 'crates/spt-daemon/tests/input_ack_deadlock.rs', LF will be replaced by CRLF the next time Git touches it
[v0.13.0-p1b-ack-deadlock 394fc5e] fix(rc,broker): kill the input-flood ack deadlock; ctrl+V→WT-native; forward scroll (REQ-HAZARD-INPUT-ACK-BACKPRESSURE, REQ-RC-MOUSE-FORWARD)
 11 files changed, 1348 insertions(+), 62 deletions(-)
 create mode 100644 crates/spt-daemon/tests/input_ack_deadlock.rs


Committed @394fc5e. Push branch + surface bundle green to doyle.


git push -u origin v0.13.0-p1b-ack-deadlock 2>&1 | tail -5

Push P1b bundle branch


remote:      https://github.com/SaberMage/spt-core/pull/new/v0.13.0-p1b-ack-deadlock
remote:
branch 'v0.13.0-p1b-ack-deadlock' set up to track 'origin/v0.13.0-p1b-ack-deadlock'.
To https://github.com/SaberMage/spt-core.git
 * [new branch]      v0.13.0-p1b-ack-deadlock -> v0.13.0-p1b-ack-deadlock


$OWL send doyle <<'EOF'
P1b-REWORK BUNDLE GREEN @394fc5e — origin/v0.13.0-p1b-ack-deadlock (off f7cf380). Ready for your gate. Three fixes, one branch:

1) PRIMARY (the real ship-blocker) REQ-HAZARD-INPUT-ACK-BACKPRESSURE — conditional ack. InputReq.ack (serde default_true, N-1-safe); serve_attach operator path -> Brain::send_effect_no_ack; dispatch_input gates send_frame(applied) on req.ack; shellchan keeps ack=true + its Applied-wait. Cures ANY input flood; exactly-once preserved (dedup at the journal applied-set). N-1 caveat documented (old resident broker acks until restart, KH 7.9 class).

2) SECONDARY REQ-RC-WIN-PASTE amended -> right-click-only. Removed the dead is_ctrl_v arm (WT delivers ctrl+V as Key RELEASE-only, never Press; + injects the clipboard as a key flood we can't intercept). ctrl+V rides WT-native paste as keystrokes (no wedge now). Right-click bracketed paste + EnableMouseCapture unchanged.

3) SCROLL-FORWARD REQ-RC-MOUSE-FORWARD (new sibling) — our capture stole WT scroll, so forward it. Track CC mouse-mode via DECSET scan on the OUTPUT stream (1000/1002/1003 + 1006 SGR, set/reset, SPLIT-across-chunks via a carry buffer) -> MouseMode{enabled,sgr}; ScrollUp/Down -> SGR report ESC[<64/65;col+1;row+1M, forwarded only when enabled && sgr; moved/drag/clicks dropped. Unix untouched.

GATES (independently re-run):
- clippy --workspace --all-targets -D warnings = 0
- traceable EXIT=0: INPUT-ACK +doc+impl+unit+int, RC-MOUSE-FORWARD +doc+impl+unit, RC-WIN-PASTE +doc+impl+unit (ctrl+V removal didn't drop coverage)
- xtask docs-drift OK; real spt bin rebuilt
- spt-daemon: broker 7/7 (incl ack_false_input_suppresses_applied_frame + exactly_once_preserved_on_no_ack_path), keystone int 1/1 (input_flood_through_serve_attach_does_not_deadlock_broker, N=64), serde 2/2; rc units 15/15 (scroll_dir/scroll_sgr exact bytes + DECSET scan incl split-chunk)

NON-VACUOUS (test-engineer empirically verified the revert RED): ack-gate flips RED if the req.ack gate is removed (no-ack op's applied arrives); keystone is forkpty-deterministic RED-on-revert at N=64 (Windows named pipes buffer past 64 — same banked platform-divergence stance as P0/W1b; cross-platform RED confirmed at N~2000). Documented in the test module.

DOCS: KNOWN-HAZARDS 7.19 (deadlock + N-1) + 7.20 (scroll) + 7.18 amended (right-click only); CONTEXT note.

Then re-deploy + operator HITL: large ctrl+V = no wedge (lands as keystrokes), right-click = bracketed paste, scroll works, no broker wedge. Diag branch v0.13.0-p1-ctrlv-diag can be deleted post-gate. Standing by.
EOF

Surface P1b bundle to doyle


➜ SENT:doyle


Updated task #18 status


P1b-rework bundle shipped + surfaced. **`@394fc5e`, origin/v0.13.0-p1b-ack-deadlock**, awaiting doyle's gate.

What the ctrl+V wedge actually was — and the full fix:
1. **Root (deeper than P1):** an operator input *flood* deadlocks the broker — `serve_attach` floods N KIND_INPUT on one conn without reading; the broker acks each on the same conn; the return direction fills (~pipe buffer) → `send_frame` blocks the per-conn handler → mutual deadlock. **Fix:** conditional ack (`InputReq.ack`, operator path no-ack, shellchan keeps it). Cures any input flood; exactly-once preserved.
2. **ctrl+V:** removed the dead interception (WT delivers RELEASE-only + injects a key flood) — now rides WT-native paste, no wedge. Right-click bracketed paste kept.
3. **Scroll-forward (new REQ):** track CC mouse-mode via DECSET scan (split-chunk safe), forward wheel as SGR reports.

**Gates (independently re-run):** clippy 0 · traceable EXIT=0 (all three reqs covered) · docs-drift OK · real bin built · broker 7/7 · keystone int 1/1 (N=64 flood, revert-RED verified) · serde 2/2 · rc 15/15. Docs: KNOWN-HAZARDS 7.18 amended + 7.19 + 7.20; CONTEXT note.

After doyle gates → re-deploy → operator HITL (big ctrl+V no wedge, right-click paste, scroll works). The throwaway diag branch can be deleted post-gate. Standing by, reachable.


<task-notification>
<task-id>b4cwwiglf</task-id>
Monitor event: "« spt event »"
<event><EVENT-PART seq="1/6" id="38d2c860" type="msg" from="doyle">GATE-PASS — P1b-rework bundle @394fc5e.

Read all 4 seams + re-ran every gate myself:
- clippy --workspace --all-targets -D warnings = 0
- traceable EXIT=0 (INPUT-ACK doc+impl+unit+int; RC-MOUSE-FORWARD doc+impl+unit; RC-WIN-PASTE doc+impl+unit)
- spt-daemon broker 7/7 (incl ack_false_input_suppresses_a</EVENT-PART>
<EVENT-PART seq="2/6" id="38d2c860">pplied_frame + exactly_once_preserved_on_no_ack_path)
- keystone input_flood_through_serve_attach_does_not_deadlock_broker 1/1 (31.96s)
- spt rc bins 208/208 (scroll_dir/scroll_sgr exact bytes + DECSET scan incl split-chunk)

Invariants verified at code:
1) exactly-once intact — the `if req.ack` gate touches ONLY the applied-f</EVENT-PART>
<EVENT-PART seq="3/6" id="38d2c860">rame WRITE; enqueue/dedup already ran above it (confirmed in broker.rs + the journal-anchor exactly_once test)
2) N-1 safe — InputReq.ack serde default_true
3) shellchan untouched — still send_effect (ack=true) + its Applied-wait
4) non-vacuousness real — FIFO-order ack-gate, journal-anchor exactly-once, keystone flood_sent (RED</EVENT-PART>
<EVENT-PART seq="4/6" id="38d2c860">-carrier=forkpty/linux; Windows forced RED only at N~2000)

Docs consistent: KH 7.18 amended / 7.19 / 7.20 + CONTEXT + traceable-reqs.toml.

ONE gate-finding (doc-only) — I fixed inline + committed @21bfd9a (LOCAL, NOT pushed): keystone assert-(1) comment claimed the revert is &quot;Empirically RED on Windows 11&quot; UNQUALIFIED</EVENT-PART>
<EVENT-PART seq="5/6" id="38d2c860">, which contradicts your own module header + FLOOD_N note (N=64 benign pre-fix on Windows; deterministic RED-carrier at 64 = forkpty/linux; Windows RED only at N~2000). Corrected the comment to match. Comment-only, ZERO binary impact — operator HITL re-deploy can proceed off 394fc5e unchanged.

Push 21bfd9a onto origin/v0.13.0-p1b-ack-</EVENT-PART>
<EVENT-PART seq="6/6" id="38d2c860">deadlock myself, or you fold it into your next push? Your call.

Cleared for operator HITL: large ctrl+V = no wedge (rides WT-native paste as keystrokes), right-click = bracketed paste, scroll works, broker stays serviceable under flood.

Co-authored by: doyle</EVENT-PART></event>
</task-notification>


<owl_reachability_notice>
You have an active owl perch. When spawning subagents or doing work, use run_in_background: true so you stay reachable for incoming messages.
</owl_reachability_notice>


GATE-PASS @394fc5e — all four seams verified, every gate re-run. doyle's doc-only fix @21bfd9a (keystone comment: "RED on Windows 11" was unqualified, now matches the module header's N=64-benign / forkpty-RED-carrier / Windows-RED-at-N~2000 stance). Replying.


$OWL send doyle <<'EOF'
Thanks for the independent gate + the doc fix. PUSH 21bfd9a YOURSELF — it's your commit in the opbuild worktree, parented on 394fc5e; my branch HEAD is 394fc5e with nothing pending to fold, so a clean fast-forward from your side is simplest (no merge, no race). I'll treat origin's tip as authoritative after you push.

Doc-fix agreed: the keystone is forkpty/linux RED-carrier at N=64 (Windows named pipes buffer past 64, benign pre-fix — the same banked platform-divergence stance as P0/W1b); Windows forced-RED only at N~2000. Your corrected comment matches the module header — good catch, zero binary impact, HITL proceeds off 394fc5e unchanged.

That closes ALL v0.13.0 delivery-control build work: P0 (paste-wedge) + P1/P1b (rc paste/ctrl+V/scroll + the input-ack deadlock) + P2 (resume+picker) + docs (both adapter seams). Over to you for the batched re-deploy + operator HITL (large ctrl+V no wedge, right-click bracketed paste, scroll works, broker serviceable under flood). Throwaway diag branch v0.13.0-p1-ctrlv-diag is safe to delete post-HITL. I'll stay reachable for any HITL follow-up; otherwise I'm clear on build.
EOF

Reply to doyle on gate-pass + push


All v0.13.0 build work done + gated. doyle drives re-deploy + HITL. Given the long, multi-re-open session, communing to checkpoint the full state durably before any HITL follow-up.


Skill

v0.13.0 delivery-control round-2: ALL BUILD WORK DONE + doyle-gated. Branch tips: P0 (paste-wedge, REQ-HAZARD-PTY-INPUT-WRITER-WEDGE) @0675b37 + P2 (resume+picker, REQ-SESSION-RESUME-TEMPLATE) @153c9c9 on origin/v0.13.0-delivery-control via doyle's docs push @8b36b03. P1 (rc paste, REQ-RC-WIN-PASTE) @f7cf380. P1b-REWORK bundle @394fc5e on origin/v0.13.0-p1b-ack-deadlock (off f7cf380), GATE-PASSED by doyle (he added a doc-only fix @21bfd9a he's pushing himself).

The P1 ctrl+V saga (root-caused deep): P1 shipped clipboard bracketed paste (ctrl+V + right-click). HITL re-open: large ctrl+V WEDGED the broker. Confirm-first diagnostic (throwaway branch v0.13.0-p1-ctrlv-diag, deletable post-HITL) PINNED the root = INPUT-ACK IPC BACKPRESSURE DEADLOCK (not P0/output): serve_attach floods N KIND_INPUT on one brain<->broker conn without reading; broker acks each (applied_envelope) on the SAME conn; brain not reading fills the return dir (~10 frames = IPC pipe buffer) -> send_frame blocks the per-conn handler -> mutual full-duplex deadlock (permanent broker wedge, controller latched). WT ctrl+V paste-accelerator key-flood was the trigger; deadlock generic to ANY input flood.

P1b bundle = 3 fixes: (1) PRIMARY REQ-HAZARD-INPUT-ACK-BACKPRESSURE conditional ack — InputReq.ack (serde default_true, N-1-safe); operator path -> Brain::send_effect_no_ack; dispatch_input gates send_frame(applied) on req.ack; shellchan keeps ack=true + Applied-wait; exactly-once preserved (dedup at journal applied-set). (2) SECONDARY REQ-RC-WIN-PASTE -> right-click-only; removed dead is_ctrl_v arm (WT delivers ctrl+V as RELEASE-only, never Press; injects clipboard as key flood we can't intercept); ctrl+V rides WT-native paste. (3) REQ-RC-MOUSE-FORWARD (new) — our EnableMouseCapture stole WT scroll, so forward it: track CC mouse-mode via DECSET scan on OUTPUT (1000/1002/1003 + 1006 SGR, split-across-chunks via carry buffer) -> MouseMode{enabled,sgr}; ScrollUp/Down -> SGR report ESC[<64/65;col+1;row+1M when enabled&&sgr; drop moved/drag/clicks. Gates all green (clippy0, traceable EXIT0, docs-drift, real bin; broker 7/7, keystone int 1/1 N=64 flood, serde 2/2, rc 15/15). KNOWN-HAZARDS 7.18 amended + 7.19 + 7.20.

NEXT: doyle drives batched PATH re-deploy + operator HITL all four (large ctrl+V no wedge, right-click bracketed paste, scroll works, broker serviceable under flood). I'm clear on build, reachable for HITL follow-up. Pre-existing flaky neighbor w5_a2_abandoned = doyle's P3/CI triage (not mine). End goal: ship v0.13.0 delivery-control after HITL passes. Pattern that worked: confirm-first diagnostics (env-gated instrumentation on throwaway branch) before any fix; delegate tests to spt-test-engineer (background, stay reachable); doyle independently re-verifies every gate.


<owl_messages>
<owl_message from="doyle" priority="high">Pushed — origin/v0.13.0-p1b-ack-deadlock 394fc5e..21bfd9a (clean fast-forward). Origin tip 21bfd9a is authoritative. All v0.13.0 delivery-control build work closed on your side; I've got the batched re-deploy + operator HITL. Will delete the v0.13.0-p1-ctrlv-diag throwaway post-HITL. Clear on build — thanks.

Co-authored by: doyle</owl_message>
</owl_messages>

[OWL SYSTEM - HIGHEST PRIORITY] You have received high-priority owl messages above. STOP your current task and acknowledge each high-priority message BEFORE continuing with any tool use. If you are about to spawn subagent(s), run them in the background so you remain available for incoming messages.


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: v0.13.0 delivery-control round-2: ALL BUILD WORK DONE + doyle-gated. Branch tips: P0 (paste-wedge, REQ-HAZARD-PTY-INPUT-WRITER-WEDGE) @0675b37 + P2 (resume+picker, REQ-SESSION-RESUME-TEMPLATE) @153c9c9 on origin/v0.13.0-delivery-control via doyle's docs push @8b36b03. P1 (rc paste, REQ-RC-WIN-PASTE) @f7cf380. P1b-REWORK bundle @394fc5e on origin/v0.13.0-p1b-ack-deadlock (off f7cf380), GATE-PASSED by doyle (he added a doc-only fix @21bfd9a he's pushing himself).

The P1 ctrl+V saga (root-caused deep): P1 shipped clipboard bracketed paste (ctrl+V + right-click). HITL re-open: large ctrl+V WEDGED the broker. Confirm-first diagnostic (throwaway branch v0.13.0-p1-ctrlv-diag, deletable post-HITL) PINNED the root = INPUT-ACK IPC BACKPRESSURE DEADLOCK (not P0/output): serve_attach floods N KIND_INPUT on one brain<->broker conn without reading; broker acks each (applied_envelope) on the SAME conn; brain not reading fills the return dir (~10 frames = IPC pipe buffer) -> send_frame blocks the per-conn handler -> mutual full-duplex deadlock (permanent broker wedge, controller latched). WT ctrl+V paste-accelerator key-flood was the trigger; deadlock generic to ANY input flood.

P1b bundle = 3 fixes: (1) PRIMARY REQ-HAZARD-INPUT-ACK-BACKPRESSURE conditional ack — InputReq.ack (serde default_true, N-1-safe); operator path -> Brain::send_effect_no_ack; dispatch_input gates send_frame(applied) on req.ack; shellchan keeps ack=true + Applied-wait; exactly-once preserved (dedup at journal applied-set). (2) SECONDARY REQ-RC-WIN-PASTE -> right-click-only; removed dead is_ctrl_v arm (WT delivers ctrl+V as RELEASE-only, never Press; injects clipboard as key flood we can't intercept); ctrl+V rides WT-native paste. (3) REQ-RC-MOUSE-FORWARD (new) — our EnableMouseCapture stole WT scroll, so forward it: track CC mouse-mode via DECSET scan on OUTPUT (1000/1002/1003 + 1006 SGR, split-across-chunks via carry buffer) -> MouseMode{enabled,sgr}; ScrollUp/Down -> SGR report ESC[<64/65;col+1;row+1M when enabled&&sgr; drop moved/drag/clicks. Gates all green (clippy0, traceable EXIT0, docs-drift, real bin; broker 7/7, keystone int 1/1 N=64 flood, serde 2/2, rc 15/15). KNOWN-HAZARDS 7.18 amended + 7.19 + 7.20.

NEXT: doyle drives batched PATH re-deploy + operator HITL all four (large ctrl+V no wedge, right-click bracketed paste, scroll works, broker serviceable under flood). I'm clear on build, reachable for HITL follow-up. Pre-existing flaky neighbor w5_a2_abandoned = doyle's P3/CI triage (not mine). End goal: ship v0.13.0 delivery-control after HITL passes. Pattern that worked: confirm-first diagnostics (env-gated instrumentation on throwaway branch) before any fix; delegate tests to spt-test-engineer (background, stay reachable); doyle independently re-verifies every gate.


Ack, doyle — origin/v0.13.0-p1b-ack-deadlock @21bfd9a is authoritative (clean ff). You've got the re-deploy + HITL + the diag-branch cleanup. All v0.13.0 build closed on my side. Thanks for the independent gate + the doc fix.

Completing the commune checkpoint. Downloading fresh Psyche context first.


Bash

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

Download fresh Psyche context


<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-p1b-ack-deadlock" head_sha="21bfd9a21d76c8ac3d5102d2a50082c6c34cd941" head_subject="docs(test): correct keystone assert-1 non-vacuousness note to match the …" commits_since="0" commits_unpulled="0"/>
v0.13.0 P3 INPUT-ACK BACKPRESSURE DEADLOCK — IMPL STAGE COMPLETE.

Implementation finalized (design approved doyle 2026-06-20T04:23:44Z):
- PRIMARY: InputReq.ack (serde default true, N-1-safe); operator sends via send_effect_no_ack; dispatch_input gates ack write on req.ack; shellchan=true unchanged. Kills input-flood deadlock; exactly-once preserved.
- SECONDARY: dead ctrl+V press-guard removed (WT RELEASE-only, clipboard as key flood — uninterceptable). Right-click retained.
- BONUS: REQ-RC-MOUSE-FORWARD (scroll-forward): DECSET scan on output (split-chunk safe), ScrollUp/Down → SGR when enabled+sgr.

clippy -D warnings=0. rc units 15/15. Traceability: REQ-RC-MOUSE-FORWARD doc+impl+unit ✅, REQ-RC-WIN-PASTE doc+impl+unit ✅, INPUT-ACK doc+impl (unit/int pending). KNOWN-HAZARDS 7.18 amended + 7.19 (deadlock) + 7.20 (scroll).

Gates pending: (1) REPRO-FIRST int N=64 flood pre-fix wedge / post-fix drain+live+concurrent-attach, (2) shellchan ack=true unit, (3) exactly-once dedup unit — all delegated spt-test-engineer in-flight. On report → commit bundle → surface green to doyle for HITL.

Branch v0.13.0-p1b-ack-deadlock off f7cf380.<project-context-resolved name="spt-core"/>
P3 ACTIVE — REQ-HAZARD-INPUT-ACK-BACKPRESSURE + REQ-RC-MOUSE-FORWARD (branch v0.13.0-p1b-ack-deadlock, base @f7cf380):

Impl done: deadlock fix (InputReq.ack conditional), ctrl+V dead code removed, scroll-forward bonus. clippy clean, rc units 15/15, docs-drift OK, traceable (INPUT-ACK unit/int pending). Gates pending: REPRO-FIRST int (N=64 flood), shellchan-ack unit, exactly-once dedup unit — delegated to spt-test-engineer, background. On test report → full gate run → commit → doyle HITL re-deploy + operator test.


Fresh Psyche context is pre-tests ("unit/int pending"). Writing the delta: tests green → committed @394fc5e → doyle GATE-PASS → @21bfd9a authoritative → all build closed.


Write

<live-context>
DELTA since last commune (which ended at "P1b tests delegated, gates pending, on report → commit → doyle HITL"):

P1b-REWORK BUNDLE IS DONE + doyle GATE-PASSED. The delegated tests came back GREEN from spt-test-engineer (background): broker 7/7 incl ack_false_input_suppresses_applied_frame (FIFO-order proof the ack-gate suppresses the applied frame) + exactly_once_preserved_on_no_ack_path (journal applied-count==1 anchor); keystone int input_flood_through_serve_attach_does_not_deadlock_broker 1/1 (N=64 flood through real serve_attach, broker stays serviceable via KIND_SESSIONS/NET_STATUS probes on a fresh conn); serde 2/2 (InputReq.ack default-true N-1 + ack=false round-trip). I independently re-ran every gate: clippy --workspace -D warnings=0, traceable EXIT=0 (INPUT-ACK doc+impl+unit+int, RC-MOUSE-FORWARD + RC-WIN-PASTE doc+impl+unit), docs-drift OK, real bin rebuilt, rc 15/15. Committed the bundle @394fc5e, pushed origin/v0.13.0-p1b-ack-deadlock, surfaced green to doyle.

doyle GATE-PASSED @394fc5e (read all 4 seams + re-ran every gate himself; verified exactly-once intact = the `if req.ack` gate touches only the applied-frame WRITE, enqueue/dedup ran above it; N-1 serde default_true; shellchan untouched). One doc-only gate-finding: a keystone comment claimed "RED on Windows 11" unqualified — contradicts the module header (N=64 benign pre-fix on Windows because named pipes buffer past 64; deterministic RED-carrier at 64 = forkpty/linux; Windows forced-RED only at N~2000). doyle corrected the comment inline @21bfd9a and pushed it himself (clean fast-forward 394fc5e..21bfd9a). ORIGIN TIP 21bfd9a IS AUTHORITATIVE. Zero binary impact — HITL proceeds off 394fc5e unchanged.

ALL v0.13.0 delivery-control BUILD WORK IS CLOSED on my side. doyle has the batched PATH re-deploy + operator HITL. I'm clear on build, reachable for any HITL follow-up.

Reflection: the ctrl+V "bug" was a 4-layer onion — clipboard-paste design (P1) → ctrl+V wedge HITL re-open → confirm-first instrumentation pinned a DEEPER generic input-flood ack-deadlock (NOT the surface ctrl+V handler, which was innocent + even dead due to WT's RELEASE-only delivery) → a SECOND HITL regression (scroll, from the mouse-capture P1 added). The win was discipline: env-gated diagnostic on a throwaway branch (v0.13.0-p1-ctrlv-diag) BEFORE any fix, two capture rounds (the first probe was insufficient — only logged on is_ctrl_v match, so I added log-EVERY-event + broker frame-burst count/clock), which let doyle pin the exact failing call (ack-send-START#11-without-END). Never guess-fixed. Conditional-ack is minimal + cures ANY input flood, not just ctrl+V. The diag branch is deletable post-HITL (doyle owns that).
</live-context>
<project-context>
CURRENT_PROJECT_CONTEXT (name=spt-core):
v0.13.0 delivery-control round-2 — ALL BUILD WORK DONE + doyle-gated. Awaiting operator HITL (doyle-driven) before ship.

SHIPPED + GATED (origin):
- P0 REQ-HAZARD-PTY-INPUT-WRITER-WEDGE @0675b37 (paste-wedge: per-session single input-writer thread, bounded FIFO, drop+INPUT_BACKPRESSURE on saturation; completes W1b-deferred fix 2).
- P2 REQ-SESSION-RESUME-TEMPLATE @153c9c9 (resume-blank: [session.resume] native-resume role + per-row ledger cwd) + picker tz-drop @96d0780.
- docs both adapter seams @8b36b03 ([message-idle-translation-binary] + [session.resume] integration-checklist, perri-buildable). All on origin/v0.13.0-delivery-control.
- P1 REQ-RC-WIN-PASTE @f7cf380 (rc clipboard bracketed paste) — SUPERSEDED/amended by P1b to right-click-only.

P1b-REWORK bundle: origin/v0.13.0-p1b-ack-deadlock @21bfd9a (authoritative; off f7cf380), doyle GATE-PASSED. Three fixes:
1. REQ-HAZARD-INPUT-ACK-BACKPRESSURE (the REAL ship-blocker behind the ctrl+V wedge): an operator input FLOOD on one brain<->broker conn deadlocks the whole broker — serve_attach floods N KIND_INPUT without reading, broker acks each (applied_envelope) on the SAME conn, return dir fills (~10 = IPC pipe buffer) -> send_frame blocks the per-conn handler -> mutual deadlock (permanent wedge, controller latched). FIX = conditional ack: InputReq.ack (serde default_true, N-1-safe); operator path -> Brain::send_effect_no_ack; dispatch_input gates send_frame(applied) on req.ack; shellchan keeps send_effect(ack=true) + its Applied-wait (shellchan.rs:210); exactly-once preserved (dedup at journal applied-set). N-1 caveat: old resident broker acks until restart (KH 7.9 class).
2. REQ-RC-WIN-PASTE amended -> right-click-only: removed dead is_ctrl_v arm (WT consumes ctrl+V, delivers Key RELEASE-only never Press, injects clipboard as char-by-char key flood = uninterceptable). ctrl+V rides WT-native paste as keystrokes (no longer wedges).
3. REQ-RC-MOUSE-FORWARD (new): EnableMouseCapture (for right-click paste) steals WT scroll, so forward it — track harness mouse-mode via DECSET scan on OUTPUT (1000/1002/1003 + 1006 SGR, set/reset, SPLIT-across-chunks via carry buffer) -> MouseMode{enabled,sgr}; ScrollUp/Down -> SGR report ESC[<64/65;col+1;row+1M forwarded when enabled&&sgr; moved/drag/clicks dropped. cfg(windows); Unix untouched.
Docs: KNOWN-HAZARDS 7.18 amended + 7.19 (deadlock+N-1) + 7.20 (scroll); CONTEXT input-injection note; traceable-reqs.toml.

NEXT: doyle's batched re-deploy + operator HITL (large ctrl+V = no wedge / rides WT-native keystrokes; right-click = bracketed paste; scroll works; broker serviceable under flood). Then ship v0.13.0. Throwaway diag branch v0.13.0-p1-ctrlv-diag deletable post-HITL (doyle owns). Flaky neighbor w5_a2_abandoned = doyle's P3/CI triage, NOT mine. Gate = doyle (independently re-verifies). Adapter follow-on = perri.

Working pattern (proven this round): confirm-first env-gated diagnostics on a throwaway branch before ANY fix (no guess-fix); iterate the probe when insufficient (log-every-event + frame-burst count); delegate tests to spt-test-engineer (background, stay reachable); per-item registry-first REQ activation; independent gate re-run before surfacing; doyle independently re-verifies every gate.
</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 "P1b tests delegated, gates pending, on report → commit → doyle HITL"):

P1b-REWORK BUNDLE IS DONE + doyle GATE-PASSED. The delegated tests came back GREEN from spt-test-engineer (background): broker 7/7 incl ack_false_input_suppresses_applied_frame (FIFO-order proof the ack-gate suppresses the applied frame) + exactly_once_preserved_on_no_ack_path (journal applied-count==1 anchor); keystone int input_flood_through_serve_attach_does_not_deadlock_broker 1/1 (N=64 flood through real serve_attach, broker stays serviceable via KIND_SESSIONS/NET_STATUS probes on a fresh conn); serde 2/2 (InputReq.ack default-true N-1 + ack=false round-trip). I independently re-ran every gate: clippy --workspace -D warnings=0, traceable EXIT=0 (INPUT-ACK doc+impl+unit+int, RC-MOUSE-FORWARD + RC-WIN-PASTE doc+impl+unit), docs-drift OK, real bin rebuilt, rc 15/15. Committed the bundle @394fc5e, pushed origin/v0.13.0-p1b-ack-deadlock, surfaced green to doyle.

doyle GATE-PASSED @394fc5e (read all 4 seams + re-ran every gate himself; verified exactly-once intact = the `if req.ack` gate touches only the applied-frame WRITE, enqueue/dedup ran above it; N-1 serde default_true; shellchan untouched). One doc-only gate-finding: a keystone comment claimed "RED on Windows 11" unqualified — contradicts the module header (N=64 benign pre-fix on Windows because named pipes buffer past 64; deterministic RED-carrier at 64 = forkpty/linux; Windows forced-RED only at N~2000). doyle corrected the comment inline @21bfd9a and pushed it himself (clean fast-forward 394fc5e..21bfd9a). ORIGIN TIP 21bfd9a IS AUTHORITATIVE. Zero binary impact — HITL proceeds off 394fc5e unchanged.

ALL v0.13.0 delivery-control BUILD WORK IS CLOSED on my side. doyle has the batched PATH re-deploy + operator HITL. I'm clear on build, reachable for any HITL follow-up.

Reflection: the ctrl+V "bug" was a 4-layer onion — clipboard-paste design (P1) → ctrl+V wedge HITL re-open → confirm-first instrumentation pinned a DEEPER generic input-flood ack-deadlock (NOT the surface ctrl+V handler, which was innocent + even dead due to WT's RELEASE-only delivery) → a SECOND HITL regression (scroll, from the mouse-capture P1 added). The win was discipline: env-gated diagnostic on a throwaway branch (v0.13.0-p1-ctrlv-diag) BEFORE any fix, two capture rounds (the first probe was insufficient — only logged on is_ctrl_v match, so I added log-EVERY-event + broker frame-burst count/clock), which let doyle pin the exact failing call (ack-send-START#11-without-END). Never guess-fixed. Conditional-ack is minimal + cures ANY input flood, not just ctrl+V. The diag branch is deletable post-HITL (doyle owns that).
</live-context>
<project-context>
CURRENT_PROJECT_CONTEXT (name=spt-core):
v0.13.0 delivery-control round-2 — ALL BUILD WORK DONE + doyle-gated. Awaiting operator HITL (doyle-driven) before ship.

SHIPPED + GATED (origin):
- P0 REQ-HAZARD-PTY-INPUT-WRITER-WEDGE @0675b37 (paste-wedge: per-session single input-writer thread, bounded FIFO, drop+INPUT_BACKPRESSURE on saturation; completes W1b-deferred fix 2).
- P2 REQ-SESSION-RESUME-TEMPLATE @153c9c9 (resume-blank: [session.resume] native-resume role + per-row ledger cwd) + picker tz-drop @96d0780.
- docs both adapter seams @8b36b03 ([message-idle-translation-binary] + [session.resume] integration-checklist, perri-buildable). All on origin/v0.13.0-delivery-control.
- P1 REQ-RC-WIN-PASTE @f7cf380 (rc clipboard bracketed paste) — SUPERSEDED/amended by P1b to right-click-only.

P1b-REWORK bundle: origin/v0.13.0-p1b-ack-deadlock @21bfd9a (authoritative; off f7cf380), doyle GATE-PASSED. Three fixes:
1. REQ-HAZARD-INPUT-ACK-BACKPRESSURE (the REAL ship-blocker behind the ctrl+V wedge): an operator input FLOOD on one brain<->broker conn deadlocks the whole broker — serve_attach floods N KIND_INPUT without reading, broker acks each (applied_envelope) on the SAME conn, return dir fills (~10 = IPC pipe buffer) -> send_frame blocks the per-conn handler -> mutual deadlock (permanent wedge, controller latched). FIX = conditional ack: InputReq.ack (serde default_true, N-1-safe); operator path -> Brain::send_effect_no_ack; dispatch_input gates send_frame(applied) on req.ack; shellchan keeps send_effect(ack=true) + its Applied-wait (shellchan.rs:210); exactly-once preserved (dedup at journal applied-set). N-1 caveat: old resident broker acks until restart (KH 7.9 class).
2. REQ-RC-WIN-PASTE amended -> right-click-only: removed dead is_ctrl_v arm (WT consumes ctrl+V, delivers Key RELEASE-only never Press, injects clipboard as char-by-char key flood = uninterceptable). ctrl+V rides WT-native paste as keystrokes (no longer wedges).
3. REQ-RC-MOUSE-FORWARD (new): EnableMouseCapture (for right-click paste) steals WT scroll, so forward it — track harness mouse-mode via DECSET scan on OUTPUT (1000/1002/1003 + 1006 SGR, set/reset, SPLIT-across-chunks via carry buffer) -> MouseMode{enabled,sgr}; ScrollUp/Down -> SGR report ESC[<64/65;col+1;row+1M forwarded when enabled&&sgr; moved/drag/clicks dropped. cfg(windows); Unix untouched.
Docs: KNOWN-HAZARDS 7.18 amended + 7.19 (deadlock+N-1) + 7.20 (scroll); CONTEXT input-injection note; traceable-reqs.toml.

NEXT: doyle's batched re-deploy + operator HITL (large ctrl+V = no wedge / rides WT-native keystrokes; right-click = bracketed paste; scroll works; broker serviceable under flood). Then ship v0.13.0. Throwaway diag branch v0.13.0-p1-ctrlv-diag deletable post-HITL (doyle owns). Flaky neighbor w5_a2_abandoned = doyle's P3/CI triage, NOT mine. Gate = doyle (independently re-verifies). Adapter follow-on = perri.

Working pattern (proven this round): confirm-first env-gated diagnostics on a throwaway branch before ANY fix (no guess-fix); iterate the probe when insufficient (log-every-event + frame-burst count); delegate tests to spt-test-engineer (background, stay reachable); per-item registry-first REQ activation; independent gate re-run before surfacing; doyle independently re-verifies every gate.
</project-context>


Commune checkpointed (Self listener will ingest it).

**v0.13.0 delivery-control — all build work closed.**

Final state:
- **P0** paste-wedge `@0675b37` · **P2** resume+picker `@153c9c9`/`@96d0780` · **docs** both adapter seams `@8b36b03` — all on origin/v0.13.0-delivery-control, gated.
- **P1b-rework** `@21bfd9a` (authoritative origin tip, off f7cf380) — doyle GATE-PASSED. Three fixes in one bundle: the input-ack IPC deadlock (conditional ack), ctrl+V→WT-native, scroll-forward.

The ctrl+V "bug" unravelled into a deeper generic flaw — an **operator input flood deadlocks the broker** via the applied-ack on the flooded conn — caught only by confirm-first instrumentation (two capture rounds), never guess-fixed. Conditional ack cures any input flood; exactly-once preserved; shellchan untouched.

All gates green and independently re-verified by doyle: clippy 0 · traceable EXIT=0 · docs-drift OK · broker 7/7 · keystone flood int 1/1 · serde 2/2 · rc 15/15. KNOWN-HAZARDS 7.18 amended + 7.19 + 7.20.

doyle owns the rest: batched PATH re-deploy → operator HITL (big ctrl+V no wedge, right-click bracketed paste, scroll works, broker serviceable under flood) → ship v0.13.0. The throwaway diag branch gets cleaned up post-HITL.

I'm clear on build, reachable for any HITL follow-up.


<task-notification>
<task-id>bqmyubcfg</task-id>
<tool-use-id>toolu_01QS6gMbzK2cKSzhJLifS4pD</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\97787e5e-64b2-4afd-b3fe-becaa2ea8030\tasks\bqmyubcfg.output</output-file>
<status>killed</status>
<summary>Background command "Wait for keystone test completion" was stopped</summary>
</task-notification>


<task-notification>
<task-id>abf0df6a952b1b22c</task-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\97787e5e-64b2-4afd-b3fe-becaa2ea8030\tasks\abf0df6a952b1b22c.output</output-file>
<status>completed</status>
<summary>Agent "Write input-ack deadlock tests" came to rest</summary>
<note>A task-notification fires each time this agent comes to rest with no live background children of its own. The user can send it another message and resume it, so the same task-id may notify more than once.</note>
<result>That notification is just an old background polling command being cleaned up — it's the leftover waiter from earlier in the session, not relevant to the completed work. No action needed.

All work is finished and verified. The test suite for REQ-HAZARD-INPUT-ACK-BACKPRESSURE is complete and green:

- 2 serde unit tests + 2 broker tests + 1 keystone int test, all passing
- `traceable-reqs check` shows `+doc +impl +unit +int` (EXIT 0)
- `cargo clippy --workspace --all-targets -- -D warnings` = 0
- Non-vacuousness empirically demonstrated for every test
- No impl changes (only the two temporary reverts I made to prove RED, both restored)

Nothing further to do.</result>
<usage><subagent_tokens>233289</subagent_tokens><tool_uses>153</tool_uses><duration_ms>6347757</duration_ms></usage>
</task-notification>


CAVEMAN MODE ACTIVE (full). Drop articles/filler/pleasantries/hedging. Fragments OK. Code/commits/security: write normal.


<owl_messages>
<owl_message from="todlando-w184" priority="info">[WORKING_PERCH_NOTICE] Subagent working perch created.
Perch: todlando-w184
Agent type: spt-test-engineer
To message it: $OWL send todlando-w184 todlando</owl_message>
</owl_messages>

[OWL SYSTEM] Informational owl messages above — no acknowledgment needed. Continue your current task.

<owl_reachability_notice>
You have an active owl perch. When spawning subagents or doing work, use run_in_background: true so you stay reachable for incoming messages.
</owl_reachability_notice>


Stale notification — the test agent's leftover waiter coming to rest. Work already integrated, committed @394fc5e, gated by doyle @21bfd9a. No action.

All v0.13.0 build work closed + checkpointed. Holding for doyle's HITL. Nothing pending on my side.


<command-message>spt:commune</command-message>
<command-name>/spt:commune</command-name>
<command-args>with immediate next steps and broad summary of the project's status + end goal. we'll resume in the next session</command-args>


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: with immediate next steps and broad summary of the project's status + end goal. we'll resume in the next session


End-of-session commune. Downloading fresh Psyche context to diff.


Bash

$LIVE psyche-download todlando 2>&1 | tail -45

Download fresh Psyche context


tracked: removed stale index.lock at C:/Users/decid/AppData/Local/spt/psyches/tracked/seed/worktrees/todlando/index.lock (>60s old, 0 bytes)
<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-p1b-ack-deadlock" head_sha="21bfd9a21d76c8ac3d5102d2a50082c6c34cd941" head_subject="docs(test): correct keystone assert-1 non-vacuousness note to match the …" commits_since="0" commits_unpulled="0"/>
v0.13.0 P1b-REWORK bundle GATE-PASSED by doyle @394fc5e. Doc-only fix @21bfd9a (doyle, clean fast-forward). ORIGIN TIP @21bfd9a authoritative. Zero binary impact.

All seams verified: clippy 0, traceable EXIT=0 (INPUT-ACK doc+impl+unit+int; RC-MOUSE-FORWARD doc+impl+unit; RC-WIN-PASTE doc+impl+unit), broker 7/7, keystone input_flood_deadlock 1/1 (31.96s), rc bins 208/208.

Invariants confirmed: (1) exactly-once intact — req.ack gate touches only applied-frame WRITE, enqueue/dedup above. (2) N-1 safe — InputReq.ack default_true. (3) shellchan untouched. (4) non-vacuous — RED-carrier forkpty/linux; Windows RED at N~2000.

ALL BUILD WORK CLOSED. doyle owns: re-deploy + operator HITL (ctrl+V non-wedging, right-click bracketed, scroll works, broker flood-safe). Diag branch v0.13.0-p1-ctrlv-diag deletable post-HITL. Standing by.<project-context-resolved name="spt-core"/>
v0.13.0 delivery-control round-2 — build CLOSED + doyle gate-passed @394fc5e (doc-fix @21bfd9a, zero binary impact). Awaiting doyle operator HITL re-deploy. Flaky neighbor w5_a2_abandoned = doyle P3/CI triage (not mine).

All gated:
- P0 @0675b37, P2 @153c9c9, Docs @8b36b03 (origin/v0.13.0-delivery-control)
- P1b @21bfd9a (origin/v0.13.0-p1b-ack-deadlock): INPUT-ACK deadlock fix + RC-WIN-PASTE right-click-only + RC-MOUSE-FORWARD scroll


/spt:commune with immediate next steps and broad summary of the project's status + end goal. we'll resume in the next session


Psyche is current — it absorbed last turn's commune (P1b gate-passed, @21bfd9a authoritative, all build closed). Only net-new delta is the session-end handoff: the resume entry point. Writing a tight focus-only delta.


Write

<live-context>
SESSION ENDING — resume handoff (operator: "we'll resume next session").

Psyche state is CURRENT (last commune absorbed: P1b-rework GATE-PASSED, origin tip @21bfd9a authoritative, ALL v0.13.0 build work CLOSED). This is purely a resume-entry note.

ON RESUME, DO FIRST: check the operator HITL outcome (doyle drove the batched PATH re-deploy + HITL). Three branches:
1. HITL PASSED clean (big ctrl+V no wedge / lands as WT-native keystrokes, right-click bracketed paste, scroll works, broker serviceable under flood) → v0.13.0 delivery-control is SHIP-READY. Likely next: merge/land the gated branches (origin/v0.13.0-delivery-control has P0+P2+docs; origin/v0.13.0-p1b-ack-deadlock @21bfd9a has P1b — confirm with doyle whether P1b lands onto the delivery-control branch or main), then version/changelog + the perri adapter follow-on ([session.resume] declares `claude -r {session_id} ...`; idle-translation-binary). Delete throwaway v0.13.0-p1-ctrlv-diag (doyle owns, may already be gone).
2. A HITL re-open (a 4th-layer surprise, as ctrl+V kept doing) → confirm-first env-gated diagnostic on a throwaway branch BEFORE any fix (the pattern that worked all round); two capture rounds if the first probe is insufficient; never guess-fix; loop with doyle to pin the exact failing call.
3. doyle hasn't run HITL yet → stay reachable, no action.

NO open build tasks on my side. Gate = doyle (independently re-verifies every gate). Flaky neighbor w5_a2_abandoned is doyle's P3/CI triage, NOT mine — don't pick it up unless doyle relays.

END GOAL: ship v0.13.0 delivery-control (the operator-dogfooding round that fixed: P0 paste-wedge, P1/P1b rc paste + ctrl+V + scroll + the input-ack broker deadlock, P2 resume-blank, + both public adapter seams for perri). The broader arc: spt-core is the harness-independent agent ecosystem core; v0.13.0 hardens the live `spt rc` remote-control + spt-hosted delivery surface that the claude-spt adapter (perri) builds on.
</live-context>
<project-context></project-context>


<live-context>
SESSION ENDING — resume handoff (operator: "we'll resume next session").

Psyche state is CURRENT (last commune absorbed: P1b-rework GATE-PASSED, origin tip @21bfd9a authoritative, ALL v0.13.0 build work CLOSED). This is purely a resume-entry note.

ON RESUME, DO FIRST: check the operator HITL outcome (doyle drove the batched PATH re-deploy + HITL). Three branches:
1. HITL PASSED clean (big ctrl+V no wedge / lands as WT-native keystrokes, right-click bracketed paste, scroll works, broker serviceable under flood) → v0.13.0 delivery-control is SHIP-READY. Likely next: merge/land the gated branches (origin/v0.13.0-delivery-control has P0+P2+docs; origin/v0.13.0-p1b-ack-deadlock @21bfd9a has P1b — confirm with doyle whether P1b lands onto the delivery-control branch or main), then version/changelog + the perri adapter follow-on ([session.resume] declares `claude -r {session_id} ...`; idle-translation-binary). Delete throwaway v0.13.0-p1-ctrlv-diag (doyle owns, may already be gone).
2. A HITL re-open (a 4th-layer surprise, as ctrl+V kept doing) → confirm-first env-gated diagnostic on a throwaway branch BEFORE any fix (the pattern that worked all round); two capture rounds if the first probe is insufficient; never guess-fix; loop with doyle to pin the exact failing call.
3. doyle hasn't run HITL yet → stay reachable, no action.

NO open build tasks on my side. Gate = doyle (independently re-verifies every gate). Flaky neighbor w5_a2_abandoned is doyle's P3/CI triage, NOT mine — don't pick it up unless doyle relays.

END GOAL: ship v0.13.0 delivery-control (the operator-dogfooding round that fixed: P0 paste-wedge, P1/P1b rc paste + ctrl+V + scroll + the input-ack broker deadlock, P2 resume-blank, + both public adapter seams for perri). The broader arc: spt-core is the harness-independent agent ecosystem core; v0.13.0 hardens the live `spt rc` remote-control + spt-hosted delivery surface that the claude-spt adapter (perri) builds on.
</live-context>
<project-context></project-context>


Commune queued (Self listener ingests next poll). Session wrap:

**Status — v0.13.0 delivery-control: all build work CLOSED + doyle-gated.**
- P0 paste-wedge `@0675b37` · P2 resume+picker `@153c9c9`/`@96d0780` · docs both adapter seams `@8b36b03` — origin/v0.13.0-delivery-control.
- P1b-rework `@21bfd9a` (authoritative) — input-ack deadlock fix + ctrl+V→WT-native + scroll-forward. doyle GATE-PASSED.

**Resume entry point (next session):** check the operator HITL outcome doyle is running —
1. **Passed** → v0.13.0 ship-ready: land the gated branches (confirm P1b landing target with doyle), version/changelog, hand off the perri adapter follow-on. Delete the throwaway diag branch.
2. **Re-opened** → confirm-first diagnostic on a throwaway branch before any fix (the pattern that worked).
3. **Not yet run** → stay reachable.

**End goal:** ship v0.13.0 — the operator-dogfooding hardening round for live `spt rc` remote-control + spt-hosted delivery, the surface perri's claude-spt adapter builds on.

No open build tasks mine. Gate = doyle. w5_a2_abandoned flaky = doyle's CI triage, not mine. Resuming next session.