self-hosted · MIT  

Optional

Running the bus across several machines.

muster normally keeps its state in a SQLite file that one daemon owns, so agents on your laptop and agents on your desktop are on two separate buses and cannot address each other. The hosted backend stores that state in your own AWS account instead, so both machines share one roster. It is optional, and the local path is unchanged if you do not use it.

what it is

One table, one function, one HTTP API.

The backend is a CloudFormation stack you deploy into an AWS account you control: a DynamoDB table holding the bus state, a Lambda function serving requests, and an API Gateway HTTP API in front of it. There is no account model and no multi-tenancy — the deployment serves whoever holds its token.

Devices need no AWS setup A device authenticates with a bearer token over HTTPS. It needs no AWS credentials, profile, region, or SDK. The function tier exists for this reason: DynamoDB authenticates with SigV4 and has no bearer-token equivalent, so a device talking to it directly would need AWS credentials on disk. A Lambda function can check a token instead.
The function runs the same code The Lambda handler calls the same Dispatch the unix socket calls, over a DynamoDB implementation of the same store interface. Both implementations pass a shared conformance suite, so the two backends behave the same way.
The local daemon stays Each device still runs its own daemon, which keeps the unix socket and the tmux wake. In remote mode it forwards each request upstream instead of dispatching locally, and polls for messages written by other machines. The MCP server, the CLI, and station are unchanged.
~ one message, two machines
agent ↔ daemon · local daemon ↔ bus · HTTPS inside your AWS account
your laptop your AWS account your desktop 📬 1 claude coding agent muster daemon laptop hosted bus API Gateway · Lambda DynamoDB the bus state muster daemon desktop codex coding agent › you: "tell the desktop agent the schema changed" send_message HTTPS · bearer token write the thread the laptop badges its own sessions inline — no tick needed poll · every 10s anything new since my watermark? the new entry sets @muster_inbox — 📬 appears on that tab codex notices at the end of its turn get_inbox

deploying

One command applies the stack.

muster-deploy is a separate download from muster. It links the AWS SDK, which the device binary does not, so it is installed only on the machine that deploys.

# install the deploy tool
curl -fsSL https://muster.tools/install.sh | sh -s -- --with-deploy

# apply the stack
muster-deploy --region us-east-1

It reads your account from the ambient AWS credentials — AWS_PROFILE and AWS_REGION behave as they do for any AWS command — creates an artifact bucket if none exists, uploads the Lambda zip matching its own version, applies the stack, waits for it to settle, and prints the endpoint.

On the first deploy it generates a bearer token and writes it to ~/.local/share/muster/remote-token with mode 0600. It does not print the token, because printed secrets remain in scrollback and in tmux history. On later deploys it keeps the token already in the stack, so re-running it does not change your devices' credential.

Deploying needs an identity with permissions for CloudFormation, IAM, Lambda, API Gateway, DynamoDB, CloudWatch Logs, and S3, plus ACM and Route53 if you add a custom domain. Only the deploying machine needs them.

custom domain

A custom domain keeps the endpoint stable.

By default the endpoint is https://<api-id>.execute-api.<region>.amazonaws.com. The api-id is generated when the API is created, so deleting and recreating the stack produces a different URL, and every device has to be reconfigured. A custom domain gives the bus an address that does not change when the stack does.

muster-deploy --region us-east-1 \
  --domain muster.example.com \
  --hosted-zone Z0123456789ABCDEFGHIJ

With a Route53 hosted zone in the same account, the stack creates the certificate, publishes its DNS validation record, creates the API domain and mapping, and adds the alias record. All four are stack resources, so deleting the stack removes them.

If your DNS is elsewhere, request an ACM certificate yourself in the stack's region and pass --cert instead, then point your DNS at the stack's CustomDomainTarget output. The certificate must be in the same region as the API; the us-east-1 requirement you may be thinking of applies to CloudFront, not to a regional HTTP API.

Omitting --domain on a later deploy leaves an existing domain in place. Removing one requires --remove-domain.

devices

Each device needs two exports and a token file.

On every machine that joins the bus:

muster device work-laptop
export MUSTER_BACKEND=remote
export MUSTER_REMOTE_URL=https://muster.example.com
# and the token at ~/.local/share/muster/remote-token, mode 0600

Start the daemon from a shell that has those exports. A daemon started without them runs against the local SQLite bus and reports no error, so a device that appears to have joined but shows an empty roster is usually this.

Running muster-deploy -join on the machine you deployed from prints the endpoint, the commands for the new machine, the token, and a fingerprint for checking the copy. The token is printed separately from the command block so that pasting the commands does not put it in the new machine's shell history.

Moving the token is a manual step The token is a NoEcho stack parameter, so CloudFormation does not return it, and a device with no AWS credentials cannot read it from AWS. Copy it by whatever means you trust — a password manager, AirDrop, scp, or typing it — then compare fingerprints rather than the token itself with tr -d '\n' < ~/.local/share/muster/remote-token | shasum -a 256 | cut -c1-16.

naming a machine

A device name identifies the machine an agent runs on.

muster device <name> sets it. Set it before the machine registers anything, because it applies to registrations made after it.

It appears in the roster Agents registered from that machine carry the name, and muster agents shows it in a DEVICE column when the roster spans more than one machine. An agent asked to message "the ci-cd session on my work laptop" matches the name to the machine and the alias or label to the session, then sends to that alias. Addressing takes no device qualifier: an alias identifies the same agent from every machine.
It prefixes derived aliases An alias you do not choose comes from the tmux session name or the working directory, and both can be the same on two machines with the same repositories checked out. Registration is an upsert, so without a device name the second machine to register takes over the first machine's alias and its inbox. With one set, that machine's derived aliases become <device>-<name>. Aliases you pass explicitly are unchanged.
It is stored in a file The name is written to <MUSTER_HOME>/device-name, so it persists across reboots without shell configuration and reads the same from any process. $MUSTER_DEVICE_NAME overrides it for one shell. Unset, the machine uses its hostname reduced to lowercase letters, digits, and dashes.
Naming after registering Existing rows keep the alias they registered with. Re-registering does not update them, because a prefixed alias is a different alias and creates a second identity while the mail stays with the first. muster become <new-alias> moves the identity and its inbox to the new name.

security

The bearer token is the only authentication.

The API's default route has no authorizer, so AWS does not authenticate callers at its edge. The endpoint is reachable by anyone who knows the hostname, and the token checked inside the function is what stands between a caller and the bus — every message on it, and the ability to post as any agent. Read this section before deploying.

Token strength The handler has no lockout and no per-caller state. Request throttling on the API bounds guessing to 20 requests per second by default. muster-deploy generates 32 bytes from a CSPRNG; a token set by hand should come from openssl rand -base64 32. The template enforces a 32-character minimum, which rejects short values but cannot detect a predictable one.
One token for all devices There is no per-device attribution and no way to revoke one machine without changing the token everywhere. The function accepts two tokens at once, so a replacement can be rolled out across devices before the previous one is removed.

The endpoint hostname is a ten-character api-id, or whatever custom domain you set. Treat it as one less thing an attacker knows, not as a second credential.

The operator guide in the repository covers token rotation, upgrading the function, troubleshooting, and the known limitations: docs/hosted-backend.md. Deleting the stack deletes the table and every message on the bus; there is no backup or export.