Millmux is a local, durable PTY session layer for long-running terminal processes.
It gives operators and agents a narrow alternative to keeping important work alive inside tmux panes: start a process, detach, reattach, inspect logs, send input, and recover visibility from another terminal. Millmux is designed to be used with Millrace, but its core job is session and terminal ownership, not runtime scheduling.
The Rust package is millrace-sessions. The main binary is millmux.
Status: early local-first infrastructure. The current main branch includes
the session host, per-session workers, daemon console, Agent Cockpit, JSON
control commands, diagnostics, and dogfood evidence.
Supported platforms: macOS and Linux. On Windows, use WSL; native Windows support is not currently documented or tested.
A Millmux session has three layers:
- a hosted process, which is the command running inside a PTY;
- a per-session worker, which owns that PTY and keeps running while clients attach and detach;
- one or more clients, such as the CLI, console, or cockpit, which observe or control the same session.
Millmux keeps the terminal/session record durable. If the hosted process has its own application state, that application remains the source of truth.
From this checkout:
cargo install --path crates/millrace-sessions --locked --forceThat installs:
millmux
millrace-sessiond
millrace-session-worker
From crates.io:
cargo install millrace-sessionsIf you need behavior from the current main branch before a matching release is
published, install from the checkout.
Start any durable PTY session:
millmux start --name shell --role shell --cwd "$PWD" -- bash
millmux attach shellStart and inspect a long-running command:
millmux start --name tests --role worker -- cargo test --workspace
millmux logs tests --tail 80
millmux status tests --jsonOpen the Millrace-oriented console when you are running a Millrace workspace:
WORKSPACE=/path/to/millrace-workspace
millmux console --workspace "$WORKSPACE" --monitor basicOpen the cockpit with an operator agent beside the daemon monitor:
millmux cockpit \
--workspace "$WORKSPACE" \
--monitor basic \
--agent millrace-cliClosing a Millmux client detaches from the session. It does not stop the hosted process.
For active sessions, millmux list --json, millmux status --json, and
millmux inspect --json expose attached_clients and input_owner from the
worker so operators can tell whether a session is only being observed or has an
active PTY input owner.
Millmux owns session and terminal truth:
- session id, name, role, cwd, argv, and optional workspace binding;
- per-session worker process and child process lifecycle;
- PTY input ownership, attach streams, resize, raw output, bounded scrollback, terminal snapshots, and bounded raw replay;
- local session artifacts:
meta.json,worker.json,pty.log,events.jsonl,scrollback.snapshot,terminal.snapshot.json, andpty.replay; - UI context records for console and cockpit clients, including daemon health for visible and managed daemons.
Millmux does not own the application-level truth of the process it hosts. For example, when the hosted process is a Millrace daemon, Millrace remains the authority for queues, tasks, probes, incidents, recovery, and completion evidence. Millmux can show daemon output and record which daemon is visible, but terminal output is not runtime truth.
The install-facing crate, millrace-sessions, ships three binaries:
| Binary | Role |
|---|---|
millmux |
CLI and TUI client for starting, attaching, inspecting, and controlling sessions. |
millrace-sessiond |
Local SessionControl host daemon. It listens on a Unix socket in the state directory. |
millrace-session-worker |
Per-session PTY worker that owns one hosted process. |
The host starts on demand when a command needs it. The control surface is local to the user account; Millmux does not expose a network API.
Most inspection and lifecycle commands support --json for agents and scripts.
Session list, status, and inspect output includes the active attach client
count and input-owner stream id when the worker reports one; terminal records
do not keep stale owner values.
| Command | Purpose |
|---|---|
millmux start -- ... |
Start an explicit argv in a durable PTY session. |
millmux attach <session> |
Attach to a session without tying process lifetime to the attaching terminal. |
millmux send <session> --text ... |
Send input to the PTY. |
millmux resize <session> --rows N --cols N |
Resize the hosted PTY. |
millmux logs <session> |
Read or follow raw PTY output. |
millmux events <session> |
Read or follow structured session events. |
millmux inspect <session> --json |
Inspect metadata, paths, argv, workspace binding, and worker state. |
millmux stop <session> |
Request graceful stop. |
millmux kill <session> |
Force a process down and record the kill. |
millmux delete <session> |
Archive or purge stopped session artifacts. |
millmux context --json |
Read the current UI context. |
millmux doctor --json |
Diagnose state, socket, session, worker, and UI context health. |
Commands that write session lists, status, inspect data, logs, events, or
doctor output treat a closed stdout reader as normal CLI termination. Short
reader pipelines such as millmux events <session> --json | head -c 20000
exit without a Rust panic while still surfacing non-pipe command failures.
Selectors can be a session id/name:
millmux status shellor a workspace/role pair:
millmux status --workspace "$WORKSPACE" --role millrace-daemon --jsonBuilt-in roles are shell, millrace-daemon, agent, generic, and
worker. Custom role strings are accepted.
millmux console is the daemon-facing TUI. It discovers or starts daemon
sessions, shows live output, keeps a scrollable log pane, and writes a UI
context record for the visible session.
millmux console --workspace "$WORKSPACE" --monitor basic
millmux console --workspace "$WORKSPACE" --layout list
millmux console --workspace "$WORKSPACE" --no-startWhen used with Millrace, console mode starts or reattaches a
role=millrace-daemon session for the workspace. Destructive console commands
require explicit confirmation with the selected session id.
millmux cockpit runs an interactive agent PTY beside the selected daemon
monitor. It is for operators who want an agent to inspect or control the same
daemon context they are watching, without making the agent guess which
workspace or session is active.
millmux cockpit --workspace "$WORKSPACE" --monitor basic --agent millrace-cli
millmux cockpit --workspace "$WORKSPACE" --monitor raw --agent millracer
millmux cockpit --workspace "$WORKSPACE" --layout wide --agent-argv -- codex execThe agent pane is a real terminal. Normal input goes to the focused agent pane when Millmux owns PTY input. If another client owns input, cockpit attaches read-only and marks the agent pane accordingly. When the owning attach closes or detaches, cockpit can reopen a writable attach and clear the read-only pane state without stopping the hosted agent or daemon session.
Cockpit avoids legacy line scrollback when rendering agent panes. Reattach and
one-shot snapshots use TUI-safe terminal snapshot/raw replay seed paths, show an
explicit initializing state when no safe frame is available, and keep
agent-pane scroll/page/jump controls inside Millmux state so scroll keys are
not sent to the agent process. The cockpit prefix is Ctrl-]; Ctrl-] [
enters scroll mode, G jumps back to the live bottom, and Ctrl-] d detaches.
Jump-to-bottom resumes live follow.
Cockpit daemon panes distinguish degraded daemon states such as failed_start,
exited, killed, and stale. Failed or exited daemon auto-starts show failure
detail and recovery choices, and the global status does not render ready for a
degraded selected daemon.
When Millmux launches the agent, it sets:
MILLMUX_UI_ID
MILLMUX_CONTEXT_FILE
MILLMUX_STATE_DIR
MILLMUX_CONTROL_SOCK
MILLMUX_AGENT_SESSION_ID
MILLMUX_ACTIVE_DAEMON_SESSION_ID
MILLRACE_WORKSPACE
Agents that need to know the currently visible daemon should read
MILLMUX_CONTEXT_FILE or call:
millmux context --jsonMILLRACE_WORKSPACE is launch-time context. The visible daemon can change
after the agent starts.
Millmux has first-class behavior for Millrace daemon sessions because that is the main production use case.
When console or cockpit auto-starts a Millrace daemon, it resolves millrace
from the invoking client's current PATH and forwards only that allowlisted
PATH by default. Failed starts remain inspectable as session records with
failure detail.
Start a Millrace daemon explicitly:
millmux start \
--workspace "$WORKSPACE" \
--role millrace-daemon \
--monitor basic \
--json \
-- millrace run daemon --workspace "$WORKSPACE" --monitor basicMillmux records the daemon as a millrace-daemon session bound to the
canonical workspace path. Duplicate active daemon sessions for the same
workspace are refused or resolved to the matching existing session when the
argv is identical.
Inspect the daemon:
millmux list --role millrace-daemon --workspace "$WORKSPACE" --json
millmux status --workspace "$WORKSPACE" --role millrace-daemon --json
millmux logs --workspace "$WORKSPACE" --role millrace-daemon --tail 100
millmux events --workspace "$WORKSPACE" --role millrace-daemon --jsonStop it through Millmux:
millmux stop --workspace "$WORKSPACE" --role millrace-daemon --jsonFor millrace-daemon, Millmux first attempts the Millrace-native stop control
path, records millrace_stop_requested, and only falls back to generic PTY or
process lifecycle handling when needed.
Set MILLMUX_STATE_DIR to choose an explicit state root:
export MILLMUX_STATE_DIR="$HOME/.local/state/millmux-dev"Default roots:
| Platform | Default |
|---|---|
| macOS | $HOME/Library/Application Support/millmux |
| Linux | $XDG_STATE_HOME/millmux, or $HOME/.local/state/millmux |
Important artifacts:
host.lock
host.json
session-control.sock
sessions/<session-id>/meta.json
sessions/<session-id>/worker.json
sessions/<session-id>/pty.log
sessions/<session-id>/events.jsonl
sessions/<session-id>/scrollback.snapshot
sessions/<session-id>/terminal.snapshot.json
sessions/<session-id>/pty.replay
views/<ui-id>/context.json
views/<ui-id>/events.jsonl
archive/<session-id>/...
Raw PTY logs, terminal snapshots, replay rings, and UI context files are
local-sensitive diagnostics. They can contain prompts, command output, paths,
tokens printed by child processes, and workspace details. Millmux uses private
Unix permissions for state artifacts, but it does not sanitize PTY output.
Active worker.json records may also include attach client counts and the
current input-owner stream id; lifecycle paths clear those fields for terminal
records.
stopis graceful and records stop evidence.killis explicit and recordskill_requested.deletearchives stopped sessions by default.delete --purgeremoves artifacts and should be used deliberately.- Crashed, stale, lost, killed, exited, and archived sessions remain inspectable instead of being silently discarded.
Host startup reconciles recorded session metadata against local process ids. Live records are preserved, dead active records are marked terminal, and logs remain available for inspection.
Run diagnostics:
millmux doctor
millmux doctor --jsonSupported repairs are explicit:
millmux doctor --repair ARCHIVE_STALE --json
millmux doctor --repair CLOSE_STALE_UI_CONTEXTS --jsonARCHIVE_STALE only archives records proven stale or lost by Millmux-owned
metadata and local process checks. CLOSE_STALE_UI_CONTEXTS closes stale UI
context records that reference no live sessions. Neither repair silently purges
session logs.
Doctor also reports unsafe_legacy_line_scrollback when agent-like sessions
still have legacy scrollback.snapshot lines containing likely full-screen TUI
control sequences. The guidance is to ignore that line scrollback for agent TUI
replay, or archive the session only when it is stale or no longer needed, while
preserving pty.log, events.jsonl, and other raw evidence.
Millmux is not a tmux clone, remote terminal server, web dashboard, restart policy engine, or second Millrace runtime. It intentionally keeps a narrower job: durable local PTY sessions, visible daemon/agent panes, lifecycle records, and a small control protocol that agents can poll.
The repository includes dogfood notes for the core release path:
docs/m1-dogfood.md: release-binary daemon launch, detached survival, duplicate handling, input send, graceful stop, preserved records, and doctor.docs/m2c-agent-cockpit.md: Agent Cockpit behavior and context contract.docs/m2e-hardening-release-dogfood.md: console/cockpit dogfood, daemon switching, detach/crash/reattach, host restart, cleanup, and cockpit QA addenda for terminal replay plus attach ownership.docs/r7-cockpit-release-qa.md: final cockpit terminal remediation release gate with deterministic fixtures, live PTY dogfood, degraded daemon/PATH recovery, doctor output, broken-pipe checks, and cross-terminal evidence limits.
The main verification baseline is:
cargo fmt --all --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace
cargo build --workspace --release
cargo install --path crates/millrace-sessions --locked --root <tmp-root>Before publishing a TUI-capable release, capture fresh dogfood evidence for
millmux console and millmux cockpit against disposable workspaces. For the
cockpit gate, record criterion-by-criterion evidence for repeated full-screen
agent questions, resize, internal scroll/jump-to-bottom, detach, reattach,
degraded daemon recovery, attach ownership, broken-pipe CLI behavior, doctor
output, and any unavailable cross-terminal checks.
Millmux is licensed under MIT. See LICENSE.