Skip to main content
Glama

Muster is frozen (2026-11-08). New work happens in Marshal: upstream at ignition-is-go/marshal, and tried first on the Castor-o7/marshal fork. This repository stays as the record of what was built here and the source Marshal's ports come from. Only fixes that keep a running daemon safe will land here.

Hello there!! This project has been forked with special thanks to the team over at ignition-is-go/marshal!

The purpose of Muster is to provide you, a very busy person, with the ability to muster some agent teams into a dynamic development environment. What do I mean by this? Agentic parallelism is rife with token waste and context creep trouble. If you or a loved one has to interact with agents in VMs, or across devices on a single local network; then you've probably experienced how quickly this can become a complicated scenario. Cue muster! Muster the troops by setting up a main 'watchman' agent in a directory of your choice and see the watchman organize the rest of your agent teams, coordinating securely to tackle given design roadmaps. Have you already taken the time to design a thorough roadmap for your catapp, and now you need to go grocery shopping? Great! Give an agent or two your roadmap, using the CLI of your choice; setup a watchman to monitor and organize workflow, and then BAM! You can go take your dog for a walk and come back to some completed stories!

Muster lets Claude Code sessions find each other and talk, on one machine or across your network. A message wakes an idle session by itself, even one nobody has typed to yet, and it does this without --dangerously-load-development-channels.

It's built for teams of agents working through a roadmap together: a session can ask another for help, hand off work, or report that a story is ready for review, while you get on with something else.

  • Live, with no flag. An ordinary Claude Code hook waits for mail and wakes the session when a message arrives. It uses no model turns while waiting.

  • Across machines. One daemon serves every paired machine over TLS, with its certificate pinned and a revocable token for each machine.

  • Durable. With Postgres, or with MUSTER_PERSIST=file and no database, messages and the roster survive a daemon restart. Messages to a session that has ended are eventually deleted with it.

  • Built on myko, in Rust. New code is Rust. Codex is supported through the same shim, and Pi, opencode and Prime Agent have plugins inherited from Marshal that aren't being actively developed.

Muster is for one operator's own machines, on a private network or over Tailscale or WireGuard: pair only machines you trust. SECURITY.md describes its security model.

See the roadmap for where this is heading and the board for how the teams work together. Muster carries the messages and doesn't track work; Rally is the separate ledger of each team's progress.

Muster is a fork of Marshal by ignition-is-go, used under its MIT OR Apache-2.0 licence, and it can run side by side with Marshal: it has its own binaries (muster-shim, muster-daemon), ports (6255, 6256 and 6258), MUSTER_* variables and ~/.config/muster. Issue and PR numbers in older comments and docs (#139 and so on) refer to the Marshal repository.

Install

Install the binaries, run the daemon, then wire the harnesses you use.

Or let scripts/setup.sh (MUS-98) do the first steps in one command. It installs the binaries into one place, ~/.local/bin (--prefix to change it), from the latest release checked against its SHA256SUMS and that against the release key's signature (MUS-110), or with cargo install --locked of the release's tag while there's no release or no release key yet, or with --from-source (--tag or --rev to pick, MUS-111). With --daemon it then runs muster-daemon service for the unit that keeps the daemon running across reboots (--tls for the gate). It always runs muster-shim setup to pair and wire Claude Code, which ends with muster-shim doctor. Like those two, it's a dry run until you add --apply, and it never passes --force, so a unit that differs from the one service would write is left for you to look at. It never starts or restarts the daemon: service prints the commands for that. Give a pairing code with --pair-file PATH, or --pair-file - on stdin, never as an argument.

scripts/setup.sh --daemon --tls            # see what it would do on the daemon host
scripts/setup.sh --daemon --tls --apply    # then do it
scripts/setup.sh --pair-file code.txt --apply   # a machine that only runs sessions

1. Install the binaries

Muster isn't published to crates.io; install from the repository (or from a local checkout with --path crates/shim and so on):

cargo install --locked --git https://github.com/Castor-o7/muster muster-shim muster-daemon
# optional but recommended for visibility into the live roster
cargo install --locked --git https://github.com/Castor-o7/muster muster-tui

This puts muster-shim and muster-daemon on your PATH (typically ~/.cargo/bin). --locked builds the dependency versions the repository was tested with. Without it, cargo picks the newest compatible ones. While installing both, cargo prints patch myko-server … was not used in the crate graph. That's expected: muster-shim doesn't use myko-server, and muster-daemon is still built with the patched copy.

On a machine that only runs sessions (not the daemon), muster-shim setup does steps 2b and 3 in one go: it pairs, then installs the plugin through Claude Code's own claude plugin commands. Without --apply it only says what it would do. Save the pairing code to a file and pass it with --pair-file PATH, or pipe it in with --pair-file -, so it never lands in shell history or ps. The code is single-use, so delete the file afterwards. The plugin installs from Castor-o7/muster's current main, not from a pinned release. Wiring you've made by hand is left alone. --apply backs up settings.json first, and ends with the doctor's checks.

Once you've finished the steps below, muster-shim doctor checks this machine's setup. It checks the binary on PATH, the pairing or local token, a handshake with the daemon, and Claude Code's MCP server and hooks. It prints a fix for each problem, and changes nothing.

2. Start the daemon

The daemon runs out-of-band — one daemon that every session's shim connects to (on the same machine, or reachable over the network), lifetime independent of any Claude Code session. Pick whichever fits:

# foreground in its own terminal
muster-daemon

# or backgrounded under your shell job control
muster-daemon &

# or under your favorite supervisor (systemd user unit, launchd, tmux pane, ...)

To keep it running across reboots, muster-daemon service prints the systemd user unit (Linux) or launchd agent (macOS) for the installed binary. Add --tls for a gated daemon. --apply writes the unit and prints the command to start it, and never starts anything itself. It refuses to replace a different unit that's already there, since that one may hold settings of its own such as MYKO_POSTGRES_URL. With --force it replaces it, after saving the old file next to it, and carries the old unit's environment over (Environment=, EnvironmentFile= and launchd's EnvironmentVariables), along with its ordering (After=, Wants=, Requires=). It lists each one it carries. Any other old setting it would lose, such as a different binary or User=, is named, and the replacement is refused unless --drop-settings is also given. systemd drop-ins (muster-daemon.service.d/) are separate files, and stay as they are.

To stop it, send SIGTERM (what systemctl stop and restart do) or press Ctrl-C. Held waits end with an empty reply marked as a restart, so each session's waiter backs off quietly and reconnects when the daemon is back.

To roll a commit on main out to a systemd-managed daemon, use scripts/rollout.sh. rollout.sh preflight <commit> builds it from a fresh tree, runs the gate end-to-end test, stages the binaries with checksums and runs scripts/restart-preview.sh, without changing anything; that's where the operator sees what the restart will remove, since apply doesn't stop for it. rollout.sh apply <commit> --operator-approved backs up the binaries, unit and database (test-restoring the dump), installs and restarts, checks the unit and its three ports, confirms the running process is the staged binary, and rolls back to the previous binary if any of that fails. --operator-approved records that the operator agreed; it doesn't prove it, since any session can type it, so a run is never itself the operator's approval. The health check runs once, HEALTH_WAIT (15 s) after the restart, so it catches a daemon that fails at start but not one that fails later: keep watching the log for a few minutes. The script needs Linux with a systemd user unit. scripts/test-rollout.sh checks the rollback against a throwaway unit.

The daemon runs in one of two modes (MUS-64).

Plain (the default), as upstream Marshal runs. myko serves WebSocket and MCP directly on MUSTER_BIND, and the hook listener takes requests without a token. There's no TLS, pairing, token or limit; the daemon is protected only by where it's bound, so keep it on loopback or a network you trust. The startup line says TLS gate: off.

Port

Variable

Default

Serves

6255

MUSTER_BIND

127.0.0.1:6255

myko's own WebSocket/MCP listener

6256

MUSTER_HOOK_BIND

127.0.0.1:6256

The plain-HTTP hook listener; refuses browser origins

TLS (muster-daemon --tls, or MUSTER_TLS=1). Everything below: the gate, pairing, tokens, the frame filter, the limits and the sender allowlist. The startup line says TLS gate: on. If --tls and MUSTER_TLS disagree (--tls with MUSTER_TLS=0), the daemon refuses to start. It also refuses to run plain while its state directory has paired machines, unless MUSTER_TLS=0 says so, so a unit file that loses the setting can't quietly open a gated daemon. Turning TLS off leaves the certificate, tokens and pairings on disk, so turning it back on restores them.

Port

Variable

Default

Serves

6255

MUSTER_BIND

127.0.0.1:6255

The gate. TLS connections must present a per-machine access token; plain HTTP from the daemon's own host (loopback) must present the local token; plain HTTP from another machine gets 426 and is told to pair

6256

MUSTER_HOOK_BIND

127.0.0.1:6256

The plain-HTTP hook listener; requires the local token, refuses browser origins

6258

MUSTER_MYKO_BIND

127.0.0.1:6258

myko's own WebSocket/MCP listener, behind the gate; admits only what a gate forwards, refuses browser origins

To accept sessions from other machines, turn TLS on, set MUSTER_BIND=0.0.0.0:6255 (or a LAN/mesh address), and pair each machine (below). Tools running on the daemon's own host, like the shim and the TUI, connect to ws://localhost:6255 in either mode. With TLS on they add the local token: the daemon writes it owner-only to ~/.config/muster/local-token (under $XDG_CONFIG_HOME if set) on first start, and the shim and TUI read it from there. A process that can't read that file gets 401, and any request naming a browser Origin gets 403, on the hook listener, the gate's local path, and myko's own listener. myko's listener admits only requests a gate forwarded, through a request filter in the patched myko-server under vendor/myko-server (MUS-59). Loopback isn't the trust boundary: a reverse proxy on the host forwards from loopback too. The local token is. The web UI needs TLS off for now.

Limits (MUS-9), with TLS on. Each session may send a burst of 20 messages, then 30 a minute; past that, a send gets "too many messages from this session" and says when to retry. A broadcast counts as one. Connections are capped per listener, and a refused connection gets 503:

Variable

Default

Caps

MUSTER_MAX_CONN_PER_IP

128

connections one IP holds on the gate, before and after the token check (past it, the connection is closed)

MUSTER_MAX_CONN_PER_MACHINE

64

connections one paired machine holds on the gate

MUSTER_MAX_CONN_LOCAL

256

connections on the local gate

MUSTER_MAX_CONN_HOOKS

256

connections on the hook listener

The operator's controls (MUS-10). Run these on the daemon's host. pause and resume work in both modes; the sender allowlist applies only with TLS on, since without pairing there are no machine names to check. The running daemon applies a change within 2 seconds.

muster-daemon pause              # no message wakes any session; mail is kept, and seen when someone types
muster-daemon resume
muster-daemon allow a1           # from now on only listed machines may send or broadcast
muster-daemon disallow a1
muster-daemon senders            # show the list; `senders --clear` lets any machine send again
muster-daemon forget mellow-weevil   # remove a stuck session from the roster: id, 8+ char id prefix, or nickname

forget is for a ghost row, one whose session is gone but whose row stays (MUS-105). The running daemon removes the row within 2 seconds and logs which session it removed. For the next 10 minutes it refuses that session's hooks and deletes the row again on every 2-second refresh, so a waiter still polling under the old id can't put it back. A name that matches several sessions removes nothing: nicknames repeat across restarts, so the log lists the candidate ids and you forget one by its id. A session that's still alive registers again after that, and a shim's own connection re-registers at once, so it doesn't remove a live session for good.

The daemon's own host is always allowed, so an allowlist never cuts off the sessions running beside it. Machine names are compared without regard to case. The daemon names its own host the way the shim does, from MUSTER_HOST or the hostname up to its first dot, and senders prints that name, so you can check it matches what the roster shows for sessions there. A session with no machine on its row is refused while a list is set. While wake-ups are paused, muster://roster and muster-shim roster report "wakes_paused": true. These files live in the state directory, so any process on the operator's account can change them; until MUS-39, that's the limit of this control.

The daemon keeps its TLS certificate and the paired machines' token hashes in MUSTER_STATE_DIR (default ~/.local/state/muster). Set MYKO_POSTGRES_URL to keep the roster, rooms and messages across restarts. Without a database, MUSTER_PERSIST=file keeps them in store.jsonl in the state directory instead (MUS-17): a private file, compacted as it grows and once a day, and on macOS excluded from Time Machine. Mail sits in it as plain text, so anything that backs up or syncs the state directory carries your mail with it; full-disk encryption covers a lost machine. Without either, the daemon runs in memory and starts empty each time. The startup line says which. See docs/local-persistence-design.md.

A link joins two daemons, each keeping its own roster, store and controls. A session on one can message a session on the other, both ways (MUS-66; see the design). One daemon dials the other's gate, so only that one needs to be reachable. Both must run with TLS on. On the daemon that will be dialed:

muster-daemon pair --name a1 --link --address murph.example:6255

It prints a one-time muster-daemon link <code> command, valid for 15 minutes. Run it on the other daemon's host, in a shell with MUSTER_TLS=1. The running daemon connects within a few seconds. muster-daemon links lists links both ways, and unlink NAME ends one from either side: the dialer stops dialing, and the dialed side revokes its token. unpair --link NAME also revokes one on the dialed side; a bare unpair NAME that names both a paired machine and a link is refused, so revoking a link never cuts off a machine. The far daemon's sessions appear on muster://roster under remote_sessions, each with an address like fuzzy-otter@murph. Send to that address to reach one; a bare name prefers a local session. Mail from a linked daemon arrives labelled the same way. A link shares each session's nickname, host, operator, project, current_task and activity, but never its cwd, pid or branch. Mail for a link that is down waits in the outbox, which survives a restart only with Postgres or MUSTER_PERSIST=file, and is refused back to its senders after a day.

2b. Pair each machine

Pairing needs TLS on. Every machine other than the daemon's own host needs a pairing. (On the daemon's host, an unpaired shim connects to ws://localhost:6255 with the local token, so it works without one. Pair it too if other people can log in to that machine.) On the daemon's host:

muster-daemon pair --name a1 --address 192.0.2.10:6255

It prints a muster-shim pair muster-pair:v2:… command. Run that on the machine you're pairing, within 15 minutes. To keep the code out of shell history, save it to a file and run muster-shim pair < file instead, then delete the file. The code holds the gate's address, the certificate fingerprint to pin, and a one-time invite. muster-shim pair exchanges the invite at the gate for that machine's access token and stores the token owner-only in ~/.config/muster/pairing. The code then stops working, and it also stops working once it expires, so a code that ends up in a transcript or a log can't be used later. Then restart that machine's Claude Code sessions.

  • --address is how the paired machine should reach the gate. It can be left out when MUSTER_BIND is a specific address rather than 0.0.0.0.

  • --expires 30s, 15m or 2h sets how long the code works. The default is 15 minutes. A used or expired code gets "the daemon refused this code"; ask for a new one.

  • muster-shim pairing shows a machine's pairing with its token masked.

  • muster-shim pair still accepts a muster-pair:v1: code from an older daemon. Those hold the token itself, so pair again to retire one that has been seen.

  • muster-daemon pairs lists paired machines, and muster-daemon unpair a1 (or unpair --machine a1) revokes one. Its open connections close within seconds and new ones are refused; nothing needs restarting.

  • muster-shim unpair removes a machine's own pairing.

  • Pairing again under the same name replaces that machine's token, so that machine's running sessions lose their connection until they restart. This is also how to rotate a machine's token.

  • If the daemon's state directory is lost, it makes a new certificate, and every machine must pair again: their pinned fingerprint no longer matches.

A paired shim never sends anything unencrypted. myko's client can only trust certificates the operating system trusts, so the shim runs a small loopback tunnel: myko connects to 127.0.0.1, and the tunnel carries the connection over TLS, with the daemon's certificate pinned, to the gate. The hooks make the same pinned TLS connection themselves. Codex, Pi, opencode and Prime Agent clients don't support pairing yet.

2c. Message the muster from a terminal

From any terminal on a paired machine, or on the daemon's own host, outside a Claude session:

muster-shim all "Full agenda today, please send your status."
muster-shim tell catapp "Please pause before the next migration."

all reaches every session on the roster, and tell takes a team name, nickname or session id. Each session gets a direct message, which wakes it if idle, and you get one line per session. An unknown or ambiguous name is refused. The exit status is non-zero if anything wasn't sent.

Recipients see [from a terminal on <machine>; it can't receive replies]. That names the machine, not the person: any process on your account can run this command, including a Claude session, so these messages carry no extra authority.

muster-shim roster prints the roster as JSON for other programs. It only reads: it registers no session and doesn't appear on the roster. Each session has observed facts only the daemon can set (whether the row is bound to a connection, whether the daemon sees it holding a wait, and until when), attested ones the gate writes for a paired machine (machine and operator; a session on the daemon's own host only claims these, and the row doesn't say which case it is), and declared ones its own software reports, which a machine can set to anything for its own sessions. Those include last_activity_at, activity and current_task. This holds against a paired machine: a process on the daemon's own host can write anything until MUS-7. wake is described under muster://roster. bound_to_connection can stay true after a connection drops, until the daemon reaps the row. It needs a daemon with MUS-4. Any process with this machine's pairing can read it.

A message from all or tell is never the operator's approval. It doesn't count as a direct OK for a daemon restart or anything else that needs one, because a session can send the same message.

3. Add the plugin in Claude Code

/plugin marketplace add Castor-o7/muster
/plugin install muster-shim@muster

Restart the session. From here on, every Claude Code instance pointed at the daemon sees the muster MCP server and can talk to its peers.

If you'd rather wire MCP up by hand, the plugin is just shorthand for adding an mcpServers entry to one of Claude Code's MCP config files. Pick the scope that fits:

Project scope — write a .mcp.json at the repo root (commit it to share with collaborators):

{
  "mcpServers": {
    "muster": { "command": "muster-shim" }
  }
}

User scope — applies to every Claude Code session for your user. Add a top-level mcpServers entry to ~/.claude.json:

{
  "mcpServers": {
    "muster": { "command": "muster-shim" }
  }
}

Or let the CLI do it for you:

claude mcp add -s user muster muster-shim

Note: mcpServers is not a supported key in ~/.claude/settings.json — adding it there silently does nothing. It must live in .mcp.json (project) or ~/.claude.json (user).

4. How messages reach Claude Code

Muster delivers direct messages through Claude Code hooks; no launch flag is needed. The plugin's hooks/hooks.json runs muster-shim claude-hook at session start, before each prompt, and after each tool call. Each run pulls this session's unread direct messages into the turn as a <muster_inbox> block, so a busy session sees new messages between tool calls rather than waiting for its next prompt.

An idle session wakes by itself, even if nobody has typed to it yet. muster-shim claude-hook wait runs as an asyncRewake hook at session start and after every turn. It waits for mail, and when a message arrives it exits with code 2, which makes Claude Code start a turn. It uses no model turns while waiting, and it exits when its session does.

If you wired the MCP server by hand (step 3) instead of installing the plugin, add the hooks to ~/.claude/settings.json yourself:

{
  "hooks": {
    "SessionStart": [
      { "hooks": [
        { "type": "command", "command": "muster-shim claude-hook session-start", "timeout": 10 },
        { "type": "command", "command": "muster-shim claude-hook wait", "asyncRewake": true, "timeout": 2592000 }
      ] }
    ],
    "Stop": [
      { "hooks": [{ "type": "command", "command": "muster-shim claude-hook wait", "asyncRewake": true, "timeout": 2592000 }] }
    ],
    "UserPromptSubmit": [
      { "hooks": [{ "type": "command", "command": "muster-shim claude-hook prompt-submit", "timeout": 10 }] }
    ],
    "PostToolUse": [
      { "hooks": [{ "type": "command", "command": "muster-shim claude-hook post-tool-use", "timeout": 10 }] }
    ]
  }
}

To see each session's nickname, the name peers address it by, in Claude Code's status line, add this too (muster-shim setup does it for you). If you already have a statusLine, muster-shim setup prints a command that runs yours and Muster's side by side:

{ "statusLine": { "type": "command", "command": "muster-shim statusline" } }

The hooks find the daemon from this machine's pairing (step 2b), the same way the shim does. If the daemon can't be reached, the hook prints nothing and gives up within about two seconds, so it never blocks a turn.

The channels flag

You don't need --dangerously-load-development-channels, and we don't recommend it: the hooks above already wake idle sessions. The shim still supports Claude Code's experimental notifications/claude/channel push if you launch with that flag, and leaves messages for the hooks when you don't.

Pi: native extension

Pi, Prime Agent and opencode connect over plain WebSocket, so they need to run on the daemon's own host, using ws://localhost:6255. They can't use the TLS gate on 6255 yet, and these plugins aren't published to npm, so install from a checkout of this repository.

After starting a reachable daemon, install the muster-pi package from your checkout:

pi install ./plugins/muster-pi

Restart Pi. The extension registers the session on the Muster roster, adds native muster_* tools, and subscribes to direct-message pushes in-process. Incoming messages are inserted into the visible transcript as steering input and start a turn when the recipient is idle. A durable inbox pull before each turn recovers messages received while disconnected.

No MCP shim or launcher wrapper is required. The default address is ws://localhost:6255. To set it explicitly:

export MUSTER_DAEMON_ADDRESS=ws://localhost:6255
pi

Use /muster-status in Pi to verify the connection. See plugins/muster-pi/README.md for the complete tool, configuration, and development reference.

Prime Agent: live conversation bridge

Install the Prime Agent extension package after starting a reachable daemon:

prime-agent package install ./plugins/muster-prime-agent

Restart Prime Agent. The extension registers each top-level conversation on the roster, adds native muster_* tools, and subscribes to direct-message pushes in-process. RLM sub-agents remain part of their parent's Muster session instead of appearing as separate roster entries. An incoming message is inserted into the visible transcript with Prime Agent's extension API and delivered as steering input; it starts a turn immediately when the recipient is idle. Messages received while disconnected are pulled from the durable inbox before the next turn.

This is a dedicated Prime Agent package, separate from muster-pi; no MCP shim or launcher wrapper is needed. The default address is ws://localhost:6255. To set it explicitly:

export MUSTER_DAEMON_ADDRESS=ws://localhost:6255
prime-agent

Use /muster-status in Prime Agent to verify the connection. See plugins/muster-prime-agent/README.md for tools, development loading, and delivery details.

Codex: local setup without Ansible

These steps give a laptop, workstation, or manually managed VM the same Muster behavior as an infrastructure-managed host. You need a reachable muster-daemon on the same host, from step 2 above: Codex can't use the TLS gate yet, so it can't reach a daemon on another machine (MUS-18). Install the Codex CLI itself first and confirm that codex is on PATH.

1. Install and wire the shim

Build muster-shim from source, then run setup. It adds the Muster MCP server and lifecycle hooks to ~/.codex/config.toml, seeds the coordination instructions in ~/.codex/AGENTS.md, and pre-trusts the hooks. This fork doesn't publish release binaries yet (MUS-14). Setup is idempotent, so rerun it to refresh the hooks or address.

cargo install --git https://github.com/Castor-o7/muster muster-shim
muster-shim codex-setup --daemon ws://localhost:6255

A normal codex launch now gets the Muster tools and pulls direct messages at prompt and tool boundaries. For a message to start a turn while Codex is idle, the interactive CLI must run through:

muster-shim codex-run [CODEX_ARGS...]

On Linux and macOS this attaches the TUI to Codex's managed app-server. On native Windows it supervises a per-TUI app-server on an ephemeral loopback-only port. In both cases a local bridge can call turn/start, while the normal hook remains responsible for injecting and acknowledging the durable inbox message. The launcher waits for the bridge's lifecycle subscription before attaching the TUI, so thread/started registers new and resumed sessions on the Muster roster before their first prompt. This registration-only path never consumes inbox messages; lifecycle hooks still own context injection and acknowledgement. After Codex or the user names the selected thread, the bridge prefixes the title with its stable Muster handle, for example [sunny-summit] Fix reconnect handling. Later automatic titles and user renames keep the handle and preserve the new title. The bridge does not name an empty thread, because doing so would prevent Codex from generating its normal title. When several Unix TUIs share the managed app-server, their bridges elect one wake leader; every bridge still observes lifecycle events, and leadership fails over automatically when a launcher exits. Windows app-servers are per-TUI, so their bridges do not share wake ownership.

2. Make idle wake the default for interactive Codex

Codex does not currently expose the shared app-server choice as a config.toml setting, so make codex-run the interactive shell launcher. Administrative and non-interactive commands must continue to call the real Codex binary.

For Bash, add the following to ~/.bashrc. For Zsh, add it to ~/.zshrc and replace type -P codex with whence -p codex:

export MUSTER_CODEX_BIN="$(type -P codex)"

codex() {
    local arg direct=0
    for arg in "$@"; do
        case "$arg" in
            exec|e|review|login|logout|mcp|plugin|mcp-server|app-server|remote-control|completion|update|doctor|sandbox|debug|apply|a|archive|delete|unarchive|cloud|exec-server|features|help|-h|--help|-V|--version|--remote|--remote=*)
                direct=1
                ;;
        esac
    done

    if [ "$direct" -eq 1 ]; then
        "$MUSTER_CODEX_BIN" "$@"
    else
        "$HOME/.local/bin/muster-shim" codex-run "$@"
    fi
}

For Windows PowerShell, add this to $PROFILE.CurrentUserAllHosts after opening a fresh shell:

$script:MusterCodexBin = (Get-Command codex -CommandType Application).Source
$script:MusterShimBin = (Get-Command muster-shim -CommandType Application).Source

function global:codex {
    $directCommands = @(
        'exec', 'e', 'review', 'login', 'logout', 'mcp', 'plugin',
        'mcp-server', 'app-server', 'remote-control', 'completion', 'update',
        'doctor', 'sandbox', 'debug', 'apply', 'a', 'archive', 'delete',
        'unarchive', 'cloud', 'exec-server', 'features', 'help', '-h',
        '--help', '-V', '--version'
    )
    $direct = $false
    foreach ($argument in $args) {
        $value = [string]$argument
        if (
            $directCommands -contains $value -or
            $value -eq '--remote' -or
            $value.StartsWith('--remote=')
        ) {
            $direct = $true
            break
        }
    }

    if ($direct) {
        & $script:MusterCodexBin @args
        return
    }

    $previous = $env:MUSTER_CODEX_BIN
    try {
        $env:MUSTER_CODEX_BIN = $script:MusterCodexBin
        & $script:MusterShimBin codex-run @args
    } finally {
        $env:MUSTER_CODEX_BIN = $previous
    }
}

Start a new shell, then run plain codex or codex resume. An interactive session should have both a muster-shim codex-run launcher and a muster-shim codex-bridge process, and it should appear in the Muster roster as soon as the TUI opens—even before its first prompt. Commands such as codex exec, codex app-server, and codex --version continue to bypass the launcher. See docs/codex-live-delivery.md for the delivery and trust model.

Related MCP server: claude-multi-peer

What you get

Reads are MCP resources; writes are MCP tools. Every session has a daemon-assigned adjective-noun nickname that stays stable for that session. Address peers by nickname, full or unique-prefix session id, or operator identity when you mean to reach the human through their most-active agent.

Resources (resources/read):

Resource

Contents

muster://whoami

This session's { session_id, pid, cwd, operator, host }.

muster://roster

Every live session: cwd, git branch, current task, last activity, room membership, and wake: waiting (observed: the daemon sees it holding a wait, so a message wakes it), channel or busy (declared: only what the session reports; a Claude Code session waits again when its turn ends), or none (no sign a message would wake it).

muster://rooms

Every room and its members.

muster://messages

Message history. Query params: inbox, sent, unread, room, from, to_session, since, limit. On a direct message you sent, recipient says whether its recipient is still on the roster and whether it has read it, so sent=true shows mail that went unread when its recipient was reaped. That mail is kept for 14 days in case the session returns.

Tools (tools/call):

Tool

Effect

send_message(to, body)

Interrupt one peer by nickname, session id/prefix, or operator identity.

broadcast(to_room, body)

Ambient room update; an @mention also directly interrupts that peer.

join_room(name) / leave_room(room)

Create/join or leave an ad-hoc room.

set_status(text)

Update this session's free-form status (the current_task field on the roster).

ack_messages(message_ids)

Mark message ids read for this session.

A peer's send_message is persisted first, then Muster attempts a synchronous live-channel push when the recipient supports one. The result reports these facts separately: persisted, live_push, and wake. Wake bridges run after the send response, so wake: unobserved does not mean wake failed. Unknown recipient or sender identities fail loudly; an offline recipient is a successful durable inbox delivery.

Direct messages start or enter a recipient turn, so they consume transcript context. Batch related details and reserve direct delivery for action, a blocker, or a needed reply. Use an unmentioned room broadcast for FYI/progress. Automatic body previews are bounded (2,000 characters per live push; a hook shows up to 6,000 of one message within an 8,000-character batch budget); the complete durable message remains available through muster://messages. Codex wakes coalesce for 30 seconds so a burst can join the active turn.

Architecture

Claude Code ── muster-shim (MCP + hooks) ──┐        paired machines:
Codex ──────── muster-shim hooks/bridge ───┤        TLS gate on :6255
opencode ───── muster-opencode plugin ─────┼─────►  or, on the daemon's own host:
Pi ─────────── muster-pi extension ────────┤        daemon :6255 and hooks :6256
Prime Agent ── muster-prime-agent ─────────┘                 │
                                                       muster-daemon
                                                  (roster + messages, in memory
                                                   or in Postgres)
  • muster-daemon owns the roster and messages. One daemon serves every session that points at it, local or remote. It keeps them in Postgres when MYKO_POSTGRES_URL is set, in a file in its state directory with MUSTER_PERSIST=file, and otherwise in memory. Its gate is the only listener meant for other machines: it checks each machine's token and filters what that machine can send.

  • muster-shim is the per-session stdio MCP server Claude Code spawns, and also the command its hooks run. It announces the session to the daemon, and muster-shim claude-hook wait wakes an idle session when mail arrives.

  • muster-pi is the in-process Pi extension that registers sessions, exposes native tools, and injects direct messages as steering input.

  • muster-prime-agent is the in-process Prime Agent extension that registers conversations, exposes tools, and injects live direct messages.

  • muster-opencode provides the equivalent in-process opencode integration.

  • muster-tui (optional) is a live ratatui dashboard of the roster + recent messages.

Configuring the daemon address

A paired machine finds the daemon from its pairing (step 2b), so most setups need nothing here. An unpaired shim on the daemon's own host defaults to ws://localhost:6255. To point an unpaired client elsewhere, set MUSTER_DAEMON_ADDRESS in the shell that launches the harness.

Set MUSTER_OPERATOR on the daemon to the operator it serves. Sessions from paired machines then show that operator and their pairing name as host, whatever they claim. On the daemon's own host, a session's claim is taken as given.

The gate's bind address is set with MUSTER_BIND. It defaults to 127.0.0.1:6255, so set it to a LAN or mesh address, or 0.0.0.0:6255, to accept other machines.

Workspace layout

Component

Description

muster-entities

Shared entity types and the SendMessage server command.

muster-daemon

Coordination daemon — roster store, event log, sweeper.

muster-shim

Stdio MCP shim that bridges Claude Code to the daemon.

muster-tui

Live terminal dashboard.

muster-ui

Leptos operator dashboard (build target only — not published).

muster-pi

Pi native session extension.

muster-prime-agent

Prime Agent live-conversation extension.

muster-opencode

opencode native plugin.

Muster has no published release or packages yet; install from the repository (see Install). .github/workflows/release.yml builds muster-shim and muster-daemon for macOS arm64 and Linux x86_64 with a SHA256SUMS (MUS-14), and scripts/install-codex.sh checks the shim against that file before running it (MUS-96). That file is signed by the release key in release/allowed_signers (MUS-110), and both installers check the signature before trusting any checksum, so a release whose assets were replaced is refused too. CI holds no key: the operator signs each draft release with scripts/sign-release.sh. Until the operator adds the key, there is none, and no release verifies. CI (.github/workflows/ci.yml) runs fmt, clippy, the tests and scripts/test-gate-e2e.sh on every push and pull request to main.

Status

Muster runs day to day between a macOS and a Linux machine, with the daemon on the Linux one. Sessions come through an overnight laptop sleep and are back on the roster within seconds of it waking. Next on the roadmap: a message waking a session that has been idle overnight (MUS-1), a session's client reconnecting after its connection drops, an install on a clean machine, and Windows beyond a cross-compile.

License

MIT OR Apache-2.0, at your option (LICENSE-MIT, LICENSE-APACHE). Muster is a fork of Marshal, under the same licence.

That covers Muster's own source code. Muster depends on myko, myko-macros and autosocket (and myko-server for muster-daemon), which declare the AGPL-3.0-or-later licence. Anyone who distributes or runs binaries built from Muster should assess the licence of the combined work. This isn't legal advice.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    Related MCP Connectors

    Related MCP Servers