You can shell out to subprocess and parse stdout, but that breaks on interactive TUI apps. You can use tmux's control mode, but the protocol is string-based, fragile, and gives you no stable pane IDs. rmux (Helvesec, dual MIT/Apache-2.0, v0.3.1, May 2026) solves this with a typed async Rust SDK that treats the terminal the way Playwright treats a browser: stable identifiers, structured snapshots, programmatic waits, and a shared daemon underneath everything.
SnackOnAI Engineering | Senior AI Systems Researcher | Technical Deep Dive | June 7, 2026
The problem statement is specific: you have a long-running AI agent that needs to interact with terminal applications. Maybe it runs a REPL, an interactive installer, a TUI debugger, or another CLI agent. The standard approaches all break in the same ways. subprocess.run() captures stdout but cannot handle interactive prompts. tmux control mode accepts string commands and returns string output with no guaranteed schema. Custom PTY code handles one platform, breaks on another.
The analogy the rmux Show HN post used is Playwright. Playwright did not solve browser automation by parsing HTML better. It solved it by building a typed, stable API over the browser's own execution model, giving every element a stable identifier and exposing programmatic waits for state changes. rmux is the same insight applied to terminals: build a typed, stable API over the terminal multiplexer's own session model, give every pane a stable ID, expose structured snapshots and locator-style waits.
The result shipped as v0.2.0 on May 18 2026 (public preview), hit Show HN on May 21, and reached v0.3.1 by May 25 with 90 tmux-compatible commands fully implemented. The upstream repo is Helvesec/rmux. The fork at mohnishbasha/rmux has 754 commits and appears to be an extended development branch.
Scope: rmux's three-surface architecture (CLI, SDK, Ratatui widget), the five-crate workspace internals, the two primary agentic use cases (host and supervisor), and the platform portability design. Not covered: the rmux configuration DSL in detail, or session layout persistence format.
What It Actually Does
rmux is a terminal session manager and automation engine. Three coordinated surfaces, one shared daemon:
Surface | Interface | Primary User |
|---|---|---|
| 90 tmux-compatible commands | Human terminal users, shell scripts |
| Typed async Rust API | AI agents, Rust orchestrators |
| Ratatui integration | TUI applications embedding live panes |
Installation:
# Install from crates.io
cargo install rmux --locked --version 0.3.0
# Or download binary from GitHub releases (includes SHA256 checksums)
# Linux: rmux-x86_64-unknown-linux-musl
# macOS: rmux-aarch64-apple-darwin, rmux-x86_64-apple-darwin
# Windows: rmux-x86_64-pc-windows-msvc.exe
CLI quickstart (tmux-compatible):
rmux new-session -s my-agent # create a named session
rmux send-keys -t my-agent:0 "python agent.py" Enter
rmux detach-client # detach: agent keeps running
# ... hours later, over a new SSH connection ...
rmux attach-session -t my-agent # resume exactly where you left off
The Architecture, Unpacked

Focus on the crate split between rmux-core and rmux-pty. Core owns the session model (sessions, panes, layouts, buffers) while PTY handles the operating system's terminal abstraction. This separation means the session model is portable across platforms; only rmux-pty needs platform-specific implementations. All three client surfaces (CLI, SDK, widget) operate on the same session model through the same IPC layer.
The Code, Annotated
Snippet One: SDK Quickstart (the Playwright Pattern for Terminals)
// rmux-sdk: typed async terminal automation
// Source: Helvesec/rmux (MIT/Apache-2.0)
// Pattern: Playwright-style API over terminal sessions
use rmux_sdk::{Client, SessionOptions, PaneLocator, WaitCondition};
use std::time::Duration;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Step 1: Connect to the rmux daemon (starts one if not running)
// ← No manual daemon management: the SDK handles "start if missing" automatically
// This is the same pattern as Playwright's browser launch
let client = Client::connect().await?;
// Step 2: Create a named session
// ← Sessions are persistent: they survive client disconnection
// An agent can disconnect and reconnect to the same session later
let session = client.new_session(SessionOptions {
name: "agent-workspace".into(),
start_command: None, // opens default shell
}).await?;
// Step 3: Get a stable reference to the initial pane
// ← STABLE PANE IDs: unlike tmux control mode where pane target strings
// are positional and can shift when windows are split/closed,
// rmux-sdk pane IDs are stable UUIDs that survive layout changes
let pane = session.pane(0).await?;
// Step 4: Send input to the pane
// ← send_keys works on interactive TUI apps that cannot be driven by subprocess
// An interactive Python REPL, a TUI editor, an interactive installer:
// all accept keys the same way a human would type them
pane.send_keys("python3 -c \"import sys; print(sys.version)\"").await?;
pane.send_keys("\n").await?; // ← Enter key
// Step 5: Wait for expected output (locator-style wait)
// ← THIS is the trick: instead of sleep(n), you wait for a CONDITION
// This is Playwright's waitForSelector equivalent for terminals
// The SDK polls the pane snapshot until the condition is satisfied
// or the timeout expires
let snapshot = pane.wait_for(
WaitCondition::TextContains("3.".into()),
Duration::from_secs(10),
).await?;
println!("Python version line: {}", snapshot.last_line());
// Output: Python version line: 3.12.3 (main, Apr 9 2026, 08:20:14)
// Step 6: Structured snapshot (not raw string parsing)
// ← snapshot.lines() returns Vec<String> (not a raw PTY byte stream)
// Scrollback is captured up to the configured buffer size
// Cursor position is part of the snapshot struct
let full_snap = pane.snapshot().await?;
println!("Lines: {}, Cursor: {:?}", full_snap.lines().len(), full_snap.cursor());
// Step 7: Detach (agent keeps running)
// ← The session persists on the daemon. The client can disconnect.
// Another client (or the same one after reconnect) can reattach.
client.detach_session(&session).await?;
Ok(())
}
The wait_for(WaitCondition::TextContains(...), timeout) pattern is the Playwright equivalence in practice. Raw tmux scripting requires sleep calls between commands and string-parsing the output of tmux capture-pane. The SDK's structured wait removes both problems: no sleep needed (waits for the actual condition), no string parsing needed (snapshot is typed).
Snippet Two: Supervisor Pattern (Driving Another CLI Agent)
// rmux-sdk supervisor pattern: orchestrating multiple terminal sessions
// Source: Helvesec/rmux SDK documentation (MIT/Apache-2.0)
// This is the use case that has no clean alternative in existing tools
use rmux_sdk::{Client, SessionOptions, PaneLocator, WaitCondition};
use std::time::Duration;
async fn run_agent_in_supervised_session(
client: &Client,
agent_cmd: &str,
session_name: &str,
) -> anyhow::Result<AgentResult> {
// Create an isolated session for this agent run
// ← Each agent gets its own session: isolated PTY, isolated state
// Multiple agents can run in parallel, each in its own session
let session = client.new_session(SessionOptions {
name: session_name.into(),
start_command: Some(agent_cmd.into()),
// ← agent_cmd launches directly into the session's shell
// Could be: "claude code", "aider", "python my_agent.py"
}).await?;
let pane = session.pane(0).await?;
// ── PHASE 1: Wait for agent to become ready ──────────────────────────────
// ← Locator wait: supervisor does not poll a fixed interval
// It waits until the agent outputs its ready signal
pane.wait_for(
WaitCondition::TextContains("Ready for instructions".into()),
Duration::from_secs(30),
).await?;
// ── PHASE 2: Drive the agent with structured input ────────────────────────
pane.send_keys("Fix all type errors in src/main.rs\n").await?;
// ── PHASE 3: Wait for task completion ────────────────────────────────────
// ← WaitCondition::Regex matches against the full pane snapshot text
// Use this when completion signal is a variable string (like a diff count)
let completion_snap = pane.wait_for(
WaitCondition::Regex(r"Fixed \d+ error".to_string()),
Duration::from_secs(300), // 5 minute timeout for the agent task
).await?;
// ── PHASE 4: Extract structured result ────────────────────────────────────
// ← snapshot.find_line() scans the terminal buffer for a matching line
// Returns None if not found (no panic on missing output)
let result_line = completion_snap
.find_line(|l| l.contains("Fixed"))
.unwrap_or("No completion line found");
println!("Agent result: {}", result_line);
// ── PHASE 5: Decide to keep or kill the session ────────────────────────────
// ← Keep: human can attach later to inspect the agent's terminal state
// ← Kill: release resources if the run is fully completed
if completion_snap.contains("errors remain") {
// Session stays alive for human inspection
println!("Keeping session '{}' for manual review", session_name);
} else {
// ← Kill cleans up the PTY and daemon state for this session
client.kill_session(session_name).await?;
}
Ok(AgentResult {
session: session_name.into(),
output: result_line.to_string(),
})
}
// ── Running multiple agents in parallel ────────────────────────────────────────
#[tokio::main]
async fn multi_agent_supervisor() -> anyhow::Result<()> {
let client = Client::connect().await?;
// ← tokio::join! runs all three agent tasks concurrently
// Each gets its own session, its own PTY, its own isolated terminal
// The supervisor orchestrates all three from typed async Rust
let (r1, r2, r3) = tokio::join!(
run_agent_in_supervised_session(&client, "claude code", "agent-claude"),
run_agent_in_supervised_session(&client, "aider --model gpt-4o", "agent-aider"),
run_agent_in_supervised_session(&client, "python custom_agent.py", "agent-custom"),
);
println!("All agents completed: {:?}", [r1?, r2?, r3?]);
Ok(())
}
The tokio::join! running three agents concurrently is the supervisor pattern's practical demonstration. Each agent runs in a separate rmux session, driven by the same SDK client, with the supervisor waiting for typed completion signals from each. This is not achievable with raw subprocess or tmux control mode: subprocess cannot drive interactive TUI agents, and tmux control mode provides no typed wait conditions.
It In Action: End-to-End Worked Example
Scenario: AI coding agent running a long task over SSH. Connection drops. Resume later with zero lost work.
Step 1: Start the agent in a named rmux session
# On remote server (over SSH)
rmux new-session -s coding-sprint
rmux send-keys -t coding-sprint "claude code --task 'Refactor auth module'" Enter
# Claude Code agent starts, begins working...
# Output appears in the pane:
# > Analyzing auth module...
# > Reading 14 files...
# > Making changes...
Step 2: SSH connection drops unexpectedly
# Network interruption: SSH session terminates
# Without rmux: agent process receives SIGHUP → dies
# With rmux: daemon keeps agent's PTY alive, agent continues uninterrupted
Step 3: Reconnect hours later
# New SSH connection to same server
rmux ls # list running sessions
# Output:
# coding-sprint: 1 windows (created 3h 42m ago) [220x56]
rmux attach-session -t coding-sprint
# Agent is still running:
# > Processing file 87/214...
# Full terminal history visible in scrollback
Step 4: Supervisor inspects agent state without attaching
// A separate monitoring process can check agent state without interrupting it
let client = Client::connect().await?;
let snap = client.session("coding-sprint")
.pane(0)
.snapshot()
.await?;
// Check current progress from the pane snapshot
let progress_line = snap.find_line(|l| l.contains("Processing file"));
println!("{:?}", progress_line);
// Some("Processing file 87/214...")
// Check if any errors appeared
let has_error = snap.contains("Error:");
println!("Has error: {}", has_error);
// false
Platform comparison:
tmux rmux-sdk subprocess
Interactive TUI: ✗ (no SDK) ✓ ✗ (no pty)
Typed snapshots: ✗ (strings) ✓ ✗ (strings)
Stable pane IDs: ✗ ✓ N/A
Locator waits: ✗ ✓ ✗
Windows native: ✗ (WSL only) ✓ (ConPTY) ~
Persistent sess: ✓ ✓ ✗
Ratatui embed: ✗ ✓ ✗
Why This Design Works, and What It Trades Away
The crate split between rmux-core and rmux-pty is the architectural decision that enables cross-platform portability without compromising the session model. rmux-core owns sessions, panes, layouts, and the command registry; it has no platform-specific code. rmux-pty owns PTY allocation and child process control with separate implementations for Unix (using openpty) and Windows (using the ConPTY API). The session model is portable; the PTY implementation is not required to be.
The Tokio-based daemon (rmux-server) is the correct architecture for a multiplexer that needs to handle multiple concurrent clients, multiple sessions, and PTY I/O without blocking. Async I/O on PTY file descriptors maps naturally to Tokio's event loop. The daemon starts automatically on first command, eliminating the "start server manually before use" friction of some tmux setups.
The typed protocol (rmux-proto) is what enables the SDK to be a genuine improvement over tmux's control mode. Tmux control mode outputs strings like %output $1 hello that clients must parse with regexes. rmux-proto defines typed DTOs (data transfer objects) that deserialize directly into Rust structs. The SDK caller receives a PaneSnapshot struct, not a raw byte buffer.
What rmux trades away:
Maturity. v0.3.1 is a fresh public preview. The README explicitly warns that bugs are expected and asks for issues. A project this new has not been stress-tested against the edge cases that production tmux usage encounters: unusual terminal escape sequences, deeply nested PTY chains, extremely high-throughput output streams. The automated regression suite (including SIXEL passthrough) is a good sign, but it does not substitute for production time.
Ecosystem. tmux has decades of integrations: tmuxinator for session management, tmux-resurrect for persistence, hundreds of shell scripts and plugins. rmux starts from zero. Teams adopting rmux for the SDK gains give up the tmux ecosystem for their existing terminal workflows.
Stability guarantee. The SDK API surface is not yet marked stable. A rmux-sdk crate that changes between minor versions creates maintenance cost for agent orchestrators that pin to it.
Technical Moats
The Playwright analogy as a design discipline. The "Playwright for terminals" framing is not just marketing. Playwright's API works because it builds on the browser's own execution model (Chrome DevTools Protocol) rather than wrapping page screenshots or HTML parsing. rmux's SDK works because it builds on the multiplexer's own session model (the daemon's internal state) rather than parsing PTY byte streams. The quality of the abstraction depends on the quality of the underlying model. rmux's architecture puts the session model in rmux-core where all surfaces share it, which is the same architectural discipline that makes Playwright reliable.
Native Windows support without WSL. tmux does not run on Windows without WSL (Windows Subsystem for Linux). rmux uses the real ConPTY API on Windows, providing native terminal multiplexing without the overhead of a Linux compatibility layer. For AI agent workflows that need to run on developer Windows machines, this is a genuine capability gap that tmux cannot fill.
The ratatui-rmux widget. Embedding a live terminal pane inside a Ratatui TUI application is not achievable with tmux. rmux provides a widget that renders a live pane snapshot inside any Ratatui app, allowing TUI dashboards that show agent terminal state without separate window management. This integration is only possible because the session model is accessible as a typed Rust API.
Insights
Insight One: rmux is not a better tmux. It is a different product that happens to be tmux-compatible. The 90 tmux-compatible commands exist so that existing users can switch without relearning. The actual differentiator is the rmux-sdk Rust crate, which enables a class of agentic workflows that tmux's string-based control mode cannot support. Teams evaluating rmux as a "tmux replacement" are evaluating the wrong thing. The correct evaluation is: does your workflow need a typed, stable API for terminal session control? If yes, rmux is the first tool that provides it. If no, tmux is fine.
Insight Two: The supervisor pattern (a Rust/Python orchestrator driving terminal apps via the rmux SDK) is the more technically interesting use case, but it is also the one that requires rmux's session model to be stable across agent-facing APIs. An agent supervisor that sends pane.send_keys() and calls pane.wait_for() is coupled to the rmux-sdk API surface. If that API changes in v0.4, the supervisor breaks. rmux is pre-1.0 with an explicitly unstable API. Teams building production supervisors on rmux today are accepting real maintenance risk for the gain of typed terminal automation. The value is real. So is the risk.
Takeaway
The rmux daemon handles PTY I/O and terminal state persistence entirely in user space. There is no kernel module, no privileged system process, and no dependency on any system-level multiplexer service. The entire session model, including the PTY file descriptors for running processes, is managed by the Tokio async daemon running as the current user. This means rmux sessions can be created and destroyed without root, work inside containers (where kernel-level session management is often restricted), and run on restricted cloud environments where installing system services requires privileges. For AI agent deployments in containerized cloud environments, this architecture is more deployable than solutions that require privileged processes.
TL;DR For Engineers
rmux (Helvesec/rmux, dual MIT/Apache-2.0, v0.3.1 May 25 2026) is a Rust terminal multiplexer with a typed async SDK. Three surfaces:
rmuxCLI (90 tmux-compatible commands),rmux-sdkcrate (stable pane IDs, structured snapshots, locator-style waits),ratatui-rmuxwidget (embed live panes in Ratatui apps). All share one Tokio daemon.Five-crate workspace: rmux-core (session model), rmux-pty (PTY abstraction for Unix PTY + Windows ConPTY), rmux-proto (typed IPC DTOs), rmux-ipc (transport layer), rmux-server (Tokio daemon). The session model in rmux-core is shared by all three client surfaces. No platform-specific code in the session logic.
Two agentic use cases: HOST (run agent's CLI in rmux session for SSH survival and inspect/detach) and SUPERVISOR (drive CLI/TUI apps programmatically via rmux-sdk). The supervisor pattern is the technically unique capability.
cargo install rmux --locked --version 0.3.0. Config: .rmux.conf, XDG, %APPDATA% on Windows. Windows uses Named Pipes for IPC (no WSL required).Pre-1.0, fresh preview: bugs expected, SDK API not yet stable. The automated regression suite includes SIXEL passthrough. File issues on GitHub.
The Terminal Finally Has an API
rmux's contribution is making the terminal session model accessible as a typed, stable, async API, the same way Playwright made browser automation accessible. The 90 tmux-compatible commands lower the adoption barrier for human users. The SDK is the reason engineers building agentic workflows should care.
The fresh preview status is real: v0.3.1 on May 25 2026 is weeks old. Production deployment requires accepting the current API instability. The architecture is the right one, the implementation is early, and the supervisor pattern has no clean equivalent in existing tooling. That is the correct context for evaluating rmux in June 2026.
References
Helvesec/rmux GitHub Repository, dual MIT/Apache-2.0
mohnishbasha/rmux fork, 754 commits (extended development)
Show HN: rmux, May 21 2026 — author: shideneyu, framing: "A programmable terminal multiplexer with a Playwright-style SDK"
Ratatui — the Rust TUI framework that ratatui-rmux integrates with
tmux control mode — the string-based protocol rmux's typed SDK improves on
rmux (Helvesec/rmux, dual MIT/Apache-2.0, v0.3.1 May 25 2026) is a Rust terminal multiplexer with three coordinated surfaces (rmux CLI with 90 tmux-compatible commands, rmux-sdk typed async crate, ratatui-rmux widget) sharing a single Tokio daemon and five-crate workspace (rmux-core for session model, rmux-pty for Unix PTY and Windows ConPTY, rmux-proto for typed IPC DTOs, rmux-ipc for transport, rmux-server for daemon). The SDK enables a "Playwright for terminals" pattern: stable pane IDs, structured snapshots, and locator-style waits that allow agent supervisors to drive interactive CLI/TUI applications programmatically without subprocess limitations or tmux's string-based control mode. Pre-1.0 public preview with explicitly unstable API; file issues.
Sponsored Ad
If you enjoy practical AI insights, check out SnackOnAI and support the newsletter by subscribing, sharing, and exploring our sponsored ad — it helps us keep building and delivering value 🚀
Moda is the AI design agent with taste
Moda's viral launch hit 4.4 million views in two days. Tens of thousands of professionals signed up. Startups, agencies, forward-thinking brands and top firms are now using Moda to create brand-aligned slides, ad creative, reports, social carousels and more.
Most AI tools tend to create what we call "AI slop": repetitions of the same colors, layouts and fonts. And when you try to fix it, you get stuck in a loop of re-prompting.
Moda is different. Drop in your website URL, and Moda learns your brand from the ground up: your colors, your fonts, your visual language. Then it helps you generate pro-quality slides, docs, and marketing assets.
The best part? Every layer is fully editable on a real canvas, and exports to powerpoint, PDF and more.


