Skip to content

Repository files navigation

muster

Muster gives you oversight of every local Claude agent session running on your machine, the way a lead keeps track of a room full of engineers. It surfaces which agents are idle, blocked, or waiting on you, and which are quietly making progress — so your attention goes where it's actually needed instead of getting spread thin across terminals and tabs. Everyone reports in. You decide who needs you next.

It is read-only. Muster observes; it never kills, pauses, resumes, or answers a prompt for a session. The hooks it installs cannot change what a session does.


The spec is the source of truth

This README tells you how to run the thing. It does not explain how it works or why it works that way, because duplicating that here guarantees the two copies drift apart.

You want Read
What Muster must do, and how to verify each claim spec/requirements.md
How it is built — schema, event flow, state machine, heuristics, latency budgets spec/design.md
Why it is built that way, and what was rejected spec/decisions.md
What the hook payloads actually contain, with provenance spec/evidence/spike-findings.md
Implementation status spec/tasks.md

The payload schemas in design.md were built from 184 real payloads captured from Claude Code 2.1.226, not from documentation and not from memory. Where the docs and the real payloads disagreed, the payloads won and the disagreement is recorded.


Install

Requires Node 22.5+ and curl.

npm install
npm run build

node bin/muster-install.mjs --dry-run   # see exactly what would change
node bin/muster-install.mjs             # merge into ~/.claude/settings.json

npm start                               # http://127.0.0.1:4599

The installer merges into ~/.claude/settings.json. It backs the file up first, keeps every hook you already had, and if you already have a statusLine it captures the command and passes its output through byte-for-byte. Re-running it is idempotent.

Uninstall

node bin/muster-uninstall.mjs

Restores your prior configuration exactly — it removes only the entries Muster added and puts back the status line you had, leaving anything you changed after installing alone. A blunt full-file restore is available if you want it:

node bin/muster-uninstall.mjs --restore-backup ~/.claude/settings.json.muster-backup-<stamp>

~/.muster/ (database, config, spool) is left in place. Delete it if you want it gone.


Verify it is working

Are the hooks firing?

Three independent checks, in increasing order of effort:

  1. The dashboard's own diagnosticsSettings → Diagnostics lists every instrumented event with when it was last seen, and explicitly names events it has never seen. This exists because a hook that failed to register looks exactly like a quiet machine. curl -s localhost:4599/api/doctor | jq gives the same thing.

    Some events legitimately read "never": Notification and PermissionRequest need an interactive permission prompt, StopFailure needs a rate limit, TaskCreated needs an agent team. SessionStart, UserPromptSubmit, Stop and PostToolUse should all appear within one session.

  2. /hooks inside an interactive Claude Code session lists the registered entries. Muster's appear as sh …/muster-hook.sh direct|detach.

  3. claude --debug-file /tmp/m.log -p "run echo hi with Bash", then grep the log for hook execution.

Is it slowing your sessions down?

npm run bench          # four arms, 300 iterations each

Measured on Node 22.22 / Linux x64 — full table in design.md §10, raw data in spec/evidence/latency/:

median p95
Added latency your session waits for, per turn 6.43 ms 14.83 ms
Background CPU per tool call (not awaited) 6.1 ms

Two numbers, because they are two different things. Only the three turn-boundary hooks are synchronous, so 6.43 ms is what a turn actually pays. The per-tool-call hooks are fire-and-forget — Claude Code spawns them and moves on — so their cost is real background work but never a delay.

The benchmark's fourth arm points a hook at a server that accepts the connection and never responds. The awaited hook costs 9.27 ms there against a healthy server's 9.26 ms: no measurable difference. That is the whole reason Muster uses command hooks rather than HTTP hooks — an HTTP hook would block for its full timeout on every event, in every session, and a deadlocked dashboard would take your work down with it.


Configure

~/.muster/config.json, created on first run. Everything in it is also editable in the UI's Settings tab and applies without a restart.

Key Default What it does
port 4599 Server port. Loopback only — there is no auth, and the interface is the security boundary.
activityWindowHours 24 How far back a session counts as recently active.
retention.eventDays 30 Raw event history.
staleness.endedAssumedMinutes 90 Silence beyond this with no SessionEnd means presumed ended. Claude Code emits no SessionEnd when a terminal is killed, so without this a dead session would read as "working" forever.
stuck.* see below Every heuristic threshold and weight.
summariser.* see below Model, triggers, spend cap.

Stuck detection

Seven heuristics, each contributing a weight toward a threshold (default 1.0). No single heuristic can flag a session — the heaviest is 0.7. That is deliberate: every one of these signals has a common, entirely healthy explanation, so it is their conjunction that carries information rather than any one of them.

The Settings tab lists each heuristic with what it detects and how it can be wrong, because a threshold you cannot reason about is one you will never tune. When a session is flagged, the card and the detail drawer show which signals fired and with what weight, so you can overrule the judgement using the same evidence the system used.

Defaults are set to be quiet. A dashboard that cries wolf is worse than none.

Task summaries

Off unless AWS Bedrock credentials and a region both resolve — AWS_BEARER_TOKEN_BEDROCK or the standard AWS chain, plus AWS_REGION or summariser.region. With no credentials everything else works and the UI says plainly that summaries are off and why.

export AWS_BEARER_TOKEN_BEDROCK=...
export AWS_REGION=us-east-1

Default model anthropic.claude-haiku-4-5 (Bedrock model ids carry the anthropic. prefix). Summaries regenerate only on a meaningful change — a new prompt, a compaction, a turn failure — never on a tool call, and never more often than minIntervalSeconds. There is a daily spend cap ($0.50 by default), and the dashboard shows Muster's own spend next to your Claude spend so it cannot quietly become a cost centre.

Summariser input is a bounded distillation of the session rather than the raw transcript. Transcripts reach megabytes; sending one per summary would make this the most expensive thing the dashboard does. Set summariser.rawTranscript if you want the literal behaviour and the bill that comes with it.

Keeping cost and rate-limit figures fresh

The status line is event-driven, so a session that goes idle stops reporting its cost, context usage and rate-limit headroom. Muster always shows how old a reading is rather than presenting a stale number flat.

If you would rather they stayed current:

node bin/muster-install.mjs --refresh-interval 10

That re-runs the status line every 10 s per session. It is off by default because it costs a process spawn per session per interval, forever.


What Muster will not tell you

Stated plainly, because a dashboard's blind spots matter as much as its readings:

  • It never estimates cost. Cost, context usage, and rate limits exist only in the status-line payload — no hook carries them. A session with no sample shows unknown, never $0.00.
  • It cannot tell a killed session from a hung one. No SessionEnd arrives either way. After the staleness threshold it says presumed ended, which is a presumption from silence and is labelled as one.
  • Coverage gaps are shown, not hidden. If the server was down, hooks spool to disk and replay on restart, and the UI states the window and how many events were recovered. What could not be spooled is reported as lost.
  • Fork lineage is best-effort. No payload links a fork to its parent; Muster recovers it from transcript UUID overlap. If that layout changes, forks appear as independent sessions rather than being linked wrongly.
  • Some of this has not been verified against a real machine. The status-line path and the interactive-only events were built against the documented schema because the development container cannot run an interactive Claude Code session. requirements.md carries an explicit table of which acceptance tests have not yet been run. Those are the first things to check on your machine.

Development

npm test         # 51 tests: installer fixtures, hook scripts, derivation
npm run typecheck
npm run dev:server   # tsx-free: node --experimental-strip-types
npm run dev:ui       # Vite on :4600, proxying the API

The tests worth knowing about: the installer fixtures assert that pre-existing hooks and status lines survive install and come back byte-identical after uninstall; the script tests assert the hooks exit 0 with empty stdout against a down, hung, and malformed-input server; the derivation tests drive every heuristic both ways — the case that should fire and the benign case that should not.

Every commit message names the requirement IDs it advances.

About

Command and control for local Claude agent sessions — roll call, status, and attention routing, so nothing runs unsupervised.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages