Terminal-Multiplexing Substrate Recommendation for radioactive-ralph
Native Windows override (2026-07-26): This historical evaluation's native ConPTY conclusion is superseded by the Windows SCM safety contract. In the implemented dependency line,
creack/ptyreturnsErrPTYUnsupportedon native Windows, so provider workers are unavailable there. Native Windows supports only the foreground supervisor/client control plane; use the Linux build inside WSL2 for provider-backed execution.
Decision: (D) Hybrid — Ralph owns ptys via creack/pty; Bubble Tea renders; tmux is not used at all (not even as an optional durability layer)
This is not "D-lite" — it's D with the tmux escape hatch declined. Ralph builds its own minimal durable-supervisor process, gets both control invariant and durability natively, and never negotiates pty ownership with an external daemon.
Why not the others
(A) gotmux+tmux wins durability for free but fails requirement 1 outright: the tmux server, not Ralph, forks the agent and holds the pty. Every read/write/kill is a CLI round-trip through os/exec to a process Ralph doesn't own. pipe-pane (the one thing that would satisfy req 4) isn't even wrapped by gotmux — you'd hand-roll it against raw tmux anyway, and capture-pane is polling-a-snapshot, not a stream. You'd be building the same tee/watchdog machinery as options C/D on top of an extra failure domain (external binary, socket reachability, native-Windows binary compatibility unverified against psmux). Paying the architectural cost of an indirection layer without shedding any of your own required engineering is a bad trade. Reject as primary.
(B) vendor/fork 3mux or sunder: sunder is dead and structurally incapable (hardcoded shell, no kill method, single-process — fails req 1 and 3 both). 3mux has genuine pty ownership + kill, but its detach/reattach is package main Unix-only fork+SCM_RIGHTS fd-passing — not an importable library, not Windows-portable, unowned by an active team, and it still gives zero output-tee (req 2/4 gap identical to building from scratch). You'd be vendoring a single-maintainer project's internals for a durability mechanism you can't even reuse without forking the binary layer, and getting nothing extra for the harder requirements. Reject.
(C) Bubble Tea + creack/pty, no durability layer: correctly identified as only solving the render half. Its own evaluation is honest that req 1's pty ownership, req 2's tee, and req 4's streaming all have to be hand-built regardless of which multiplexer you pick — Bubble Tea contributes nothing there either way. The only thing missing from C to make it D is: don't stop at "single foreground process," split the pty-owning supervisor into its own long-lived process that Bubble Tea attaches to as a thin client. That split is small, incremental, and is the entire reason D beats C.
Why D is correct
The control invariant is non-negotiable and is best satisfied by the option every evaluation independently converges on as "you'll have to build this yourselves anyway": a Go process that calls creack/pty.Start(cmd) directly and holds *os.File + cmd.Process per agent. That gives:
- Control (req 1):
pty.Start→ directWrite()for input, directProcess.Kill()for instant termination, non-interactive by construction (Ralph never blocks waiting on a pty it doesn't hold). - Hybrid I/O (req 2): tee the ptmx reader — one branch feeds a ring-buffer/vterm for the live Bubble Tea pane, the other branch is Ralph's own structured-result channel. Since Ralph already controls the agent CLI invocation, the cleanest structured-result path isn't scraping the tee at all — it's having the agent write JSON to a known fd/file path Ralph passes in (e.g.
--output-format json > $RESULT_FIFO) or, for agents lacking that, parsing a clearly-delimited marker out of the tee stream. Either way this is Ralph-owned code, not a multiplexer feature — no candidate offered this natively, so no candidate is being passed up. - Streaming (req 4): same tee — the watchdog goroutine reads its branch via
bufio.Scanner/timer-based stall detection. Native Go channels/goroutines, no fifo/pipe-pane indirection needed since Ralph owns the fd directly (this is actually strictly better than tmux'spipe-pane-to-FIFO model, which requires an extra OS-level fifo Ralph doesn't need when it already has the pty fd in-process). - Durability (req 3): since Go can't
fork(), split into two roles in the same binary: a supervisor subcommand (radioactive_ralph service start, which already exists per the repo's CLAUDE.md) that owns all agent ptys, and re-execs itself headless (os.StartProcess/exec.Command(os.Args[0], "service", "start")detached from the parent's controlling terminal, redirecting stdio away from the launching tty) so it survives the launching terminal or TUI dying. The TUI (radioactive_ralph tui) is a separate, restartable client that attaches over the existing IPC socket (internal/ipc/— already in the tree) purely to render; killing/detaching the TUI does nothing to the supervisor or its agent ptys. This is exactly the tmux client/server shape without adopting tmux's process, socket protocol, or Windows-portability gap — Ralph's owninternal/ipcsocket is already cross-platform-designable (named pipes on Windows, Unix sockets elsewhere) in a waySCM_RIGHTSfd-passing orsevlyar/go-daemonnever would be. - Windows (req 5):
creack/ptyhas real (if weaker) Windows ConPTY support, and this whole design has zero dependency on Unix-only fd-passing tricks,fork(), or an external tmux/psmux binary compatibility bet. WSL works trivially as Linux; native Windows is a tractable, isolated risk (ConPTY quirks) rather than an architectural dead-end. - Dependencies/license:
creack/pty(MIT, small, actively used industry-wide — this is the de facto standard Go pty library, and is literally what 3mux forked from and sunder vendors) +bubbletea/lipgloss(MIT, very healthy, already the stated TUI choice). No tmux binary, no vendored multiplexer, no daemon-fork library.
Strongest risk and mitigation
Risk: Ralph is now fully responsible for the supervisor/client split and its IPC protocol — durability quality depends entirely on code Ralph writes and owns, with no upstream project absorbing that maintenance burden. The specific failure mode to guard against is a supervisor crash (not just TUI detach) taking every agent pty down with it, since there's no external process (tmux) keeping them alive independently of Ralph's own code.
Mitigation:
- Keep the supervisor process itself minimal and boring (pty ownership + IPC socket + structured-result routing only — no rendering, no business logic) so its crash surface is small and it can be a service-managed unit (
radioactive_ralph service install, already planned per the repo's owninternal/service/package) with OS-level restart-on-crash (systemd/launchd), rather than something only a human-launched terminal keeps alive. - Treat the supervisor/IPC layer as the highest-priority test target: unit + integration tests specifically for "TUI client disconnects/crashes mid-session, agent ptys keep running, reattach recovers full state" and "supervisor itself restarts, in-flight agent processes are either cleanly reaped or reattached via recorded pid/pty state" — this is the one place where the hybrid design carries risk none of the vendored alternatives would (tmux's decades of hardening on exactly this scenario is real and is what you give up by not using it).
- If the from-scratch supervisor proves harder than expected specifically on the reattach-after-supervisor-crash case (not the common TUI-detach case, which is straightforward), revisit tmux as a last-resort durability backend for that one narrow scenario — but do not adopt it for req 1's control invariant, only ever as a process-liveness fallback, given how clearly every evaluation shows tmux mediation defeats direct pty ownership.
