Connect another machine on your tailnet to an existing darkmux hub: many machines, one development environment. This is a clean-machine, brew-only walkthrough: every command after brew install should work as written. Start small (an observability peer you dispatch to directly), and graduate to a full fleet worker when the hardware's ready.
A peer connects to a hub, the always-on machine running Redis. If you haven't set one up, do that first: set up a secure hub. It's a short, secure, copy/paste recipe (password-less Redis bound to your tailnet, never the public internet). Come back here once darkmux doctor is green on the hub. You'll need one value from it: the hub's tailnet address (tailscale ip -4 on the hub).
A peer can play two roles, and they're a progression. Start with the first; move to the second when you want it.
On the new machine: install Homebrew and join your Tailscale tailnet (tailscale up). If you'll run dispatches on the peer, also do the base getting-started setup (LMStudio + Docker). Then, confirming it can reach the hub:
# 0. Sanity: can this machine reach the hub over the tailnet?
# HUB_IP is the hub's Tailscale address. Run `tailscale ip -4` ON THE HUB.
HUB_IP=<hub-tailnet-addr>
nc -zvw1 "$HUB_IP" 6379 # expect "succeeded"
# 1. Install darkmux.
brew tap kstrat2001/darkmux
brew install darkmux
# 2. Declare this machine a peer and point it at the hub's Redis (password-less).
darkmux config set fleet.mode peer
darkmux config set redis.enabled true
darkmux config set redis.host "$HUB_IP"
darkmux config set machine_id mini-1 # operator-named; your call
# 3. Verify it reaches the hub.
darkmux doctor
darkmux doctor should read ✓ on flow sink health (a Redis-backed sink reaching the hub) and machine_id, and the verdict banner should land on ● ok (a ⚠ on out-of-scope checks like "no profile loaded yet" is fine). Then prove the end-to-end write:
darkmux flow note --text "hello from mini-1"
Open the hub's live viewer (the hub itself, or https://<hub>.<tailnet>.ts.net/ if you set up Tailscale Serve). The note appears, tagged with this machine's id. That's the peer participating: its records flow to the hub, you see the whole fleet in one place.
darkmux command; the peer does not need to run darkmux serve for its activity to show up in the hub's viewer. (The trade-off: without a local daemon, the peer won't appear in darkmux machine list --deep's live specs probe, which fans out to each machine's daemon. Its flow records are still in the hub viewer.)
Short answer for Part 1: no, and it's worth understanding why before you reach for it.
A darkmux dispatch <role> --machine mini-1 … from another machine publishes the job to the global work queue. But a job only runs when some machine's claiming worker picks it up, and that worker only exists inside a running darkmux serve daemon with Redis configured. In Part 1 the peer isn't running that daemon, so it never claims. And because --machine is an advisory hint, not a pin, the hub's own worker (which IS running) would claim your --machine mini-1 job and run it on the hub.
So:
On the peer, dispatch with no --machine. It runs in the peer's own container, sized by you to fit the hardware:
# On the peer:
darkmux dispatch coder "implement the X feature"
This is the reliable way to use a small box: drive it directly (in person, or over SSH / its own Claude Code session), pick model profiles that fit its RAM, and let its activity stream to the hub for the fleet-wide view. The dispatch path is identical to the hub's (same container, same runtime loop, same flow records), just initiated on the machine that runs it.
To have a machine pull work off the fleet queue, so a dispatch from anywhere in the fleet can run on it, run the always-on daemon. The daemon spawns a worker that claims jobs from the global darkmux:work stream. The Part 1 config already points at the hub's Redis, so this is just starting the daemon:
# On the peer (Part 1 config already set):
brew services start darkmux # daemon + claiming worker
# Confirm it's serving + claiming:
darkmux doctor 2>&1 | grep -i "daemon reachable" # ✓
The worker enables itself because Redis is configured, and shares a consumer group with every other worker on the fleet, so each job goes to exactly one of them. To take the machine back out of the pool, brew services stop darkmux: it reverts to a Part 1 observability peer (records still flow, it just stops claiming).
Because the queue is first-available, any worker can claim any job. Match a worker to the work you send the fleet: a small box is great for light dispatches, but a big-model job that lands on it will run out of memory. If you want a machine to take only the work you hand it, keep it a Part 1 peer and dispatch on it directly.
Register it in the hub's roster so it shows in machine list, and add the hub to the peer's roster:
# On the hub:
darkmux machine add mini-1 --address <peer-tailnet-addr>:8765
# On the peer:
darkmux machine add <hub-id> --address "$HUB_IP":8765
darkmux machine list
Three layers, so the commands above make sense:
machine_id). That's what flow records carry and what --machine hints reference.darkmux:work (the global job queue; workers claim via a shared consumer group) and darkmux:flow (the fleet-wide event log; every machine writes, any daemon's /flow endpoint reads).http://localhost:8765/; with Redis configured it aggregates events from every machine on the shared stream. The demo at darkmux.com/demo shows the bundled showcase. darkmux machine list --deep is the same per-machine view from the terminal.darkmux machine list --deep
MACHINE ADDRESS PROBE RAM-FREE OS VERSION MODELS
laptop 100.64.1.5:8765 ✓ 23ms 78 GB macos aarch64 1.17.0 darkmux:qwen3.6-35b-a3b-mlx
studio 100.64.2.1:8765 ✓ 45ms 12 GB macos aarch64 1.17.0 darkmux:qwen3-4b-instruct
Bounded at ~1s per peer on --deep; degraded peers render with specs? rather than failing the whole command. (Only machines running a daemon, meaning Part 2 workers or the hub, appear here; Part 1 observability peers show their records in the viewer but not in this live probe.)
The trust boundary is your tailnet (Tailscale / WireGuard / ZeroTier), not enforcement in darkmux's code. The fleet's Redis connection carries no auth beyond what your mesh VPN + Redis bind already provide; provenance fields are operator-asserted. This works because everyone on the substrate is you. Multi-tenant deployment is explicitly out of scope; see DESIGN.md.
The substrate degrades gracefully. Knowing the modes saves you confused debugging.
The most common failure. Peers' flow writes fall back to local-file-only (records still land on disk per-machine; the Redis sink errors are logged + skipped). Cross-machine dispatch from a Part 2 worker bails loud. darkmux doctor reports the Redis sink unreachable. An open viewer tab's live tail retries for a bounded budget (~5s), emits a synthetic stream.error, and closes cleanly rather than spinning forever. Recovery: bring the hub back; the substrate self-heals on the next operation, no manual intervention.
Because --machine is advisory, not a pin (see above). A routed dispatch emits a dispatch route flow record; filter the viewer for action: dispatch route. The payload shows target_machine and decision (pinned for an explicit --machine hint, local for a local fall-through). The substrate's reasoning is in the record, not hidden.
Out of scope. machine_id is per-machine, not per-user. If you need per-user provenance on a shared Mac, darkmux has outgrown its target: fork it.