muster

How it works

Every part is named for a crew role or a place on a ship at muster. The names are the same in the packages and on the command line.

The parts

One module, three process types

Muster builds three process types from one module and one version. The web process runs intake, the ops pages, health, metrics and the seat API. The worker runs the leader ticker, the shadow runner, the settings loop and the tally loop. Work that must run once holds a leader lease, so a second worker waits.

The client runs the seat runner and the tool call guard. It links no database package and holds no database credential. A rule in the tests fails the build if it does.

webintake, pages, seat APIworkerticker, shadow, tallyclientseat runner, tool guardPostgresno databasecode

The roll: the event log

Events are appended and never updated or deleted, and database triggers refuse both. Each event has a source, a delivery id, a type and a schema version. The pair of source and delivery id is unique, so a redelivered event does nothing. Secrets are redacted from a payload before it is stored.

The gangway verifies and dedupes each webhook before it is recorded. The roll also keeps derived tables, which Rebuild can derive again from the log.

gangwayevent 1event 2event 3append onlyderived state tables

The bridge: a pure decision core

Reduce(state, event) returns the new state and orders. It does no input or output, reads no clock and draws no random number. It imports only the standard library, so it is tested by replaying recorded events.

Every order names the event that caused it and carries a rule name and a plain explanation. The classes of rule are dispatch, hold, precondition, cancel, operator request, issue intake and settings.

events and statebridgeorderseach order carries its causeimports only the standard library

The signalman and the executor

The signalman applies orders to the tracker and the forge through ports. Each effect carries an idempotency key made from the order alone, so a retry never repeats it. A failed effect is kept, retried with backoff and parked after a set number of tries.

The executor is off by default. An order is sent only when its switch is on, its ticket is on the allow list, all-stop is not set, the spend brake allows it and no other executor holds it.

order recordedfive checkseffect with keyoutcome marked

The bosun, berths and seats

The bosun supervises agent runs. A seat is one hand that runs one agent run. It claims a run through a lease, sends heartbeats, fetches its role bundle and reports the end. Berths are the work tables and leases, with one row for each role and ticket generation.

An expired lease can be claimed again. A failed attempt retries with backoff, and a row past its attempt cap is dead lettered. The seat API runs inside serve.

bosunstartleaseheartbeatstopagent runone live berth per ticketberths: work and leases

Roles: files that direct an agent

Roles resolve in layers when a run is claimed: project, then install, then base. The base set is embedded in the binary with ten roles: implementer, reviewer, investigator, db-engineer, docs-writer, infra-engineer, release-manager, security, sre and triager. A read only role never lists the edit or write tools.

A layer only adds. It cannot remove or weaken a safety rule or raise a sandbox tier. The project layer is read from the .muster folder of the repository.

Direction: a studio will let you edit roles and rulesets in the app. A role will extend a base role, and a ruleset will apply to all repositories, one repository or one role. A compiler will turn them into a committed, hashed .muster tree that reaches each repository only as a reviewed pull request, with the safety core still last and unweakened.

$ muster roles list
implementer	Shipwright	build
reviewer	Surveyor	read-only
project: .muster folderinstall: settingsbase: in the binary

Ports: four kinds of outside system

Muster talks to an issue tracker, a code forge, a model provider and an agent runtime. Each sits behind a small interface in internal/ports. The core never imports a vendor client. Every write takes an idempotency key, errors are typed, and each adapter has an in memory fake.

All traffic goes through one shared HTTP client with a timeout, retry with jitter, an ETag cache and a request budget. More on the ports.

bridge: ordersports: adapters fortrackerforgemodel provideragent runtimeskiff worker image

Skiff: the worker image

The skiff is the worker image. muster skiff assemble turns a lockfile into an OCI image layout. Every file is pinned by digest, the same inputs give the same bytes, and the image holds no scripts or credential files. The seat loop claims one run, runs the agent runtime on it, reports and exits.

basenodepitoolchaintoolingdocs

Shadow mode and the tally

Every five seconds the leader folds the roll's new events through the bridge and records the orders. Nothing goes to the tracker or the forge. Each recorded order has its cause, its rule, an explanation and an executed flag.

The tally reads the outside world, counts it against the roll and reports drift as events. It never edits state directly.

real worldrolltallydrift events on the roll

A real event

These are two event payloads, copied from the frozen shape files that the tests replay. The first is a review verdict from a reviewer run. The second is a ticket opened with a role label. The ticket key T-1 is a fixture.

{"ticket":"T-1","verdict":"changes_requested","generation":1}
{"ticket":"T-1","labels":["role:implementer"]}

The roll wraps each payload with its source, delivery id, type and schema version. A payload change needs an upcaster, a new shape file and a registry bump, and old shape files are never edited.

The record of why

The tagline says receipts: every order keeps its cause, and the whole trail from event to effect is on the gates page.