{
  "summary": "End-to-end finding: on a fresh daemon restart, the long-lived broker process starts a brain child; the brain child starts one `livehost` sweep thread. `resume_restart_orphaned_endpoints` runs on that sweep thread, and each orphan is resumed serially through `launch_ledger_resume` -> `harnesshost::launch_harness_brokered_in`. Each resumed endpoint opens its own temporary `Brain::cold_start` local IPC connection, so capture conn_ids 3–6 are consistent with four separate restart-resume spawn connections. Broker `dispatch_spawn` immediately auto-installs that spawning connection as the session controller with `by=None`, before starting the PTY drain and before replying `Spawned`. The only output consumer on that connection is the same livehost sweep thread inside `Brain::spawn_session_pid`: it blocks in `read_event` and discards interleaved `BrokerEvent::Output` until it receives `Spawned`. It then returns, the stack-local `Brain` drops, and the broker should observe EOF and remove that connection's `ControllerSink`. There is no intended persistent controller reader for a headless resumed endpoint after this point.\n\nThe exact normal detach path is strong: `Broker::handle_conn` breaks on any `read_frame` error/EOF, then visits every session accumulated in `my_subs` and calls `OutputLog::detach_if(&send)`; `detach_if` uses `Arc::ptr_eq` against `ControllerSink.send`, calls `clear_controller`, removes the sink immediately, and clears `controlled`/`driven_by`. It does not take the `SharedSend` mutex and does not join the writer. A writer thread can remain blocked or drain already-queued frames through its own `SharedSend` clone, but once the sink is removed it is no longer visible to `controller_write_stalled` and cannot itself produce a later `BRAIN_SUBSCRIBER_STALL_EVICT`. Existing real-broker evidence is `crates/spt-daemon/tests/driven_by_selfheal.rs:281-303`, which drops the spawner and observes the broker's `detach_if -> clear_controller` disk clear after 300 ms; `tests/attach.rs:470-530` explicitly identifies this dropped spawner as the transient `endpoint run` shape. Therefore current source does not intentionally leave restart-spawn connections installed. If the exact conn_ids 3–6 later stall-evict, that is discriminating evidence of an orphaned/open client connection: the broker did not observe EOF, despite the logical launcher having completed. It is not explained by server-side `SharedSend` clones alone.\n\nImportant instrumentation correction: `controller_writer` sets `write_blocked_since=Some(now)` before it calls `send.lock()`, not after acquiring the mutex. Thus the 15-second stall metric conflates two states. `CTRL_WRITE_WAIT` with no matching `CTRL_WRITE_LOCKED` means a same-connection `SharedSend` mutex convoy. `CTRL_WRITE_LOCKED` with no matching `CTRL_WRITE_DONE` means the writer owns the mutex and is blocked in `write_frame`, directly indicating the client process/connection is not draining fast enough. A large `wait_us` followed by `DONE` is a resolved mutex convoy. Because conn_ids 3–6 are separate connections, their `SharedSend` mutexes are separate: one session cannot convoy another across those IDs. Also, after a restart spawn has received `Spawned`, its connection has one session controller and no normal recurring ack writer, so a steady-state `LOCKED`-without-`DONE` on that conn is much more consistent with an open-but-nonreading consumer than with an internal same-connection convoy.\n\nThe v56 capture excludes the daemon heartbeat connection as the new subscriber: deployed v0.30.5 uses `Brain::resume_session_cursors`, which queries/seeds cursors but sends no `subscribe`. Exact source is preserved in `.worktrees/uwedge2/crates/spt-daemon/src/brain.rs:1015-1093` and `brainproc.rs:174-258`; the field log says `re-established ... cursor(s)`, not `re-attached`. The root checkout is v0.30.0 and still calls subscribing `resume_sessions`, so it is not the deployed-v56 truth for this one point. Since v56 was cursor-only and steady-state evictions existed with no update, an update-only brain-resubscribe mutex convoy is falsified.\n\nTestable root-cause hypothesis, without proposing a fix: a controller client connection remains open after its intended consumer stops calling `Brain::read_event`. For the cold-restart conn_ids 3–6, that means a temporary restart-spawn `Brain` connection failed to reach observable EOF/detach even though `DAEMON_RESTART_RESUME` proves `spawn_session_pid` received `Spawned` and returned. Under output, its broker `controller_writer` initially succeeds while the launch loop is draining, then blocks once that consumer stops; the next `KIND_SESSIONS` reconcile observes `write_blocked_since > 15s` and stall-evicts it. Discriminate this from a mutex convoy by correlating client connect/drop, broker EOF/detach, controller install/drop, and the existing WAIT/LOCKED/DONE markers. A faithful regression scenario is four noisy restart-resumed endpoints, not the existing quiet `endpoint_survival` harness: after each `DAEMON_RESTART_RESUME`, assert broker EOF/detach and `has_controller=false` for that spawn conn before 15 seconds. If EOF/detach occurs, the orphan hypothesis is false and the stalled conn must be a later operator/serve connection; if client logical drop occurs but broker EOF does not, the runtime/handle lifetime is the gap. A process-suspension control should produce `LOCKED` without `DONE`; a deliberately held same-connection send mutex with an actively draining client should produce WAIT without LOCKED.\n\n`by=None` is not a PID/process-type identity. It means the subscribe/spawn supplied no transport-proven node origin. Broker `dispatch_spawn` hardcodes `None`; private `Brain::subscribe` also hardcodes `None` for direct resume/handoff-style local IPC clients. The broker handshake labels every such IPC peer only as Role::Brain, so `None` cannot distinguish daemon brain, a short-lived CLI `Brain`, or another direct local client. The harness/`claude-spt launch` child is not the controller connection at all; it only owns PTY stdio. Normal `spt rc`, including same-machine rc, uses a broker loopback network connection; `serve_attach` receives the loopback origin node and calls `attach_as(..., Some(origin_node))`, so an ordinary local rc controller is usually `by=Some(this-node)`, not `None`.",
  "files": [
    {
      "path": "spt-core/crates/spt-daemon/src/brainproc.rs",
      "description": "Broker-supervised brain child entry. `run_brain` owns the main heartbeat connection and starts `spawn_live_host`; root checkout lines 192-236 still show the pre-v56 subscribing call, while the v56 source is in `.worktrees/uwedge2`."
    },
    {
      "path": "spt-core/.worktrees/uwedge2/crates/spt-daemon/src/brainproc.rs",
      "description": "Deployed v56 production truth: lines 174-258 call cursor-only `resume_session_cursors`, write `BRAIN_RESUMED: re-established`, then start livehost and enter the 500 ms heartbeat."
    },
    {
      "path": "spt-core/.worktrees/uwedge2/crates/spt-daemon/src/brain.rs",
      "description": "V56 split: lines 1015-1093 retain subscribing `resume_sessions` only for draining callers and add cursor-only `resume_session_cursors` for the non-draining heartbeat connection. Its doc names the SharedSend self-deadlock."
    },
    {
      "path": "spt-core/crates/spt-daemon/src/livehost.rs",
      "description": "Restart-resume owner and caller chain. `launch_ledger_resume` lines 415-489 calls harnesshost synchronously; `resume_restart_orphaned_endpoints` lines 537-610 walks orphaned perches serially; `spawn_live_host` lines 978-1040 runs the one-shot in a dedicated brain-process thread before normal reconciliation."
    },
    {
      "path": "spt-core/crates/spt-daemon/src/harnesshost.rs",
      "description": "`launch_harness_brokered_in` lines 233-304 creates a stack-local `Brain::cold_start`, calls `spawn_session_pid`, and returns only `HarnessSpawned`; it retains no Brain/connection. It fills `[session.resume]`, env, translation argv, and project cwd into `SpawnReq`."
    },
    {
      "path": "spt-core/crates/spt-daemon/src/brain.rs",
      "description": "`spawn_session_pid` lines 445-475 sends spawn and is the temporary connection's only reader, looping through `read_event` until `Spawned` and discarding other events. `read_event` lines 600-710 decodes Output. `read_frame_until` lines 1521-1555 shows ordinary `BrainConn::Whole` is an unbounded blocking `read_frame`. `subscribe` lines 1452-1455 hardcodes `by=None`."
    },
    {
      "path": "spt-core/crates/spt-daemon/src/broker.rs",
      "description": "Core ownership. `SharedSend=Arc<Mutex<SendHalf>>` lines 73-77. `become_controller` lines 1019-1080 installs the controller and writer. `controller_writer` lines 1569-1700 marks blocked before mutex acquisition, then locks and performs blocking `write_frame`. `handle_conn` lines 3033-3240 records spawn/subscribe session IDs and detaches them after EOF. `dispatch_spawn` lines 3254-3472 spawns PTY/input/translation, auto-subscribes `by=None`, starts drain/waiter, inserts HostedSession, then sends `Spawned`. `detach_if` lines 1371-1385 removes the matching sink. `send_frame` lines 4479-4483 is the competing same-connection mutex path."
    },
    {
      "path": "spt-core/crates/spt-daemon/tests/driven_by_selfheal.rs",
      "description": "Real-broker detach proof at lines 257-345: drop(spawner), wait 300 ms, observe broker `detach_if -> clear_controller -> stamp_driven_by` and a live session with no controller."
    },
    {
      "path": "spt-core/crates/spt-daemon/tests/attach.rs",
      "description": "Lines 470-530 explicitly model the transient endpoint-run spawn connection: drain startup output, drop spawner after Spawned, then attach a fresh operator. Also proves the separate forwarding connection needed by `serve_attach`."
    },
    {
      "path": "spt-core/crates/spt-daemon/tests/endpoint_survival.rs",
      "description": "Current DAEMON_RESTART_RESUME E2E. It proves a fresh broker re-spawns one previously-online orphan, but the harness is intentionally quiet and the test does not assert spawn-connection EOF/controller detachment, so it cannot catch the reported noisy orphan-controller class."
    },
    {
      "path": "spt-core/crates/spt-daemon/src/dispatch.rs",
      "description": "Steady-state operator controller process/thread owner. `run_dispatch_loop` claims inbound streams and spawns one worker thread per stream; Attach worker calls `serve_attach` in the brain process."
    },
    {
      "path": "spt-core/crates/spt-daemon/src/attach.rs",
      "description": "The persistent operator controller reader. `serve_attach` lines 219-555 owns a dedicated broker Brain connection, calls `attach_as(..., Some(origin_node))`, blocks in `read_controller_event_resuming`/`brain.read_event`, handles `BrokerEvent::Output`, and synchronously forwards each frame through a second Brain connection. This is a brain-process dispatch worker, not the CLI or harness process."
    },
    {
      "path": "spt-core/crates/spt/src/cli.rs",
      "description": "CLI bringup path `cmd_endpoint_run` lines 1632-2050: ensure daemon, prevent duplicate live sessions, call the same harnesshost launcher, then either return headless or enter the rc attach path. The CLI's spawn Brain is likewise temporary."
    },
    {
      "path": "spt-core/crates/spt/src/rc.rs",
      "description": "Operator side. `establish_attach` lines 1368-1497 opens a pump-mode Brain connection, uses broker loopback for same-node rc, sends Attach Request, and subscribes its network stream. It reads NetStreamData/AttachRecord output, not session `BrokerEvent::Output` directly."
    },
    {
      "path": "spt-claude-code/adapter/claude-spt.toml",
      "description": "Adapter delegates `[session.self]` to `{adapter_dir}/claude-spt launch --id {id} --node {node}` and `[session.resume]` to the same wrapper with `--resume {session_id}` (lines 733-760); translation is `{adapter_dir}/claude-spt translate` (lines 863-864); psyche/echo roles are separate bounded commands."
    },
    {
      "path": "spt-claude-code/tools/claude-spt/src/launch.rs",
      "description": "Wrapper process ownership. On Unix it `exec`s Claude/CCS; on Windows it spawns the harness with inherited PTY stdio and blocks in `cmd.status()` for the harness lifetime. It never connects/subscribes to the broker, so it cannot read `BrokerEvent::Output`."
    },
    {
      "path": "spt-core/crates/spt-daemon/src/translation.rs",
      "description": "`TranslationChild::spawn` lines 232-307 is broker-owned: a direct child with piped stdin/stdout and a broker-process reader thread. It is not the controller connection consumer."
    },
    {
      "path": "spt-core/crates/spt-daemon/src/lifecycle.rs",
      "description": "Psyche/echo ownership. `host_one` gives each endpoint a separate pulse thread; `run_psyche_event_turn` and `fire_echo` invoke bounded manifest commands. They do not own or lock the controller Brain connection."
    },
    {
      "path": "spt-core/crates/spt-runtime/src/runtime.rs",
      "description": "Bounded child wait implementation lines 793-907: stdout/stderr drain on separate threads, `try_wait` polls every 20 ms, timeout kills/reaps, then joins pipe readers. Psyche/echo can block their own endpoint pulse thread for the configured bound, not the controller reader thread."
    },
    {
      "path": "spt-core/docs/UPDATE-WEDGE-SYMPTOMS-FOR-RCA.md",
      "description": "Capture facts: v56 says cursor-only `re-established`, yet stall eviction still follows promotion; steady-state evictions also occurred with no update. This falsifies an update-only resume subscription explanation."
    },
    {
      "path": "spt-core/docs/UPDATE-WEDGE-2-ROUND3-RULING.md",
      "description": "Historical confirmed same-connection deadlock model and v56 cursor-only split. Useful as prior mechanism evidence, but the current field recurrence is explicitly beyond that removed production subscription."
    }
  ],
  "architecture": "Process and connection map for one cold-restarted endpoint:\n\n1. Broker process: `Daemon::run` owns `Broker`, PTY session table, PTY child handles, per-session output ring/drain, `ControllerSink` writer thread, and the resident translation child. It accepts every local IPC connection on a separate `handle_conn` thread. Every connection gets a distinct `SharedSend` mutex.\n2. Brain child process: `brainproc::run_brain` owns the non-subscribing v56 heartbeat Brain connection. It spawns the livehost sweep thread and dispatcher. The livehost thread performs DAEMON_RESTART_RESUME.\n3. Restart spawn connection: livehost thread -> `launch_ledger_resume` -> `launch_harness_brokered_in` -> temporary `Brain::cold_start`. Broker `dispatch_spawn` auto-installs this connection as controller `by=None`; the livehost thread drains Output until Spawned. On return, client Brain should drop; broker EOF cleanup should remove ControllerSink. Four endpoints naturally create conn_ids 3,4,5,6 rather than sharing one mutex.\n4. Harness process: broker PTY spawns `claude-spt launch`; on Windows wrapper waits for child Claude, on Unix it execs Claude. It communicates through PTY stdin/stdout only. It neither owns nor drains broker controller IPC.\n5. Translation process: broker directly spawns `claude-spt translate`, retains it in `HostedSession.translation`, reads its stdout on a broker thread, and applies commands through the PTY input writer. It is independent of controller IPC, though it can increase PTY traffic.\n6. Psyche/echo: after bind/reconcile, brain livehost hosts a separate pulse thread per endpoint. Psyche and echo are bounded child invocations from that pulse thread. There is no resident psyche controller reader and no shared Brain/SharedSend with the restart-spawn connection.\n7. Operator attach, if later present: spt CLI opens a loopback/QUIC Attach stream. A brain-process dispatcher worker opens a dedicated session Brain connection and `serve_attach` is the code that actually reads `BrokerEvent::Output`; a second Brain connection forwards AttachRecord bytes. The CLI reads NetStreamData and writes terminal stdout. Same-node rc has `by=Some(self-node)` because loopback supplies an origin.\n\nBlocking inventory:\n\nRestart-spawn reader thread: local socket connect; hello `write_frame`; spawn request `write_frame`; then `read_event -> read_frame -> read_exact(4-byte length) -> read_exact(body)` until Spawned. Broker-side work delaying its reply includes up to 2 seconds of wake-dedup polling, PTY process creation, translation process creation, one synchronous translation Init pipe write/flush, sessions-map insertion, waiting for the connection's `SharedSend` mutex, and the Spawned socket write. While waiting, the livehost thread is continuously available to read Output. After Spawned it does only resume pid/error disk writes and proceeds; it does not wait for the harness or psyche.\n\nPersistent `serve_attach` reader thread: initial net-stream subscribe write; separate forwarding-connection connect/hello; blocking `brain.read_event`; possible controller gap re-subscribe write; access/disk checks on Request; attach/subscribe write; PTY input/resize writes back to broker; synchronous resting-state filesystem updates and possible shell cascade at wake/detach edges; viewer eviction rate-limit sleep; and, for every Output, synchronous NDJSON/base64 construction plus fire-and-forget `wire.net_stream_send`, whose local IPC `write_frame` can itself block if that second broker handler is not draining. Slow operator CLI stdout can backpressure the network stream and eventually block this forwarding chain. The two-connection split prevents a direct same-SharedSend replay/forward deadlock, but it does not make the single serve thread asynchronous.\n\nPsyche/echo do not execute on either controller reader thread. Their child process creation and up-to-30-second bounded waits occur on per-endpoint pulse threads. They can only starve controller reading indirectly through whole-process suspension or extreme OS-level CPU/memory/thread/handle pressure. Translation likewise cannot call/suspend the reader; it runs in the broker and shares PTY input/output-log resources, not the controller socket consumer.\n\nConvoy versus nonreading consumer:\n\n`SharedSend` serializes output, replay, ack, exit/error, and network log frames for one connection. Same-connection convoy is possible because any writer may hold the mutex across blocking `write_frame`. The stall clock starts before mutex acquisition, so the eviction line alone cannot classify it. WAIT/no-LOCKED classifies mutex wait. LOCKED/no-DONE classifies blocked socket write and therefore insufficient client drain. Distinct conn_ids rule out cross-connection mutex convoy. For temporary restart-spawn connections, after Spawned there should be neither a retained controller nor recurring competing sender, so a later stall-evict on the same conn is an orphan/open-client condition unless the capture-to-conn mapping is wrong. Existing drop tests prove normal eventual detach, so the field hypothesis is precise and falsifiable rather than source-proven."
}