skill
pi-muster
Muster π: the project manager for Herdr project spaces in Pi, on top of Bellwether
Set up
Skills are installed by placing their folder where your agent loads skills.
The download comes from github.com/joelhooks/pi-muster itself, not from our server, so what you get is exactly what its author published.
Install instructions come from the README
README
synced Oct 25, 2026Muster π
Muster is the project manager for Herdr project spaces in Pi. A project is one Herdr space. A lane is one tab. An agent is a catalog row with a full launch profile. A packet is one harvested result named by its hash. The desk is the operator's queue.
It sits on top of Bellwether, which owns the generic Herdr runtime: sockets, panes, agents, watches, wakes. Muster imports Bellwether's exported modules as a library and never reaches into its extension. Bellwether never imports Muster.
herdr (terminals) β Bellwether (runtime) β Muster (projects, lanes, packets, agents, desk)
Default shape
One writer, one gate, one accountable desk. The desk owns the project; each lane has one writer, a worker who reports directly to the desk. The desk may also be the writer. The land gate proves the exact candidate.
Bosses, the hawk, judges and arbiters are opt-in for a named risk. Record the risk on the lane or in the launch before opening the role: for example, many packets touching shared files, a long unattended run, or customer money. There is no standing judge slot.
Retros run only after an incident (a bad landing, outage or lost work), friction that repeats across lanes, or when Joel asks. The desk opens one by hand; lane counts, elapsed days and ordinary project reviews do not trigger one.
Install
pi install git:github.com/joelhooks/pi-muster
Muster expects Herdr, pi-ratking (@rat-king/pi-ratking) and pi-until in the same Pi install. Muster traffic rides Rat King by name; pi-intercom is not needed. Muster always uses pi-ratking. Existing policy.comms values, including intercom and network, still decode but cannot select a transport. project_status reports ignored values and keeps the file unchanged. Without pi-ratking loaded, sends fail immediately with pi-ratking not loaded; there is no fallback. Two paths default to the author's rig and can be pointed elsewhere:
MUSTER_WORKER_WORKTREE: the script that allocates, harvests, and removes worker clones.MUSTER_DESK_EXTENSION: the Pi extension a desk agent loads to read its queue.
Communications
Muster emits ratking/send and receives ratking/message. Rows answer to <project>/<row>; rowless owners use the ownerName recorded on their rows. Owner posts and agent messages retain the deployed {type: "owner" | "message", recipient, β¦} JSON payload inside kind: "data", so old and new Muster sessions can exchange mail during rollout. A session id inside that payload is local queue metadata, not a delivery fence.
pi-ratking owns names, DIDs, keys, leases, cursors and the reader. Muster neither provisions identities nor starts a mailbox consumer. comms_doctor reports pi-ratking's live status when available, otherwise its canonical claims, directory and cursor files. Live status reports reader, leaseGeneration and leaseUntil, including null names and DIDs after retirement. It takes precedence over missing file claims for env-given names. Without live status, a cursor with no claim is reported as unknown; a wrong session or dead claimant suggests a restart. Remote rows are probed on their own machine. The doctor never mints or pushes keys.
An owner pass backfills ownerName and pushes updated orchestration bindings to running remote rows. Workers read that binding before their launch-time MUSTER_OWNER_NAME. A restart rebinds the catalog and emits ratking/retire from the old Pi; the successor takes the same name. There is no successor ledger, scoped owner forward or stale-session quarantine. Local queue history stays intact.
Detached launch helpers run on the owner's machine without a Pi event bus. They append their result to that owner's local queue and never send. Initial work travels through argv/file pointers and requires first-turn journal proof.
desk_phone uses pi-ratking's record API (rat-king 819c4ab or later). Item and update records go to the reserved joel-phone alias with encryption explicitly enabled, without a message wrapper. Answers arrive on ratking/record, never as agent text. Muster accepts them only when verified is true and the signed did matches the phone DID in the private ~/.config/muster/desk-inbox.json; it never trusts the sender label alone. The same file's Switchboard DID must match this Pi's live pi-ratking status.
Phone state remains in ~/.local/state/muster/desk-inbox/pending.json. Old pending threads and their message references remain readable. A send intent without a confirmed receipt is never resent blindly; sync retries recorded rulings and resolved updates, not uncertain initial sends. poll only reads state. Muster opens no mailbox, lease or second reader. The inert comms-network.ts compatibility stand-in stays until the desk finishes restarting older Pis. All vendor code and fixtures are gone. The old lazy item, receipt and quarantine schema imports were phone/network-only, not part of non-phone Rat King operations. Inbound Muster messages and phone records share a process-local 500-ID deduplication window before any follow-up or notification. Owner items also deduplicate against the local queue by URI and CID, including deliveries with a new message ID.
Long-lived sessions
Muster records the package commit when the extension loads, without spawning Git. Every tool, including desk and Switchboard tools, checks for changes at most once per minute. A changed commit adds a stale-tools warning with both short SHAs and the commit count when Git can calculate it. project_status also marks its board header. After a dependency upgrade, restart Pi with pi --session <session file>, not /reload: stale reloads keep Muster tool names and return the restart command instead of silently losing them. Muster-managed agents restart with agent_launch action: "restart", never /reload.
Outside a Git checkout, the check uses the package version and newest mtime of the extension's local source imports. Missing metadata or a failed check never blocks a tool. Startup reads version metadata only; it opens no socket or bus channel.
Roster
Role models are data. Muster reads ~/.config/muster/roster.json (or MUSTER_ROSTER) on every call, so one edited and synced file changes what every machine launches next. Without it, built-in defaults apply. Precedence: built-in, roster role, the alternate matching the chosen model, project policy, explicit agent_launch arguments.
{
"version": 1,
"aliases": {
"opus": "claude-bridge/claude-opus-5-5",
"sol": "openai-codex/gpt-6.1-sol"
},
"roles": {
"boss": {
"model": "claude-bridge/claude-opus-5-5",
"alternates": [
{
"model": "openai-codex/gpt-6.1-sol",
"thinking": "high",
"compactAt": 200000,
"useFor": ["investigation", "root-causing bugs", "reviewing a builder's output"],
"avoidFor": ["front-end design", "long unattended builds"]
}
]
}
}
}
Use opus or sol in launch and policy models; roster aliases can override these built-ins or add names. Thinking suffixes such as opus:high are supported. Aliases resolve before alternate settings are selected. Sonnet is refused, including aliases that point to it, except front-desk's claude-bridge/claude-sonnet-5-5 (Joel, 2026-10-04: "front desk needs to be opus 5 5 and sonnet 5 5"); exceptions live in MODEL_EXCEPTIONS in src/models.ts. Workers in any project may also opt into claude-bridge/claude-sonnet-5-5 (Joel, 2026-10-05: "we can offer exploratry use of sonnet 55 for workers"), but only as an explicit launch model; a policy or roster default naming it is still refused (ROLE_MODEL_EXCEPTIONS). Fable is off fleet-wide (Joel, 2026-10-04) and refused everywhere; the judge defaults to Opus. project_update returns the aliases with the effective policy.
Every launch, fork, and restore runs pi --list-models once in the launch cwd, with a 20-second timeout, before writing a row or opening a pane. A missing authenticated model fails with working-route suggestions. A failed or empty listing permits launch with a model check skipped note.
Delivery requires Herdr working, two clean pane reads three seconds apart, and a newly appended matching user entry followed by a clean first assistant entry in the session journal (90 s bound); a missing or failed first turn returns unproven with a FIRST_TURN event and repair call. Long or multiline prompts use private durable file pointers, including on remote hosts; receipts include the pane's Pi path and version, or unknown if the probe fails. Only Pi-rendered Error: lines in the newest output above its editor count; prose, older errors, editor text, and footer text do not. Auth/model errors fail the launch and row, record the line in row events, and leave the pane open. Rate limits under the same prefix warn without failing. project_status checks only running, silent, nudged, and restarted rows whose Herdr status is not working. With act: true, it fails owned rows with a current model error. Finished rows keep their state. Model-failed rows require an explicit restore, not automatic adoption.
Role and alternate skills name standing skills (names or absolute paths); launch skills append after project policy skills, de-duplicated. Desk, boss, hawk and judge launches also load the package's skills/muster, unless noSkills is set. Workers keep -ns and search the installed catalog with skill_find, then read a matching SKILL.md.
Tools
Owner side: project_open, project_move, project_update, lane_open, lane_deliver, lane_close, agent_launch, agent_close, packet_verify, packet_land, desk_post, project_status, project_review, desk_inbox, desk_answer, desk_report, desk_rulings, thinking_set (a session lowers or raises its own thinking level, as an opted-in hawk may do when the line stops).
Deploy posture
VISION.md is canonical. Add a section like this; Muster prints this conservative template when it is missing and never edits the file:
## Deploy posture
Level: 1 (prove)
Live proof frees the slot.
project_update policy.deployLevel is the fallback. No section or policy means level 1. A malformed section is reported and falls back. VISION wins over policy; status names a mismatch and shows each lane's effective level. Reads happen at call time.
| Level | Name | Deploy permission | WIP frees |
|---|---|---|---|
| 0 | locked | Cite a resolved approval desk item in evidence | Live proof |
| 1 | prove | Green gate | Live proof |
| 2 | ship-and-watch | Green gate, Rollback: ... and Watch: ... evidence lines | Deploy; desk follows watch evidence |
| 3 | jfdi | Green gate and Rollback: ... evidence line | Deploy; no proof step |
lane_open deployLevel only lowers permission and requires deployRule: customer-facing, money, outbound-sends, irreversible, shared-infra or slow-rollback. The tool refuses an override above that rule's cap. Customer-facing work, money or outbound sends cap permission at 1; irreversible work at 0; shared infrastructure at 2; rollback over five minutes or needing another person at 1. A later project-level decrease also lowers existing lanes.
project_update sets the sidebar headline and the project's policy: WIP and flow limits, silence limits and per-role model, thinking, compaction, and skills, merged over Muster's defaults.
Delivery and flow
A lane is done when its work is live and proven, not merely merged. See the definition of done and brief template. packet_report accepts deploy, proof, and signals: { working, failing, where } and includes them in its .svx report.
Committed packet_land outcomes start delivery at landed, with a timestamp. lane_deliver({ slug, stage: "deployed" | "proven" | "waived", evidence }) records the next stage and its plain-word evidence. Proof may follow a waiver or deploy at any level. Delivery otherwise only moves forward within a cycle; another committed packet starts a new cycle. Docs or probes with nothing to deploy can be waived. Rejected and no_changes packets need no delivery. Old catalogs decode committed lanes as proven with evidence before done-live; new landed work is never backfilled.
Feature WIP includes open and draining work lanes, plus closed lanes still at landed or deployed, even if archived. At deploy levels 2 and 3, deployed lanes leave WIP even with an open tab; the flow line keeps them visible as watching. Role tabs and proposed (open: false) ideas do not count. policy.wipLimit defaults to 3; null disables it. At the limit, lane_open refuses and lists in-flight lanes with stage and age. Park ideas with open: false, or pass override containing Joel's words; the lane records them.
The board header and turn feeds show one flow line: WIP 2/3 Β· landed, not live: lexicon-pin 40m Β· oldest in flight 2h Β· last proven 35m ago. β not flowing marks WIP with no recent proof or stage movement for policy.flowStallMin (default 120), or a reported packet waiting longer than policy.landWaitMin (default 30). These limits are minutes and can be changed with project_update. No extra sounds or toasts.
Worker side, when MUSTER_AGENT is set: owner_note, owner_reply, owner_inbox, packet_report and per-role compaction (--compact-at, /compact-at). A worker (MUSTER_ROLE=worker) gets its own queue tools but no project-management or operator tools.
muster-heavy -- <command> registers a job and runs it immediately. It never queues, grants capacity, checks memory for admission, or holds an exclusive window. Local packet_land gates run through the same CLI. Old admission flags and environment settings are accepted and ignored, with one stderr line naming them. Old grant management commands are inert.
Jobs live in ~/.local/state/muster/jobs/<id>.json: id, host, repo, cwd, full command, wrapper pid and start time. The wrapper is the process-tree root. A shared cache batches one ps across live jobs every five seconds. Status shows tree CPU percentage, current RSS and sampled peak RSS in KB. On exit the record has exit code, wall milliseconds, CPU-seconds and peak RSS. /usr/bin/time supplies final CPU accounting even for commands shorter than the sample interval. Darwin uses portable output; GNU/Linux includes the child exit field to distinguish signals from intentional exits such as 143. RSS peaks are sampled, so short spikes between samples are not measured. A dead wrapper without an exit is marked exit: null, lost: true on the next status or report pass.
Use muster-heavy status [--json] for live jobs and muster-heavy report [--since 24h] [--json] for per-repo and per-command count, total wall time, total CPU-seconds and maximum RSS. The report selects jobs by start time and includes lost-job counts. State and telemetry files stay private.
The status JSON keeps fleet-compute's placement fields. holders has one held entry per live job with mode: "job" and the full command, including any fc-<runId> marker. slots is live jobs plus one, not a capacity limit; minFreeGB reports MUSTER_HEAVY_MIN_FREE_GB (default 16 GB), which fleet-compute may use for placement. Local execution never consults that floor. availableGB counts free, inactive and speculative pages on macOS, and MemAvailable on Linux, rather than raw free memory. loadLimit is a large constant and exclusivePending is not held. jobs adds telemetry.
muster-heavy gate --wait 1200 -- <command> is unchanged: fleet-compute owns placement and queueing. --wait still applies to fleet routing, not local execution. MUSTER_FLEET_COMPUTE selects an absolute runner script; off or a missing runner falls back to immediate local execution. Installed packet_land uses fleet receipts to decide success and prove the committed tree. Keep pack:check and smoke local.
Owner queues
Owner messages and inbox results share a styled timeline: mention cards first, quiet posts grouped by agent, with expandable threads and refs and a plain NO_COLOR view.
Workers and bosses use owner_note({kind, title, body?, refs?, replyTo?}). FYI, progress and done accumulate silently. Questions, blocked notices and packet actions automatically mention the owner resolved at send time. owner_note reads the MUSTER_PROJECT catalog row named by MUSTER_AGENT; if the catalog is unreadable or the row is absent, its receipt names the MUSTER_OWNER fallback. Takeover and restored or relaunched rows leave an owner-queue forward. Each new forward repoints earlier forwards to the new owner, so routes stay one hop deep across repeated restarts. New senders follow up to 16 hops and rewrite owner mentions; cycles and longer routes fail closed. The new owner's feed also tails old queues, so workers still on old code need no restart. Forwarded records appear in owner_inbox with via <old owner short id>. Each source keeps a branch cursor across reloads. History at takeover is included only after the old reader's last heartbeat, or entirely when no reader file exists. owner_reply still targets the parent's author.
Only a mention wakes a reader, and only when idle; busy arrivals ride on the next turn. One digest groups unread posts by author, keeps the latest progress, and includes all FYI/done titles.
owner_inbox({since?, kinds?, limit?, ack?}) reads this session's queue. Without since, it returns undelivered posts; since includes recent delivered posts. ack consumes only returned items. owner_reply({uri, text}) replies to a post in this session's queue, threads its root and parent, and mentions the author in their queue. Workers run the reader too.
Storage is private local JSONL at ~/.local/state/muster/owner-queue/<session>.jsonl, separate from Joel's desk queue. Lines use dev.muster.note.post records with local muster:// URIs, time-sortable keys, truncated canonical-JSON SHA-256 cids, session authors and UTF-8 mention facets. Titles cap at 200 characters, bodies at 4096 bytes. NSIDs and the local lexicon live in src/owner-lexicon.ts and .json. This is not a PDS: follows, a public timeline and real atproto transport are deferred.
A watch plus a 30-second poll delivers mentions. Session custom entries persist the cursor and delivered URIs across reload. A reader presence heartbeat expires after two minutes; under the legacy intercom policy, a missing, stale or dead reader, queue failure or telemetry failure sends waking posts directly, while retaining the queued copy when possible. Under ratking, the default, every post rides Rat King. Tool results identify the path. Relay counters record metadata only, never titles or bodies.
Switchboard
One inbox over every project's desk queue. It reads and routes; project desks stay the authority for their own work. Start a session as the Switchboard with pi --switchboard (or MUSTER_SWITCHBOARD=1, or /switchboard on). Nothing starts on its own otherwise.
- The collapsed widget is a theme-colored flame graph, at most eight lines: four to six flame rows, project labels (
βmeans a dead desk), and one open-count summary. Blocked/approval/decision weights are 3/2/1; logarithmic age adds height and heat. Quiet projects show dim embers and yield space first. Below 40 columns, withNO_COLOR,TERM=dumb, or an uncolored theme, it shows only the plain summary. - Flames flicker deterministically at about 7 fps, flare for 1.5 seconds on arrivals, and settle for 2 seconds on resolutions. Only the active visible Switchboard animates; the overlay pauses it. After 60 seconds without queue changes or input it freezes, resuming on either. Pi-tui 0.84.3 exposes no terminal-focus notification API; incoming CSI focus reports are honored if the host supplies them. Animation only invalidates the widget and requests a TUI render, never a model turn.
- The expanded
alt+soverlay keeps the rankedproject#idasks and ticker in its header, above the navigable project rows. It keeps the newest five queue events for an hour; resolutions showβ resolved. On short terminals the header yields room to navigation. - Queue changes repaint without a model turn, with a one-second timer as the watch backstop. Queue reads retain byte offsets and reset on truncation or replacement; ages tick from cached records. Fleet topology and desk liveness refresh every 30 seconds or on registry changes.
β no live deskmeans no catalog desk or owner is in the live session list; an unavailable session list, as under ratking, shows no marker. The first refresh failure is logged with its message. - Nudges are only a courtesy: writes before Switchboard registration, old extension versions, and external queue writers can miss them. Activation and filesystem reads show the entire open queue anyway.
alt+sor/switchboardbrowses everything:j/kmove,spacefolds,enterputs an[project#id]reference in the editor,aanswers,dmarks done.desk_inboxlists open items ranked blocked, approval, decision, oldest first. It is read-only; onlysubscribe: true,--switchboard, or/switchboardmakes a session the Switchboard that is paged on every queue change, and never one Muster launched (anyMUSTER_ROLE).desk_answerappends a resolving line to the item's own queue and nudges that project's desk.
Relay diet
Packet reports still send one actionable owner-queue message to the owner. The owner must verify and land them.
The Muster desk feed suppresses ids created by this session's desk_post, desk_answer, and desk_rulings tools. It reserves the queue id before writing, keeps it in memory, and saves a muster-desk-self-post custom session entry for reload. A failed attempt may leave an unused reserved id. Other sessions' items still arrive, even when their sender label matches. The queue and inbox remain unchanged; only self-delivery is suppressed.
With Bellwether's pane-close bus support, agent_close and lane_close notify Bellwether before closing a pane Muster opened. The bellwether/pane-close/v1 event carries the pane id, recorded terminal id, reason and synchronous reply callback. Bellwether retires the owner's matching watches and withdraws their held wakes; close receipts report watches retired: <ids> (or none). Owners no longer need to cancel their own blocked-watch manually. With older Bellwether or no listener, list the pane's watches and cancel them first with herdr_watch action=cancel id=<id>. Fallback receipts name candidate ids from session receipts, not confirmed live watches; named agent targets need inspection too.
~/.local/state/muster/relay-events.jsonl holds best-effort metadata counters, one line per packet relay attempt or queue item delivered/skipped. Each line contains {ts, session, kind, project, packetId?, itemId?}; no bodies, titles or refs. kind is packet_report, desk_note, desk_note_skipped_self, or watch_retired. A synchronous pane-close acknowledgement emits one watch_retired line per retired id, stored in itemId; the fallback emits none. A batch delivered before a user turn records one desk_note per item, not one per card. These counts measure relay boundaries, not completed model turns. Write failures never fail a tool or feed delivery.
Desk report
When a desk holds several decisions, it publishes one static feedback page instead of a chat digest. desk_report builds it from report items: one card per decision, radio sets per decision axis, and a copy-feedback button. The page is noindex and holds no links or addresses. desk_rulings turns the pasted feedback into one resolving line per desk item, plus one message for the owner. The contract is in skills/muster/references/desk-report.md. The page inlines ratstack's app.css when it is on the machine (MUSTER_DESK_REPORT_CSS overrides the path).
State
<project>/.brain/data/muster/project.json: the project, its lanes, agent rows, and packets. Schema-decoded on every read; written with a lock and an atomic rename.<project>/.brain/data/muster/reports/: packet reports as.svx, with title, packet id and lane frontmatter. Worker text is fenced as code for MDsveX; existing.mdreport paths still verify and land without migration.closed/: pane tails saved before a close or restart.<project>/.brain/projects/muster/<slug>.svx: a generated Brain board.~/.local/state/herdr-desk/<slug>.jsonl: the desk queue, one JSON line per item.~/.local/state/muster/jobs/<id>.json: private atomic job records, retained after exit.~/.local/state/muster/jobs/samples.json: shared five-second process telemetry cache;sampling/serializes cache writes only, never command execution. Legacy admission files are unused and left untouched.~/.local/state/muster/projects.jsonl: every projectproject_openhas seen, so the Switchboard can find them all.
Muster refuses project state, briefs, and agent cwds under a temp dir unless the project was opened with ephemeral: true.
Lifecycles
XState machines, persisted as the state value on each row:
- Agent:
planned β launching β running β reported β verified β landed β closed;running β silent β nudged β restarted;* β interrupted β restoring β running;failed. - Lane:
proposed β open β draining β closed. - Lane delivery:
none β landed β deployed β proven;landed β provenpermits a combined live deploy check;none | landed | deployed β waivedrecords nothing to deploy. - Project:
setup β active β reviewing β active | archived.
An illegal transition is a typed IllegalTransition error. Sidebar tokens (now, progress, agents, needs, source user:muster.v1) are derived from these states, the headline, and the desk queue. In a space it owns, Muster keeps the space label a plain name.
Checks
npm install --ignore-scripts
npm run check
npm test
npm run smoke
npm run pack:check
License
MIT