Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Environment variables for cmux development
# Environment variables for mux development

# API Keys for AI providers
# Required for integration tests when TEST_INTEGRATION=1
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ jobs:
with:
fetch-depth: 0 # Required for git describe to find tags

- uses: ./.github/actions/setup-cmux
- uses: ./.github/actions/setup-mux

- name: Install GNU Make (for build)
run: choco install -y make
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ runs/
__pycache__

tmpfork
.cmux-agent-cli
.mux-agent-cli
storybook-static/
*.tgz
src/test-workspaces/
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

# mux - coding agent multiplexer

[![CI](https://github.com/coder/cmux/actions/workflows/ci.yml/badge.svg)](https://github.com/coder/cmux/actions/workflows/ci.yml)
[![Build](https://github.com/coder/cmux/actions/workflows/build.yml/badge.svg?event=merge_group)](https://github.com/coder/cmux/actions/workflows/build.yml?query=event:merge_group)
[![Download](https://img.shields.io/badge/Download-Releases-purple)](https://github.com/coder/cmux/releases)
[![CI](https://github.com/coder/mux/actions/workflows/ci.yml/badge.svg)](https://github.com/coder/mux/actions/workflows/ci.yml)
[![Build](https://github.com/coder/mux/actions/workflows/build.yml/badge.svg?event=merge_group)](https://github.com/coder/mux/actions/workflows/build.yml?query=event:merge_group)
[![Download](https://img.shields.io/badge/Download-Releases-purple)](https://github.com/coder/mux/releases)
[![License: AGPL-3.0](https://img.shields.io/badge/License-AGPL%203.0-blue.svg)](LICENSE)

</div>
Expand Down Expand Up @@ -51,10 +51,10 @@ like [opportunistic compaction](https://cmux.io/context-management.html) and [mo
## Install

> [!WARNING]
> cmux is in a Preview state. You will encounter bugs and performance issues.
> mux is in a Preview state. You will encounter bugs and performance issues.
> It's still possible to be highly productive. We are using it almost exclusively for our own development.

Download pre-built binaries from [the releases page](https://github.com/coder/cmux/releases) for
Download pre-built binaries from [the releases page](https://github.com/coder/mux/releases) for
macOS and Linux.

[More on installation →](https://cmux.io/install.html)
Expand Down
7 changes: 4 additions & 3 deletions benchmarks/terminal_bench/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Terminal-Bench Integration

This directory contains the cmux agent adapter for [Terminal-Bench](https://github.com/benediktstroebl/terminal-bench), a benchmarking framework for evaluating agentic CLI/terminal capabilities.
This directory contains the mux agent adapter for [Terminal-Bench](https://github.com/benediktstroebl/terminal-bench), a benchmarking framework for evaluating agentic CLI/terminal capabilities.

## Quick Start

Expand Down Expand Up @@ -57,7 +57,7 @@ TB_TIMEOUT=600 make benchmark-terminal TB_SAMPLE_SIZE=5

## Agent Configuration

The cmux agent supports the following kwargs (passed via `--agent-kwarg`):
The mux agent supports the following kwargs (passed via `--agent-kwarg`):

- `model_name`: Model to use (e.g., `anthropic:claude-sonnet-4-5`, `openai:gpt-5-codex`)
- `thinking_level`: Thinking level (`off`, `low`, `medium`, `high`)
Expand Down Expand Up @@ -103,6 +103,7 @@ Based on analysis of the Oct 30 nightly run (15-minute timeout):

- `mux_agent.py`: Main agent adapter implementing Terminal-Bench's agent interface
- `mux-run.sh`: Shell script that sets up environment and invokes mux CLI
- `mux_payload.py`: Helper to package the mux app for containerized execution
- `mux_payload.py`: Helper to package mux app for containerized execution
- `mux_payload.py`: Helper to package mux app for containerized execution
- `mux_setup.sh.j2`: Jinja2 template for agent installation script
- `sample_tasks.py`: Utility to randomly sample tasks from dataset
96 changes: 47 additions & 49 deletions benchmarks/terminal_bench/mux_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@

class MuxAgent(AbstractInstalledAgent):
"""
Minimal Terminal-Bench adapter that installs cmux into the task container and
forwards the benchmark instruction to the cmux headless runner.
Minimal Terminal-Bench adapter that installs mux into the task container and
forwards the benchmark instruction to the mux headless runner.
"""

_ARCHIVE_NAME = "cmux-app.tar.gz"
_RUNNER_NAME = "cmux-run.sh"
_ARCHIVE_NAME = "mux-app.tar.gz"
_RUNNER_NAME = "mux-run.sh"
_DEFAULT_TRUNK = "main"
_DEFAULT_MODEL = "anthropic:claude-sonnet-4-5"
_DEFAULT_PROJECT_CANDIDATES = "/workspace:/app:/workspaces:/root/project"
Expand Down Expand Up @@ -50,18 +50,18 @@ class MuxAgent(AbstractInstalledAgent):
)

_CONFIG_ENV_KEYS: Sequence[str] = (
"CMUX_AGENT_GIT_URL",
"CMUX_BUN_INSTALL_URL",
"CMUX_PROJECT_PATH",
"CMUX_PROJECT_CANDIDATES",
"CMUX_TRUNK",
"CMUX_MODEL",
"CMUX_TIMEOUT_MS",
"CMUX_THINKING_LEVEL",
"CMUX_CONFIG_ROOT",
"CMUX_APP_ROOT",
"CMUX_WORKSPACE_ID",
"CMUX_MODE",
"MUX_AGENT_GIT_URL",
"MUX_BUN_INSTALL_URL",
"MUX_PROJECT_PATH",
"MUX_PROJECT_CANDIDATES",
"MUX_TRUNK",
"MUX_MODEL",
"MUX_TIMEOUT_MS",
"MUX_THINKING_LEVEL",
"MUX_CONFIG_ROOT",
"MUX_APP_ROOT",
"MUX_WORKSPACE_ID",
"MUX_MODE",
)

def __init__(
Expand All @@ -72,18 +72,18 @@ def __init__(
**kwargs: Any,
) -> None:
super().__init__(**kwargs)
repo_root_env = os.environ.get("CMUX_AGENT_REPO_ROOT")
repo_root_env = os.environ.get("MUX_AGENT_REPO_ROOT")
repo_root = (
Path(repo_root_env).resolve()
if repo_root_env
else Path(__file__).resolve().parents[2]
)
if not repo_root.exists():
raise RuntimeError(f"cmux repo root {repo_root} does not exist")
raise RuntimeError(f"mux repo root {repo_root} does not exist")

runner_path = Path(__file__).with_name(self._RUNNER_NAME)
if not runner_path.is_file():
raise RuntimeError(f"cmux runner script missing at {runner_path}")
raise RuntimeError(f"mux runner script missing at {runner_path}")

self._runner_path = runner_path
self._repo_root = repo_root
Expand All @@ -95,7 +95,7 @@ def __init__(

@staticmethod
def name() -> str:
return "cmux"
return "mux"

@property
def _env(self) -> dict[str, str]:
Expand All @@ -106,63 +106,61 @@ def _env(self) -> dict[str, str]:
if value:
env[key] = value

env.setdefault("CMUX_TRUNK", self._DEFAULT_TRUNK)
env.setdefault("CMUX_MODEL", self._DEFAULT_MODEL)
env.setdefault("CMUX_CONFIG_ROOT", "/root/.cmux")
env.setdefault("CMUX_APP_ROOT", "/opt/cmux-app")
env.setdefault("CMUX_WORKSPACE_ID", "cmux-bench")
env.setdefault("CMUX_THINKING_LEVEL", "high")
env.setdefault("CMUX_MODE", "exec")
env.setdefault("CMUX_PROJECT_CANDIDATES", self._DEFAULT_PROJECT_CANDIDATES)
env.setdefault("MUX_TRUNK", self._DEFAULT_TRUNK)
env.setdefault("MUX_MODEL", self._DEFAULT_MODEL)
env.setdefault("MUX_CONFIG_ROOT", "/root/.mux")
env.setdefault("MUX_APP_ROOT", "/opt/mux-app")
env.setdefault("MUX_WORKSPACE_ID", "mux-bench")
env.setdefault("MUX_THINKING_LEVEL", "high")
env.setdefault("MUX_MODE", "exec")
env.setdefault("MUX_PROJECT_CANDIDATES", self._DEFAULT_PROJECT_CANDIDATES)

model_value = self._model_name or env["CMUX_MODEL"]
model_value = self._model_name or env["MUX_MODEL"]
model_value = model_value.strip()
if not model_value:
raise ValueError("CMUX_MODEL must be a non-empty string")
raise ValueError("MUX_MODEL must be a non-empty string")
if "/" in model_value and ":" not in model_value:
provider, model_name = model_value.split("/", 1)
model_value = f"{provider}:{model_name}"
env["CMUX_MODEL"] = model_value
env["MUX_MODEL"] = model_value

thinking_value = self._thinking_level or env["CMUX_THINKING_LEVEL"]
thinking_value = self._thinking_level or env["MUX_THINKING_LEVEL"]
normalized_thinking = thinking_value.strip().lower()
if normalized_thinking not in {"off", "low", "medium", "high"}:
raise ValueError(
"CMUX_THINKING_LEVEL must be one of off, low, medium, high"
)
env["CMUX_THINKING_LEVEL"] = normalized_thinking
raise ValueError("MUX_THINKING_LEVEL must be one of off, low, medium, high")
env["MUX_THINKING_LEVEL"] = normalized_thinking

mode_value = self._mode or env["CMUX_MODE"]
mode_value = self._mode or env["MUX_MODE"]
normalized_mode = mode_value.strip().lower()
if normalized_mode in {"exec", "execute"}:
env["CMUX_MODE"] = "exec"
env["MUX_MODE"] = "exec"
elif normalized_mode == "plan":
env["CMUX_MODE"] = "plan"
env["MUX_MODE"] = "plan"
else:
raise ValueError("CMUX_MODE must be one of plan, exec, or execute")
raise ValueError("MUX_MODE must be one of plan, exec, or execute")

# These env vars are all set with defaults above, no need to validate
for key in (
"CMUX_CONFIG_ROOT",
"CMUX_APP_ROOT",
"CMUX_WORKSPACE_ID",
"CMUX_PROJECT_CANDIDATES",
"MUX_CONFIG_ROOT",
"MUX_APP_ROOT",
"MUX_WORKSPACE_ID",
"MUX_PROJECT_CANDIDATES",
):
env[key] = env[key].strip()

if timeout_value := env.get("CMUX_TIMEOUT_MS"):
if timeout_value := env.get("MUX_TIMEOUT_MS"):
if not timeout_value.strip().isdigit():
raise ValueError("CMUX_TIMEOUT_MS must be an integer")
raise ValueError("MUX_TIMEOUT_MS must be an integer")

if project_path := env.get("CMUX_PROJECT_PATH"):
if project_path := env.get("MUX_PROJECT_PATH"):
if not project_path.strip():
raise ValueError("CMUX_PROJECT_PATH must be non-empty when provided")
raise ValueError("MUX_PROJECT_PATH must be non-empty when provided")

return env

@property
def _install_agent_script_path(self) -> Path:
return self._get_templated_script_path("cmux_setup.sh.j2")
return self._get_templated_script_path("mux_setup.sh.j2")

def perform_task(
self,
Expand Down
40 changes: 20 additions & 20 deletions benchmarks/terminal_bench/mux_agent_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,20 @@


@pytest.fixture(autouse=True)
def _clear_cmux_env(monkeypatch: pytest.MonkeyPatch) -> None:
def _clear_mux_env(monkeypatch: pytest.MonkeyPatch) -> None:
keys = [
"CMUX_AGENT_GIT_URL",
"CMUX_BUN_INSTALL_URL",
"CMUX_PROJECT_PATH",
"CMUX_PROJECT_CANDIDATES",
"CMUX_TRUNK",
"CMUX_MODEL",
"CMUX_TIMEOUT_MS",
"CMUX_THINKING_LEVEL",
"CMUX_CONFIG_ROOT",
"CMUX_APP_ROOT",
"CMUX_WORKSPACE_ID",
"CMUX_MODE",
"MUX_AGENT_GIT_URL",
"MUX_BUN_INSTALL_URL",
"MUX_PROJECT_PATH",
"MUX_PROJECT_CANDIDATES",
"MUX_TRUNK",
"MUX_MODEL",
"MUX_TIMEOUT_MS",
"MUX_THINKING_LEVEL",
"MUX_CONFIG_ROOT",
"MUX_APP_ROOT",
"MUX_WORKSPACE_ID",
"MUX_MODE",
]
for key in keys:
monkeypatch.delenv(key, raising=False)
Expand All @@ -32,20 +32,20 @@ def _repo_root() -> Path:


def test_env_defaults_are_normalized(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("CMUX_AGENT_REPO_ROOT", str(_repo_root()))
monkeypatch.setenv("MUX_AGENT_REPO_ROOT", str(_repo_root()))
agent = MuxAgent(model_name="anthropic/claude-sonnet-4-5")

env = agent._env

assert env["CMUX_MODEL"] == "anthropic:claude-sonnet-4-5"
assert env["CMUX_THINKING_LEVEL"] == "high"
assert env["CMUX_MODE"] == "exec"
assert env["CMUX_PROJECT_CANDIDATES"] == agent._DEFAULT_PROJECT_CANDIDATES
assert env["MUX_MODEL"] == "anthropic:claude-sonnet-4-5"
assert env["MUX_THINKING_LEVEL"] == "high"
assert env["MUX_MODE"] == "exec"
assert env["MUX_PROJECT_CANDIDATES"] == agent._DEFAULT_PROJECT_CANDIDATES


def test_timeout_must_be_numeric(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("CMUX_AGENT_REPO_ROOT", str(_repo_root()))
monkeypatch.setenv("CMUX_TIMEOUT_MS", "not-a-number")
monkeypatch.setenv("MUX_AGENT_REPO_ROOT", str(_repo_root()))
monkeypatch.setenv("MUX_TIMEOUT_MS", "not-a-number")

agent = MuxAgent()
with pytest.raises(ValueError):
Expand Down
6 changes: 3 additions & 3 deletions benchmarks/terminal_bench/mux_payload.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@


def build_app_archive(repo_root: Path, include_paths: Iterable[str]) -> bytes:
"""Pack the cmux workspace into a gzipped tarball."""
"""Pack the mux workspace into a gzipped tarball."""
if not repo_root.exists():
raise FileNotFoundError(f"cmux repo root {repo_root} not found")
raise FileNotFoundError(f"mux repo root {repo_root} not found")

buffer = io.BytesIO()
with tarfile.open(fileobj=buffer, mode="w:gz") as archive:
Expand All @@ -30,7 +30,7 @@ def stage_payload(
archive_name: str,
runner_path: Path,
) -> None:
"""Copy the cmux bundle and runner into the task container."""
"""Copy the mux bundle and runner into the task container."""
with tempfile.NamedTemporaryFile(suffix=".tar.gz", delete=False) as temp_file:
temp_file.write(archive_bytes)
temp_path = Path(temp_file.name)
Expand Down
26 changes: 13 additions & 13 deletions benchmarks/terminal_bench/mux_setup.sh.j2
Original file line number Diff line number Diff line change
Expand Up @@ -30,30 +30,30 @@ export PATH="${BUN_INSTALL}/bin:${PATH}"

if ! command -v bun >/dev/null 2>&1; then
log "installing bun"
curl -fsSL "${CMUX_BUN_INSTALL_URL:-https://bun.sh/install}" | bash
curl -fsSL "${MUX_BUN_INSTALL_URL:-https://bun.sh/install}" | bash
fi

CMUX_APP_ROOT="${CMUX_APP_ROOT:-/opt/mux-app}"
CMUX_CONFIG_ROOT="${CMUX_CONFIG_ROOT:-/root/.mux}"
CMUX_AGENT_VERSION="{{ version if version is not none else '' }}"
MUX_APP_ROOT="${MUX_APP_ROOT:-/opt/mux-app}"
MUX_CONFIG_ROOT="${MUX_CONFIG_ROOT:-/root/.mux}"
MUX_AGENT_VERSION="{{ version if version is not none else '' }}"

rm -rf "${CMUX_APP_ROOT}"
if [[ -n "${CMUX_AGENT_VERSION}" ]]; then
: "${CMUX_AGENT_GIT_URL:?CMUX_AGENT_GIT_URL required when version is set}"
log "cloning mux from ${CMUX_AGENT_GIT_URL} @ ${CMUX_AGENT_VERSION}"
git clone --depth 1 --branch "${CMUX_AGENT_VERSION}" "${CMUX_AGENT_GIT_URL}" "${CMUX_APP_ROOT}"
rm -rf "${MUX_APP_ROOT}"
if [[ -n "${MUX_AGENT_VERSION}" ]]; then
: "${MUX_AGENT_GIT_URL:?MUX_AGENT_GIT_URL required when version is set}"
log "cloning mux from ${MUX_AGENT_GIT_URL} @ ${MUX_AGENT_VERSION}"
git clone --depth 1 --branch "${MUX_AGENT_VERSION}" "${MUX_AGENT_GIT_URL}" "${MUX_APP_ROOT}"
else
log "extracting mux archive"
mkdir -p "${CMUX_APP_ROOT}"
tar -xzf "/installed-agent/mux-app.tar.gz" -C "${CMUX_APP_ROOT}"
mkdir -p "${MUX_APP_ROOT}"
tar -xzf "/installed-agent/mux-app.tar.gz" -C "${MUX_APP_ROOT}"
fi

cd "${CMUX_APP_ROOT}"
cd "${MUX_APP_ROOT}"

log "installing mux dependencies via bun"
bun install --frozen-lockfile

mkdir -p "${CMUX_CONFIG_ROOT}"
mkdir -p "${MUX_CONFIG_ROOT}"

chmod +x /installed-agent/mux-run.sh

Expand Down
Loading