Skip to content
mu-crewPublic

About

tmux, ssh and agent-harness config that wires mu, murmur and mule together

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

29 Commits

Folders and files

Repository files navigation

mu-crew dotfiles

tmux, ssh and harness config that makes mu, murmur and mule work as one setup: agent state in your tab bar and pane borders, a status pill, keys for the picker, side panel and dash, and the hooks that clear an agent's badge when you look at it.

It adds wiring, not a theme. Your status line, colours and other keys stay yours; you place the pieces where you want them.

Requirements

  • tmux 3.3 or newer.
  • murmur, for anything agent-related. Without it the keys print "murmur is not installed" and the formats stay empty.
  • A Nerd Font for the default glyphs. No Nerd Font? See Glyphs.
  • fzf for the workstream picker, Python 3.11 or newer for the poller.

Install

Clone it anywhere and source it from ~/.tmux.conf:

git clone https://github.com/mu-crew/dotfiles ~/.local/share/mu-crew-dotfiles
source-file ~/.local/share/mu-crew-dotfiles/tmux/mu-crew.conf

# Place the pieces:
set -ag status-right '#{E:@mu_crew_pill}'
set -g window-status-format ' #I #W#{E:@mu_crew_window_glyph} '
set -g window-status-current-format ' #I #W#{E:@mu_crew_window_glyph} '

Pin a commit if you manage dotfiles declaratively, and move it forward when you update murmur: both sides read the same tmux options.

What sourcing it does

Each part below is on by default. Set its flag to off before sourcing to skip it; sourcing again with a flag off removes what that part set.

Flag Part
@mu_crew_hooks Focus hooks
@mu_crew_keys a / C-m / G / u
@mu_crew_session_keys Forwarding s / g / T to tsesh
@mu_crew_pane_border Pane border format
@mu_crew_probes Poller probes; none runs no poller (see Poller and probes)
set -g @mu_crew_keys off
set -g @mu_crew_pane_border off
source-file ~/.local/share/mu-crew-dotfiles/tmux/mu-crew.conf

The formats (pill, glyphs, pills from probes) need no flag: they do nothing until you place them.

Focus hooks. Selecting an agent's pane, window or session runs murmur clear for that pane, which acknowledges a done or blocked badge. Without these a badge never clears. Clear runs only when the pane, its window or its session shows done, blocked, error or crashed, so a switch with no badge in sight starts no process. They use hook index 42, so hooks you set yourself are kept.

Keys, under the prefix:

Key Does Option
a Agent picker popup, across every machine murmur peers with @mu_crew_key_pick
C-m Toggle murmur's side panel in this window @mu_crew_key_sidepanel
G Toggle the murmur dash: go to it (opening one if none runs), or back from it @mu_crew_key_dash
u Toggle between a workspace session and its mu workstream session (mu-<name>) @mu_crew_key_workstream
s / g / T tsesh: session picker, last session, session at the pane's directory @mu_crew_key_session_{pick,last,root}

Set an option before sourcing to move a key, or set it to none to leave it unbound. The session keys are passed on to tsesh's own options, and tsesh binds them, so source this file before TPM runs and nothing is bound twice. Most terminals send the same code for C-m and Enter, so prefix Enter opens the side panel too.

Pane border. mu titles each agent's pane with the agent name and its tasks; the border shows that title plus murmur's live state. To keep your own pane-border-format, set @mu_crew_pane_border off before sourcing and add #{E:@mu_crew_pane_glyph} to your format.

Poller. One background Python process per tmux server keeps the enabled remote, memory, CPU, uptime, and muzzle options current. See Poller and probes.

Formats you place

Option Shows Reads
@mu_crew_pill Robot icon, then <count><glyph> per state, most urgent first, then the crew total. Empty when no agents exist. @murmur_count_*, @mu_crew_remote_*
@mu_crew_window_glyph One glyph for the window's most urgent agent state; the idle glyph for an agent window with no news @murmur_window_state
@mu_crew_pane_glyph · <glyph> for the pane's own state @murmur_pane_state
@mu_crew_muzzle_pill This host's muzzle slots: held/budget, then queued jobs, slow jobs (past their usual run time) and jobs queued 30 s or more, each hidden at zero. Empty when nothing holds or waits. Needs the muzzle probe. @mu_crew_muzzle_*
@mu_crew_mem_pill Memory use or macOS memory pressure @mu_crew_mem
@mu_crew_cpu_pill CPU use @mu_crew_cpu
@mu_crew_uptime Compact system uptime @mu_crew_uptime_*
@mu_crew_host_color This host's accent hex, the color murmur gives it everywhere. A value, not a format: use it in a style, #[bg=#{@mu_crew_host_color}] set once at source time

Read them through #{E:...} so the colour runs inside apply. They set no background, so they take the background of wherever you place them.

murmur also sets @murmur_session_state on each session. Session pickers can colour rows from it; tsesh does.

Glyphs

The defaults are Nerd Font glyphs, the same ones murmur dash draws, so one agent looks the same in every surface:

State Glyph Codepoint
crashed fa-times-circle U+F057
blocked fa-comment U+F075
done fa-check-circle U+F058
working fa-play U+F04B
waiting fa-hourglass-half U+F252
idle fa-moon-o U+F186
crew fa-users U+F0C0
agent md-robot U+F06A9

Without a Nerd Font these render as boxes. Set @mu_crew_ascii 1 before sourcing to use x ! + > - c ai instead.

To change one glyph or colour, set its option after sourcing: @mu_crew_g_<state> for glyphs and @mu_crew_c_<state> for colours (Catppuccin Mocha by default). The defaults sit between THEME BEGIN / THEME END markers, so a theme generator can rewrite them.

Poller and probes

The poller defaults to remote murmur counts, so an existing setup does not start new system probes. To enable more probes, set the space-separated list before sourcing the config:

set -g @mu_crew_probes "murmur_remote memory cpu uptime"
source-file ~/.local/share/mu-crew-dotfiles/tmux/mu-crew.conf

set -ag status-right '#{E:@mu_crew_mem_pill}'
set -ag status-right '#{E:@mu_crew_cpu_pill}'
set -ag status-right '#{E:@mu_crew_uptime}'

Each probe has its own interval. Set @mu_crew_<probe>_interval in seconds to change it. The defaults are 10 seconds for murmur_remote, 5 seconds for memory, cpu and muzzle, and 60 seconds for uptime.

The muzzle probe runs muzzle status --json (muzzle 0.2.3 for the slow counts; an older muzzle shows none) and writes @mu_crew_muzzle_*. muzzle decides what is slow. A failed call, or no muzzle on PATH, clears the options; the probe tries again on its next tick. Place #{E:@mu_crew_muzzle_pill} next to @mu_crew_pill.

Set @mu_crew_mem_min, @mu_crew_cpu_min, or @mu_crew_uptime_min to show a format only above that value. Memory and CPU thresholds are percentages. The uptime threshold is seconds. The formats use @mu_crew_c_mem, @mu_crew_c_cpu, and @mu_crew_c_uptime for their colours.

Linux memory reports used memory from /proc/meminfo. macOS memory reports a pressure score from vm_stat and memory_pressure, including compression and swap rates. CPU uses /proc/stat deltas on Linux and top on macOS.

The pid file is keyed by the tmux socket path. The loop exits when no tmux server answers on that socket; a server restarted on the same socket keeps it. Run tmux/scripts/mu-crew-poller --once to update enabled probes once for tests or debugging.

Why the pill costs nothing

tmux redraws the status line on every pane, window and session event, not only every status-interval. A 14-window session was measured redrawing 1.2 times a second. Anything a status line runs through #(...) runs that often, and murmur status is a Node start: as a #(...) it cost most of a CPU core.

So nothing here runs on a redraw:

  • murmur writes this host's counts into @murmur_count_<state> whenever an agent's state changes. The pill is tmux format arithmetic over those options.
  • Remote peers' counts come from tmux/scripts/mu-crew-poller: one loop per tmux server, started when the config is sourced. It runs murmur status --json every 10 seconds and writes @mu_crew_remote_<state> through tmux options. No peers or no murmur stops that probe without stopping other probes.

Keep it that way when you extend the config. A new format reads options; if it needs data from a program, a long-lived loop writes the data into an option. tests/test.sh fails if a format in mu-crew.conf contains #(.

ssh

ssh/mu-crew.conf is an Include for ~/.ssh/config:

  • a shared ControlMaster, so murmur can collect from a host that needs a second factor after you log in once;
  • what to do on a host with MaxSessions 1, where murmur, mule and your shell must not share one master;
  • mule's own socket, so long jobs never take murmur's slot.

Background: murmur's SSH.md.

Host colors

murmur gives every host one accent color: crc32 of its short hostname over eight Catppuccin accents. The dash, side panel and picker paint the host in it, and the notification hook gets it as MURMUR_HOST_COLOR. Sourcing mu-crew.conf runs tmux/scripts/mu-crew-host-color once and stores this host's accent in @mu_crew_host_color, so your status line can paint the host in the same color:

set -ag status-right '#[fg=#11111b,bg=#{@mu_crew_host_color},bold] #H #[default]'

The palette list and the hash are mirrored in murmur's src/dash-paint.ts; both sides pin the same value in a test.

Notifications

murmur/on-attention is a notification hook for murmur, which runs ~/.config/murmur/on-attention once for each new done, blocked, error or crashed. Link it there, or exec it from a hook of your own:

ln -s ~/.local/share/mu-crew-dotfiles/murmur/on-attention ~/.config/murmur/on-attention
OS Sends with Urgency
Linux notify-send -a murmur done is normal; blocked, error and crashed are critical
macOS terminal-notifier with the icon, else osascript none
  • Each notification carries a 128px icon: a rounded frame in the host's accent with the event kind's glyph in its color, so you can tell the host and the severity apart at a glance. murmur supplies all three (MURMUR_HOST_COLOR, MURMUR_GLYPH, MURMUR_KIND_COLOR). The host name in the body is in the same accent, where the notifier renders markup.
  • Icons render once with ImageMagick (magick) and a Nerd Font, and are cached in ~/.cache/murmur/. Without magick, a Nerd Font, or a murmur that sets the colors, the notification goes out without an icon.
  • A done from a crew (orchestrated) agent is skipped: the orchestrator reads it.
  • MURMUR_ICON_BASE overrides the icon's inner fill (default #1e1e2e) and MURMUR_ICON_FONT its font (default JetBrainsMono Nerd Font).
  • The notifier may scale icons down. mako's default is 64px; for a larger one: [app-name=murmur] then max-icon-size=72 in the mako config.
  • Test it: MURMUR_KIND=blocked MURMUR_AGENT=test MURMUR_HOST=$(hostname) MURMUR_HOST_COLOR='#cba6f7' MURMUR_GLYPH=$'\uf075' MURMUR_KIND_COLOR='#fab387' murmur/on-attention.

Harness hooks

pi reports to murmur, mu and muzzle from inside the agent, through their pi packages. Claude Code, Codex, the Cursor CLI and Muse Code report through hooks in their own config files, and three commands write those:

murmur init   # agent state: sessions, prompts, permission prompts, turns, interrupts
mu init       # keep-driving: one Stop hook (not for Muse Code), and mu's skill
muzzle init   # queueing: a hook before each shell call

Each writes for every harness whose config directory exists; --hooks <list>|all (murmur, muzzle) or --harness <list>|all (mu) writes others too. The files are shared: each tool touches only handlers whose command starts with its own name, leaves yours where they are, and a second run changes nothing. murmur doctor and muzzle doctor report drift. Codex asks you to trust the new hooks once (/hooks). The agent must run inside tmux.

The files here are what the three write, in that order, into an empty home. They show what lands where; run the inits rather than copying them, since the inits merge into what you already have.

Harness Example Written to
Claude Code claude/settings.json ~/.claude/settings.json
Codex codex/hooks.json ~/.codex/hooks.json
Cursor CLI cursor/hooks.json ~/.cursor/hooks.json
Muse Code muse/settings.json the hooks key of ~/.config/muse/settings.json

CLAUDE_CONFIG_DIR and CODEX_HOME, when set and not empty, move Claude Code's and Codex's files for all three, as they do for the harnesses themselves: $CLAUDE_CONFIG_DIR/settings.json and $CODEX_HOME/hooks.json, with the same contents. The Cursor CLI's and Muse Code's stay under ~.

  • codex/config.toml needs nothing from mu-crew. Remove the older notify = ["murmur", "notify", …] line: with murmur's hooks in, it reports every finished turn twice.
  • Muse Code gets no mu hook stop: a Stop block there does not hand its reason to the model, so a Muse agent keeps driving by the mu skill's rule. murmur's Interrupt handler is async: true, the only way Muse delivers that event.
  • The Cursor CLI's mu hook stop lifts its loop_limit; mu caps its own follow-ups (MU_KEEP_DRIVING_MAX).
  • mu init also copies the skills it ships, mu among them, to ~/.claude/skills and ~/.codex/skills (or skills in the moved directories), never ~/.agents/skills. The Cursor CLI reads those two literal paths too, and gets ~/.cursor/skills/mu only when neither holds one. Muse Code installs skills through muse skills.
  • opencode keeps its attention-only plugin, opencode/plugins/murmur/tui.ts, which runs murmur notify for done and blocked, not running, idle or crashes. It is an OpenCode 2 TUI plugin. Install it as ~/.config/opencode/plugins/murmur/tui.ts. It needs a directory of its own, because opencode skips a bare file in plugins/. It also needs @opencode/plugin 2.x installed in ~/.config/opencode.

Tests

tests/test.sh

Runs against an isolated tmux server with a fake murmur on PATH: keys and the none opt-out, the side panel key, glyph defaults and ASCII, the pill and window formats, the cheap-tick rule, probe formats, local probes, remote counting, the host color, and exit codes. python3 tests/test_poller.py covers the parsers and probe calculations; python3 tests/test_host_color.py pins the host color against murmur's.

It first runs tests/test_examples.sh, which checks the harness hook examples against a copy of the three inits' entry tables, then runs murmur init, mu init and muzzle init into an empty temp home and checks that they write the examples byte for byte, and that a home already holding them is left as it is with no warning. It repeats both with CLAUDE_CONFIG_DIR and CODEX_HOME pointing outside the home, where Claude Code's and Codex's examples must land, that with only those two directories there and no harness named the inits find both through the variables, and that set but empty they write under the home as if unset. It also checks that murmur keeps its state in murmur2, never in the 1.x murmur directory. It takes the CLIs from PATH. When one there predates hook entries it skips the second part and prints a SKIP line, repeated at the end of tests/test.sh; with REQUIRE_INITS=1 that skip fails instead. Point MURMUR_CMD, MU_CMD and MUZZLE_CMD at newer builds to run it anyway:

MURMUR_CMD=../murmur/dist/cli.js MU_CMD=../mu/dist/cli.js \
  MUZZLE_CMD=../muzzle/target/debug/muzzle tests/test.sh

Related

  • mu-crew: the org, with the stance and design rules these files follow.
  • tsesh: a session picker that shows murmur's session state.

Part of mu-crew. Written mostly by AI coding agents, with a human reviewing what ships, and built for running them.

About

tmux, ssh and agent-harness config that wires mu, murmur and mule together

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages