01 · foundation

Getting started

Install darkmux, verify the setup, declare your first profile, dispatch a role, and launch your first mission. Everything else in the guide assumes you've done this.

Prerequisites

RequiredWhyInstall
Homebrew How darkmux installs (the tap + formula below). One-line install at brew.sh.
LMStudio darkmux drives LMStudio via the lms CLI for load/unload. macOS / Windows / Linux installer.
At least one model in LMStudio Nothing for a dispatch to load without one. Download via the LMStudio UI; verify with lms ls.

brew install needs no toolchain: Homebrew handles the build, and bottled binaries (once published) ship precompiled.

darkmux is developed and tested on Apple Silicon. Linux should work; Intel Mac is untested.

For the dispatch + lab path

The profile / machine core needs only LMStudio. Docker is required for dispatch / lab run (the only dispatch path).

ToolWhen you need it
Docker Required for darkmux dispatch / lab run. The dispatch runs in a per-invocation darkmux-runtime container; the image is pulled from GHCR on first use, so you just need Docker running.
Claude Code (or any frontier orchestrator) Recommended. darkmux is designed to be driven by a frontier orchestrator. darkmux init installs agent skills (including the /darkmux-bootstrap setup skill) and dispatches agentic work through one. The bare CLI works without it, but the orchestrator is the intended path; see Recommended: let your orchestrator set it up below.

Install + bootstrap

From a fresh macOS machine that has LMStudio installed:

# 1. Tap + install
brew tap kstrat2001/darkmux
brew install darkmux                # stable release (or --HEAD for latest main)

# 2. Bootstrap config + agent skills
darkmux init
If brew install refuses with an "untrusted tap" error, newer Homebrew gates third-party taps behind a one-time trust step: run brew trust kstrat2001/darkmux, then re-run the install. (Older Homebrew won't prompt for this.)

The tap is at kstrat2001/homebrew-darkmux. The formula installs the darkmux CLI, the serve daemon, and the bundled agent skills. Updating later is brew upgrade darkmux (plus brew services restart darkmux to pick up the new daemon).

That's the whole install. Local dispatch works too: the darkmux-runtime Docker image is pulled from GHCR on first use, so you just need Docker running.

Contributing to darkmux, or building from source? See CONTRIBUTING.md on GitHub.

Recommended: let your orchestrator set it up

darkmux is built to be driven by a frontier orchestrator: a Claude Code, Gemini, Antigravity, Cursor, Codex, or Copilot session that runs the loop, reads the project docs, and proposes the commands. darkmux init installs the agent skills + guidance for exactly this. The recommended way to finish setup (and to operate darkmux day to day) is from inside an orchestrator session, not by hand.

Whichever frontier model you prefer, the first move is the same: have it read the project docs. They're written for any agent and carry darkmux's operating doctrine:

Claude Code gets a head start: open a session in your working directory and run the guided setup skill (installed by darkmux init):

/darkmux-bootstrap

It walks the whole setup interactively: detecting your hardware tier, downloading the recommended models, registering profiles, declaring the orchestrator for flow-record provenance, and validating the end state with darkmux doctor. The skill reads and proposes; you run the commands, so you stay in the loop with provenance on every step. On any other frontier, point the session at the docs above and ask it to do the same; the steps below are exactly what it follows.

Why orchestrator-first?

darkmux's reason for existing is local-AI orchestration: the frontier model is the strategist that drives your local models, and setup is the first place that shows up. Everything from here down documents the same steps by hand: read it to understand what the orchestrator does for you, or follow it directly if you're running darkmux as a bare CLI.

Verify your setup

darkmux doctor runs pre-flight checks across the substrate: profile registry, lms binary, models loaded, RAM headroom, power state, role prompt coverage, mission-config registry, daemon reachability, flow-sink health, audit integrity, machine_id resolution, orchestrator declaration, and bundled-skill freshness (it flags any stale darkmux-* skill and points you at darkmux init), plus a battery of empirical rules from the eureka set.

darkmux doctor

Each check returns Pass, Warn, or Fail. Fail-level lines include actionable hints; the exit code is 0 on all-pass-or-warn, 1 if anything Fails.

Output is one line per check, color-coded: for Pass, ! for Warn, for Fail. Run it yourself on a fresh setup. That's the canonical reference for what it looks like.

Common first-run warnings:

Configure your first profile (or let /darkmux-bootstrap do it)

This is the manual version of what the /darkmux-bootstrap skill does for you. Follow it to understand the moving parts, or if you're running darkmux without an orchestrator.

A profile is a named loadout: which model(s) to keep resident in LMStudio at which context length. Profiles live in ~/.darkmux/profiles.json (created by darkmux init).

Inspect what's already there

darkmux profile list              # list configured profiles
cat ~/.darkmux/profiles.json      # see the actual JSON

The bootstrap config ships with four reference profiles in increasing capacity: fast (32K), balanced (100K), deep (200K), and a gpt-oss preset (the 120B MXFP4 build for Apple Silicon, at 100K). fast/balanced/deep carry a placeholder worker id, <your-worker-model-id>, for you to fill in; gpt-oss ships a concrete id (mlx-community/gpt-oss-120b-MXFP4-Q8). The default profile is balanced, not the 120B preset.

Point a profile at a real model

The quickest way: darkmux profile scan lists the models LMStudio has downloaded that aren't yet in any profile, with a suggestion for each, so you can see what's available to slot in without hand-matching ids. Want a whole starter profile written for you? darkmux profile draft --model <model-id> <profile-name> prints a ready-to-paste profile JSON for a given model and task class (--task-class fast|mid|long).

# Downloaded models not yet in a profile, with suggestions
darkmux profile scan

# Or emit a starter profile for a specific model (prints to stdout)
darkmux profile draft --model <model-id> my-fast --task-class fast

Then replace a placeholder in profiles.json with a real id (lms ls shows the raw list if you prefer). Pick one to start, whatever you've been using day-to-day:

# See what's available (raw)
lms ls

# Edit the file (use whatever editor)
${EDITOR:-vim} ~/.darkmux/profiles.json

Each profile model has two fields that matter:

Profile models are worker models; there's no per-model role field. The standing utility/compactor model is declared once for the machine in the top-level internal.utility binding, not per profile. If a profile lists more than one worker, an optional default_model names which one is the default.

Verify the edits

darkmux profile list              # the profile should now list your model
darkmux doctor                    # confirm "profile match" is green or close

Residency is automatic

You never load a profile by hand. A dispatch loads the models the named profile declares, under the resident RAM budget: it brings up what the profile needs and unloads to make room when the budget requires it, and writes nothing outside LMStudio. The swap verb that used to drive this is gone; the internal runtime reads the loaded model directly.

What happens on dispatch.

When a dispatch needs your profile's models, darkmux issues lms load for each with a darkmux: namespace prefix (so darkmux can recognize its own loads later), sets the context length, and reports success. Nothing outside LMStudio is modified.

If a dispatch reports a warning about RAM headroom or a model not found in lms ls, that's doctor-style feedback at load time, usually fixable by either trimming context length or downloading the model first via the LMStudio UI.

Your first dispatch

A dispatch is the task-grain execution entry point: hand one local-AI role a single message and let it run in the container-bounded runtime. The message is positional, so nothing but the role and the text is required:

darkmux dispatch code-reviewer "Summarize the risk in this change." # role + message, positional
git diff | darkmux dispatch pr-reviewer                             # or pipe the message in on stdin

darkmux loads the profile's model if it isn't resident, runs the role's tool loop, and prints the result. Every dispatch emits a flow record you can watch live in the viewer (below).

Verified at release rehearsal.

This step calls a real model, so its output depends on your hardware and loaded model and isn't reproduced here. It is exercised live against a loaded model as part of the pre-release dogfood, per darkmux's release gate. Run it yourself once a model is loaded; that is the canonical reference for what your setup produces.

Launch your first mission (the centerpiece)

A dispatch runs one role once. A mission runs a whole task graph: a crew of roles works the phases, and the graph is live in your browser as it goes. The built-in coder-phase config is the dispatch-to-PR loop, a worktree, a coder, a QA review, then a stop at an operator sign-off gate.

# start the daemon first so you can watch the graph (the "See it work" section below has the details)
brew services start darkmux

# launch the built-in coder-phase mission; --param supplies its declared inputs
darkmux mission launch coder-phase \
  --param workdir=/tmp/darkmux-first-mission \
  --param base=main \
  --param branch=darkmux/first-mission \
  --param mission_id=first-mission

Those four --param values are coder-phase's declared inputs (worktree path, base branch, new branch name, mission id); launch bails with a copy-pasteable example if one is missing. In practice your frontier orchestrator fills these in for you. This is what it runs under the hood.

Open http://127.0.0.1:8765/ and you see the mission graph come alive: phases light up as the crew works them, each dispatch streaming its tokens and detectors into the timeline. The run stops at the sign-off gate; from there your frontier orchestrator ships the git work by hand (commit, push, gh pr create, gh pr merge), then darkmux mission finalize <id> closes out the darkmux-side state, or darkmux mission abort <id> tears the worktree down. Watching that graph is the first-wow moment: this is what darkmux is for.

Verified at release rehearsal.

A mission launch drives real model dispatches, so its trajectory and timing depend on your hardware and loaded model and aren't reproduced here. The coder-phase gate-held loop is exercised live before every release per darkmux's release gate (a real gate-held dogfood on the pepper-grinder fixture). Run it against a repo of your own to see your graph.

The health duo: machine + doctor

Two read-only checks answer two different questions. darkmux doctor asks is my setup correct (preflight, config, pass/warn/fail, run earlier in this guide). darkmux machine asks is my host healthy right now (live residents, RAM truth). Bare darkmux machine routes to machine status:

darkmux machine status            # what's loaded right now; which profile (if any) matches
darkmux machine resources         # live per-model RAM commitment vs footprint + machine pressure

machine status groups loaded models by ownership: darkmux-managed (under the darkmux: namespace) vs your own user state, which darkmux never touches. machine resources is the live memory ledger, kernel counters and lms metadata only, zero model dispatches. To release darkmux's own RAM footprint without disturbing anything you loaded yourself: darkmux machine eject.

The smoke test

darkmux lab run quick-q           # single-turn smoke prompt against the active profile
darkmux lab run inspect <run-id>      # quick look at what happened

This runs through darkmux's internal Docker-bounded runtime. The runtime image is pulled from GHCR on first use, so you just need Docker running. No Docker yet? Skip this step and come back after the lab section. (Like a dispatch, this calls a real model, so its numbers are yours to generate.)

See it work: start the daemon

darkmux records everything it does as flow records, and the daemon serves them to a live viewer in your browser. Starting it is one command.

The daemon runs as a managed background service. It starts now, and again at every login:

brew services start darkmux

Open the viewer at the daemon's own address:

open http://127.0.0.1:8765/        # or just visit it in any browser

The page is served by your local daemon, so it loads your own records directly: no account, nothing uploaded. It's empty until darkmux does some work; if you ran the smoke test above with the daemon up, that dispatch is already on the timeline. Want to see the shape before installing anything? The playback demo at darkmux.com/demo renders a recorded session.

That's the whole live-viewer setup. The next section is the deeper tour: how to read the timeline, manage the daemon, and the HTTP endpoints it exposes.

What's next

If you set up via /darkmux-bootstrap (or had your orchestrator follow the steps above), your profiles, models, and orchestrator declaration are already in place. Pick a direction: