- Rust 94.8%
- Nix 5.2%
| assets | ||
| docs | ||
| src | ||
| .envrc | ||
| .gitignore | ||
| .muxrun.toml | ||
| AGENTS.md | ||
| Cargo.lock | ||
| Cargo.toml | ||
| devenv.lock | ||
| devenv.nix | ||
| devenv.yaml | ||
| flake.lock | ||
| flake.nix | ||
| LICENSE | ||
| package.nix | ||
| README.md | ||
| SKILL.md | ||
muxrun
Durable, inspectable PTY commands for coding agents.
muxrun runs a command inside a project-scoped
RMUX terminal. A coding agent can wait for a
bounded interval, leave the command running, inspect it later, send input, or
attach a real terminal without restarting the process.
Use it for builds, tests, dev servers, experiments, installers, and interactive programs that should survive an agent request or terminal ending.
Want your coding agent to install muxrun for you? Give it Install muxrun for an agent and ask it to follow the document in your project.
Why muxrun
- Commands survive the
muxrunclient and agent request that started them. - Every command gets a durable task ID and a real PTY.
- Agents can inspect rendered history, raw terminal output, or the current TUI.
- Agents can send text and terminal keys without owning stdin continuously.
- Text and JSON output support both humans and automation.
muxrun lsrecovers task IDs after an agent or terminal exits unexpectedly.- The Nix package pins RMUX 0.8.0 at evaluation, build, and runtime.
Requirements
- Linux on
x86_64for the current Nix flake - Nix with flakes, or devenv
- A
.muxrun.tomlin each project that uses muxrun
The Nix package includes the matching RMUX runtime. Building directly with
Cargo instead requires rmux 0.8.0 and rmux-daemon on PATH.
Install
Try It Without Installing
Run muxrun directly from the Forgejo repository:
nix run git+https://git.gjz010.com/gjz010/muxrun.git -- skill
Commands that manage tasks must run in a project containing .muxrun.toml.
Nix Flake
Add muxrun as an input and put its package in your dev shell or package list:
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
muxrun.url = "git+https://git.gjz010.com/gjz010/muxrun.git";
};
outputs =
{ nixpkgs, muxrun, ... }:
let
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
in
{
devShells.${system}.default = pkgs.mkShell {
packages = [ muxrun.packages.${system}.default ];
};
};
}
Then update the lock file and enter the shell:
nix flake lock
nix develop
muxrun --version
muxrun has a separate, locked rmux-nixpkgs input that supplies only RMUX
0.8.0. Do not override it to follow the project's nixpkgs. Updating your
project's nixpkgs, or muxrun's build-tool nixpkgs, therefore keeps the exact
same RMUX derivation and continues to use its existing binary-cache entry.
devenv
Add muxrun to devenv.yaml without changing your existing inputs:
inputs:
muxrun:
url: git+https://git.gjz010.com/gjz010/muxrun.git
Then expose that input's package from devenv.nix:
{ inputs, pkgs, ... }:
{
packages = [
inputs.muxrun.packages.${pkgs.stdenv.system}.default
];
}
Merge these snippets into existing inputs and packages; do not replace
other project settings. The muxrun input carries its independent
rmux-nixpkgs pin transitively; do not override it with follows.
Resolve the input and verify the shell:
devenv update muxrun
devenv shell -- muxrun --version
Configure A Project
Create .muxrun.toml at the project root:
root = ".devenv/muxrun"
interval = "3m"
history_limit = 100000
completed_ttl = "24h"
cancel_grace = "5s"
# Omit for unlimited completed output. When set, muxrun retains the head and
# tail and marks the report as truncated.
# output_limit = 1048576
[terminal_size]
cols = 120
rows = 40
muxrun first uses the file named by MUXRUN_CONFIG. Otherwise it searches
from the current directory toward the filesystem root for .muxrun.toml.
Relative root paths are resolved from the configuration file's directory.
Ignore the runtime state directory if your project does not already ignore it:
.devenv/muxrun/
Install The Agent Skill
The executable embeds SKILL.md, which teaches OpenCode-compatible agents when and how to use muxrun:
mkdir -p .opencode/skills/muxrun
muxrun skill > .opencode/skills/muxrun/SKILL.md
Restart OpenCode after installing or updating the skill. For another agent
that supports SKILL.md, redirect muxrun skill into that agent's project or
global skill directory.
Quick Start
Wait up to the configured interval for a command:
muxrun run -- cargo test --workspace
If the command finishes, muxrun prints its rendered terminal history and
returns its exit status. If it is still running, muxrun prints a task ID with
reason=timeout and leaves the process running.
Start immediately in the background:
muxrun start -- cargo run --release
List and inspect retained tasks:
muxrun ls
muxrun status TASK_ID
muxrun tail -n 100 TASK_ID
muxrun screen TASK_ID
muxrun transcript TASK_ID
Continue waiting for a task:
muxrun resume TASK_ID
When resume observes completion, it prints the final output, returns the
child's exit status, and consumes the task. A timeout preserves the task.
Interactive Input
send does not append a newline. Send Enter explicitly:
muxrun send TASK_ID 'yes'
muxrun send --key TASK_ID Enter
muxrun send --key TASK_ID C-c
Attach a human terminal when direct interaction is needed:
muxrun attach TASK_ID
Stop And Clean Up
muxrun cancel TASK_ID
muxrun clean TASK_ID
muxrun gc
cancelsendsC-c, waitscancel_grace, then sendsSIGTERMto the PTY process group if needed. The task remains inspectable.cleanimmediately kills the session and deletes its metadata.gcremoves expired completed tasks and never kills running tasks.
JSON Output
Place global --json before the subcommand:
muxrun --json run -- cargo test
muxrun --json ls
muxrun --json status TASK_ID
Reports include the task ID, state, command, elapsed time, truncation and
consumption flags, and relevant reason, wait_signal, exit_code, signal,
or output fields.
Signals And Recovery
If a waiting run or resume receives SIGHUP, SIGINT, SIGQUIT, or
SIGTERM, it prints the retained task ID with reason=signal, reports the
signal, and exits with 128 + signal. It does not signal the child task.
SIGKILL cannot be caught. The RMUX task still survives; recover its ID with:
muxrun ls
Raw RMUX Escape Hatch
Forward an operation that muxrun does not expose directly to the pinned RMUX CLI and current project socket:
muxrun rmux -- list-sessions
muxrun rmux -- list-panes -a
stdin, stdout, stderr, and the RMUX exit status are passed through. Do not pass
-S; muxrun owns project socket selection.
PTY Semantics
- stdout and stderr are merged into one terminal stream.
- The child sees a TTY and may enable colors, line buffering, progress redraws, or a full-screen interface.
- stdin from
start,run, andresumeis not forwarded. Usesendorattach. tailand completed output are rendered terminal history.transcriptretains raw PTY bytes including ANSI escapes and carriage returns.- RMUX owns the PTY, rendered screen, output retention, and scrollback. muxrun stores task metadata and uses a project-scoped Unix socket.
RMUX Version Binding
The flake uses two independent nixpkgs inputs. The main nixpkgs builds
muxrun, while rmux-nixpkgs supplies one cache-stable RMUX 0.8.0 derivation.
Updating the main input does not rebuild or move RMUX. The package also asserts
RMUX 0.8.0 during evaluation, embeds its absolute store path for CLI operations,
and wraps the binary with the matching daemon path. An unrelated rmux earlier
on PATH is not used. Update rmux-sdk, the version assertion, and
rmux-nixpkgs together when intentionally changing the RMUX protocol version.
Development
cargo fmt --all -- --check
cargo clippy --all-targets -- -D warnings
cargo test
nixfmt --check devenv.nix flake.nix package.nix
nix flake check
nix build .#muxrun
devenv build outputs.muxrun
License
Licensed under the Apache License 2.0.