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.
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.
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:4599The 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.
node bin/muster-uninstall.mjsRestores 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.
Three independent checks, in increasing order of effort:
-
The dashboard's own diagnostics — Settings → 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 | jqgives the same thing.Some events legitimately read "never":
NotificationandPermissionRequestneed an interactive permission prompt,StopFailureneeds a rate limit,TaskCreatedneeds an agent team.SessionStart,UserPromptSubmit,StopandPostToolUseshould all appear within one session. -
/hooksinside an interactive Claude Code session lists the registered entries. Muster's appear assh …/muster-hook.sh direct|detach. -
claude --debug-file /tmp/m.log -p "run echo hi with Bash", then grep the log for hook execution.
npm run bench # four arms, 300 iterations eachMeasured 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.
~/.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. |
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.
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-1Default 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.
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 10That 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.
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
SessionEndarrives 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.mdcarries an explicit table of which acceptance tests have not yet been run. Those are the first things to check on your machine.
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 APIThe 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.