Hub Setup

The hub is a coordinator that manages multiple muxd-daemon instances (nodes) across machines. It tracks registered nodes, proxies requests between them, serves the web dashboard, and manages workers.

There are three components:

  • muxd-hub — the coordinator process. Runs on one machine.
  • muxd-daemon — the agent server. Runs on one or more machines, registers with the hub.
  • muxd — the TUI client. Connects to the hub from your laptop.

Quick start

Same machine

# Terminal 1: start the hub (prints token + QR code)
MUXD_HUB_BIND=0.0.0.0 muxd-hub

# Terminal 2: start the daemon
MUXD_HUB_URL=http://localhost:4097 \
MUXD_HUB_NODE_TOKEN=<token> \
MUXD_BIND=0.0.0.0 \
muxd-daemon

# Terminal 3: connect from TUI
muxd --remote localhost:4097 --token <token>

Separate servers

# Server A (hub)
MUXD_HUB_BIND=0.0.0.0 muxd-hub

# Server B (daemon)
MUXD_HUB_URL=http://server-a:4097 \
MUXD_HUB_NODE_TOKEN=<token> \
MUXD_NODE_NAME=worker-1 \
MUXD_BIND=0.0.0.0 \
muxd-daemon

# Your laptop
muxd --remote server-a:4097 --token <token>

Adding more servers

Each additional machine just runs muxd-daemon pointing at the hub:

MUXD_HUB_URL=http://server-a:4097 \
MUXD_HUB_NODE_TOKEN=<token> \
MUXD_NODE_NAME=worker-3 \
MUXD_BIND=0.0.0.0 \
muxd-daemon

Web dashboard

The hub serves a web dashboard at its root URL. The web UI is embedded in the hub binary — no separate setup needed. Open http://<hub-ip>:4097 in your browser and enter the hub token to log in.

Pages:

  • Dashboard — overview of nodes, workers, sessions, and cost
  • Workers — create and manage autonomous agents triggered by webhooks or schedules
  • Worker Detail — activity feed with approve/reject for human-in-the-loop
  • Nodes — registered daemon nodes with status

In development, the web UI runs separately with hot reload:

cd web && npm run dev    # starts on :5173, proxies /api to hub on :4097

Hub configuration

The hub has no CLI flags. Configure via environment variables or config.json.

Env VarConfig KeyWhat
MUXD_HUB_BINDhub_bind_addressBind interface (default: localhost)
MUXD_HUB_TOKENhub_auth_tokenAuth token (auto-generated if not set)

Daemon registration

Each daemon registers with the hub on startup using these settings:

Env VarConfig KeyWhat
MUXD_HUB_URLhub_urlHub URL (e.g. http://hub:4097)
MUXD_HUB_NODE_TOKENhub_node_tokenToken to authenticate with hub (same as hub's auth token)
MUXD_NODE_NAMEhub_node_nameDisplay name (defaults to hostname)
MUXD_BINDdaemon_bind_addressBind interface (0.0.0.0 for network access)

Set via environment variables:

# Linux / macOS
export MUXD_HUB_URL=http://hub:4097
export MUXD_HUB_NODE_TOKEN=<token>
export MUXD_NODE_NAME=worker-1
muxd-daemon

# Windows (PowerShell)
$env:MUXD_HUB_URL = "http://hub:4097"
$env:MUXD_HUB_NODE_TOKEN = "<token>"
$env:MUXD_NODE_NAME = "worker-1"
muxd-daemon

Or set inside the TUI:

/config set hub.url http://hub:4097
/config set hub.node_token <token>
/config set hub.node_name worker-1

How it works

The hub runs on port 4097 and provides:

  • Node registry — nodes register on startup and send heartbeats every 30 seconds. The hub marks a node offline after 90 seconds of inactivity.
  • Request proxy — session and agent traffic is routed through the hub to the target node. The hub swaps its own auth token for the node's daemon token transparently.
  • Log broker — nodes push log entries to the hub. The hub stores them and streams them to SSE subscribers.
  • Session aggregation — the hub queries all online nodes and returns sessions grouped by node for cross-node discovery.
  • Shared memory — nodes push project memory facts to the hub and pull new facts every 60 seconds.
  • Web UI — serves the browser dashboard for monitoring and worker management.
  • Workers — manages autonomous agents triggered by webhooks or schedules.

Authentication

Bearer token auth (32 random bytes, hex-encoded). The token is auto-generated on first start. All protected routes validate the token via middleware.

The hub and daemons share the same ~/.config/muxd/config.json. The hub only reads hub_* keys. The daemon reads hub_url, hub_node_token, and hub_node_name along with its own model/provider/API key settings.

Service management

Install the hub as a system service:

# Linux
export XDG_RUNTIME_DIR=/run/user/$(id -u)
export DBUS_SESSION_BUS_ADDRESS=unix:path=$XDG_RUNTIME_DIR/bus
MUXD_HUB_BIND=0.0.0.0 muxd-hub -service install
muxd-hub -service start

# macOS
MUXD_HUB_BIND=0.0.0.0 muxd-hub -service install
muxd-hub -service start

# Windows (PowerShell)
$env:MUXD_HUB_BIND = "0.0.0.0"
muxd-hub -service install
muxd-hub -service start

On Linux, enable lingering (run as root, replace YOUR_USER):

loginctl enable-linger YOUR_USER

Without enable-linger, systemd kills user services when you close your SSH session.

All service commands:

muxd-hub -service status    # check if running
muxd-hub -service stop      # stop the service
muxd-hub -service uninstall # remove the service
muxd-hub -service qr        # re-show the QR code and token

Same commands work for muxd-daemon -service <action>.

Open the firewall

The hub listens on port 4097.

Fedora / RHEL (firewalld):

sudo firewall-cmd --add-port=4097/tcp --permanent && sudo firewall-cmd --reload

Ubuntu / Debian (ufw):

sudo ufw allow 4097/tcp

Docker

git clone https://github.com/batalabs/muxd.git
cd muxd
docker compose build

# Create .env with your API keys
echo "MUXD_MODEL=claude-sonnet" > .env
echo "ANTHROPIC_API_KEY=sk-ant-..." >> .env

# Start hub + daemon
docker compose up -d

# Connect from your laptop
muxd --remote localhost:4097 --token <token>

The web dashboard is available at http://localhost:4097.

Configuration keys

Hub group

KeyEnv varWhat
hub_bind_addressMUXD_HUB_BINDHub bind address (default: localhost, use 0.0.0.0 for LAN access)
hub_auth_tokenMUXD_HUB_TOKENBearer token for hub authentication (auto-generated on first start)

Node group

KeyEnv varWhat
hub_urlMUXD_HUB_URLURL of the hub this daemon registers with
hub_node_tokenMUXD_HUB_NODE_TOKENToken to authenticate with the hub
hub_node_nameMUXD_NODE_NAMEDisplay name for this node in the hub (defaults to hostname)