muster
README.md
# Muster
[](./LICENSE)
Muster launches instructed Codex or Claude Code agents on this machine. A
`session` returns an address only after its runtime is reachable. A `task`
runs once, captures output, and never advertises a peer address.
**Live launch verification:** Codex and Claude both launched and answered their
initial prompts without terminal interaction. Claude requires a directory
already trusted by the operator. The automated suite uses fake runtimes,
not real models.
## Build and run locally
Requires Node 22.12+ (tested with 24.16), TypeScript 5, and Codex 0.155.1 / Claude
Code 2.1.267. Terminal drivers use tmux or node-pty. Process discovery currently
requires a POSIX host with `ps` and `lsof`; the terminal interface itself is
platform-neutral. The macOS Terminal driver is an unavailable v1 stub.
Before launching a Claude session, open Claude normally in the target directory
and complete its workspace-trust review yourself, then exit that setup session.
This is a one-time prerequisite for each directory Claude requires you to trust.
Muster never accepts trust prompts or changes trust settings. An untrusted
directory can block startup; Muster times out and cleans up that launch.
```sh
npm ci
npm run build
node dist/muster.js run codex --prompt 'Review the authentication flow'
node dist/muster.js run claude --prompt 'Summarize the project' --host tmux
node dist/muster.js run codex --kind task --prompt 'Explain the test layout'
node dist/muster.js list
node dist/muster.js list --format human
node dist/muster.js list --kind task
node dist/muster.js output RUN_ID
node dist/muster.js stop THREAD_OR_SESSION_OR_RUN_ID
```
`--prompt` is required and cannot be blank. `--cwd` defaults to the current
working directory. `--kind` defaults to `session`. All commands except `output`
emit JSON; `output` prints the captured task output. `stop` also accepts an
unambiguous peer name or canonical address, refusing ambiguity with candidates.
Use the durable ID to stop a session whose runtime has renamed it.
For readable terminal output, add `--format human` to `run`, `list`, or `stop`:
```sh
muster run codex --prompt 'Review this project' --format human
muster list --format human
muster stop THREAD_OR_SESSION_OR_RUN_ID --format human
```
Human output keeps full IDs and shows directory, host, state, and relevant
attach/output/stop commands with actual IDs. pty sessions are labeled as not
watchable or attachable. `--format json` is explicit JSON; omitting the flag
still returns JSON. Put Muster options before any `-- RUNTIME_OPTIONS`.
`output` always prints captured task text and does not accept `--format`.
MCP tools retain their existing JSON responses.
Human output uses subtle colors in interactive terminals: green for idle or
running, yellow for busy or starting, gray for ended states and field labels,
red for failures or nonzero exit codes, and cyan for follow-up commands. IDs
and paths retain the normal text color. Set `NO_COLOR=1` to disable colors;
piped output and `TERM=dumb` are always uncolored. JSON never includes colors.
`run` accepts an optional `--` followed by runtime arguments. The normal allowlist
is `--model` / `-m`, plus Claude's `--effort`. Unknown options, bundled short
options, config injection and raw permission overrides are refused, even with
`allow_dangerous_flags` enabled. Use Muster's normalized permission flags. Prompt strings
are passed as a single literal argument after the runtime's option terminator.
## Terminal lifetime
Auto-selection tries tmux, then pty. Every peer includes `host`, `capabilities`
and `attach_hint`. Watchability is separate from the runtime's idle/busy state.
- **tmux:** a window in the dedicated `muster` session on the `muster` tmux
server. Watchable and attachable; survives the CLI or MCP server exiting.
Use the returned attach hint, or `tmux -L muster attach -t muster`.
- **pty:** not watchable or attachable. The CLI prints the peer record and stays
running to own the terminal. Ctrl-C stops it. MCP-owned pty sessions stop when
the MCP server disconnects. There is no persistent pty daemon.
- **task:** a per-run worker captures stdout/stderr and exit status after the
launching CLI exits. It supervises one task, with no retry or restart behavior.
`list` refreshes live session metadata and shows ended runs distinctly. A timeout
or startup failure cleans the process tree and host window, logs failure, and
returns an error instead of a peer record.
## Configuration and permissions
Muster reads `~/.muster/config.toml` once at startup and **never writes it**.
Missing configuration uses these defaults:
```toml
host = "auto"
launch_timeout_sec = 30
max_concurrent = 4
permissions = "deny"
sandbox = "read-only"
allow_dangerous_flags = false
```
Choose `sandbox = "workspace-write"` yourself when agents should edit files.
`full-access` and `permissions = "bypass"` require `allow_dangerous_flags = true`.
The old config spelling `danger-full-access` remains accepted as an alias.
Per-launch `--permissions` and `--sandbox` override these config defaults. The
same named fields are accepted by MCP `run`. Both session and task launches
support them:
```sh
muster run codex --permissions auto --sandbox workspace-write \
--prompt 'Implement the change' --format human
muster run claude --permissions auto --sandbox workspace-write \
--prompt 'Implement the change' --format human
```
| Muster permissions | Codex translation | Claude Code translation |
|---|---|---|
| `deny` (default) | Approval policy `never` | Permission mode `dontAsk` |
| `auto` | `--approve-for-me` | Permission mode `auto` |
| `bypass` | Bypass approvals and sandbox | Permission mode `bypassPermissions` |
`deny` refuses actions that would require approval; it does not prohibit tools
already allowed by the sandbox or permission rules. `auto` delegates permission
review to the runtime and may still reject an action; it is not blanket approval.
Auto mode requires `workspace-write` in Muster because the Codex preset selects
that sandbox. Explicitly select it; Muster never widens a read-only request.
Bypass requires `full-access`; combinations claiming a sandbox while bypassing
it are refused. `deny` can be combined with any authorized sandbox setting.
`full-access` disables the runtime command sandbox and requires operator-owned
config authorization even without bypass. Muster never writes that config.
Config defaults apply to subsequent launches; overrides apply to one launch.
Resolved `permissions` and `sandbox` are returned in records, shown in human
output, persisted for listing, and written in the pre-launch log. Old records
without those fields remain readable; their settings are not guessed.
The runtimes' enforcement differs: Codex reviews sandbox escalation requests;
Claude's classifier reviews tool permission requests while its Bash sandbox is
separate. Auto-mode availability and decisions remain subject to runtime,
model, account, and managed policy. These fields describe Muster's resolved
launch settings, not a continuous attestation of remote policy or user changes.
See [Codex auto-review](https://learn.chatgpt.com/docs/sandboxing/auto-review) and
[Claude permission modes](https://code.claude.com/docs/en/permission-modes).
The concurrency cap is shared by separate CLI/MCP processes, including pending
launches. Settings apply to both kinds; tasks are read-only by default.
Codex defaults to an explicit sandbox and never-ask approval policy. Muster enumerates
and explicitly disables inherited MCP servers, then verifies the effective
configuration. Hooks, plugins, app connectors, automatic skill-MCP installation
and external notifications are disabled for the child. Unknown MCP names that
cannot be addressed safely are refused.
Claude's built-in tools default to enabled. Unless full access is selected, its command sandbox is enabled,
requires availability, and forbids unsandboxed retries. File-writing tools and
sandbox writes are denied in read-only mode; default permissions use `dontAsk` so an
unattended child does not auto-grant escalations. User/project settings and MCP
servers are not inherited. Detected managed policy is refused because the CLI
cannot prove that inline settings override it. Enterprise remote policy can
arrive after startup; this v1 is not an enterprise policy-enforcement layer.
Claude permissions and its command sandbox are different mechanisms; neither
claim implies that every external tool is OS-sandboxed.
These child restrictions deliberately prevent propagating spawn authority.
Custom MCP tools—including Tin Can—are not automatically injected into the
child. The child's runtime inbox remains reachable from an external Tin Can.
No user-level runtime configuration is rewritten, and workspace-trust dialogs
are never accepted automatically.
Launch intent is fsynced to `~/.muster/launches.jsonl` before a runtime starts;
ready/failure outcomes follow. The log contains the **full prompt**, cwd,
requester, runtime, kind and host. Registry and task outputs also live under
`~/.muster`, with private file permissions. An abandoned `registry.lock` fails
closed: verify no Muster operation is running before removing that directory.
There is no automatic time-based lock theft.
## MCP installation
Installed deliberately, in the one session that should hold spawn authority —
never at user scope.
Muster exposes `run`, `list`, `stop`, and `output` over stdio. No arguments or
`mcp` starts the server. Diagnostics go to stderr, never protocol stdout.
Schemas match the CLI (`args` is the array of optional runtime arguments).
For a single Codex session, use per-invocation configuration:
```sh
codex -c 'mcp_servers.muster.command="node"' \
-c 'mcp_servers.muster.args=["/absolute/path/to/muster/dist/muster.js","mcp"]'
```
For a single Claude session:
```sh
claude --mcp-config '{"mcpServers":{"muster":{"command":"node","args":["/absolute/path/to/muster/dist/muster.js","mcp"]}}}'
```
Do not add Muster to `~/.codex/config.toml`, Claude's user-scope MCP registry,
or another shared configuration that grants launch authority to every agent.
## Tin Can compatibility
Muster has no Tin Can build or runtime dependency. Each tool implements the
written address contract independently. Vendored mechanics carry source-commit
headers and the original MIT license in `TINCAN_LICENSE`.
Addresses belong to running sessions and can expire or collide. Store
`thread_id` / `session_id`, and re-resolve through Tin Can's `peers` before
sending instead of caching a launch address. Canonical IDs are not unique keys.
The frozen cases intentionally preserve Tin Can's known naming defects.
Codex `idle` means reachable and not known to be busy, not guaranteed free.
The querying app-server can report `notLoaded` for a live thread; Muster maps
that to `idle`. Claude state comes from its session registry.
## Verification
```sh
npm test
npm run build
npm run test:contract
```
The ordinary suite requires tmux, node-pty, POSIX `ps`/`lsof`, and Python 3 for
real file locks in the fake Codex executable. It uses isolated runtime homes and
no model APIs. It skips the four explicitly invoked compatibility cases.
The verified contract baseline is **Tin Can 0.2.0**. The suite spawns an installed binary as an MCP
subprocess, compares durable IDs, and verifies delivery to fake runtimes through
both terminal hosts. A missing binary or missing durable field fails the test.
For reproducible verification, install that exact release outside Muster:
```sh
contract_dir=$(mktemp -d)
npm install --prefix "$contract_dir" --no-save @brutalsystems/tincan@0.2.0
MUSTER_TINCAN_BIN="$contract_dir/node_modules/.bin/tincan" npm run test:contract
```
Verification uses the published 0.2.0 release installed in an isolated
temporary directory. No Tin Can source edits are made.
Fixture version metadata alone does not indicate address-format drift: compare
the case arrays. Those arrays cover naming only, not peer-list membership or
state semantics. Upgrades require separate compatibility verification; passing
fixture hashes alone does not establish compatibility. The tests locate the
launched peer by canonical ID and compare its durable ID; they do not assert a
total peer count, exclude same-runtime peers, or assert Tin Can's busy state.
`CONTRACT_PROVENANCE.md` records both types of integrity checks.
One-time live probes are separate from the automated suite. Both runtimes
launched and answered an initial prompt with the versions listed above; Claude
used a directory already trusted by its operator. `npm test` does not run
real models.
## License and releases
MIT © 2026 Mike Williams. See [LICENSE](./LICENSE). Vendored Tin Can code
retains source attribution and its [MIT notice](./TINCAN_LICENSE).
[RELEASING.md](./RELEASING.md) covers versioning, package inspection, publication,
and release notes. Changes to the shared address format require an explicit
contract update; Muster never independently fixes the frozen naming behavior.
TDQS
B3.4/5.0
Scored across 4 tools
Disambiguation5/5
Each tool targets a distinct concern: launching a run, listing resources, stopping a run, and reading output. There is no meaningful overlap between actions.
Naming Consistency4/5
All tools use short, lowercase, single-word names in an imperative style, which is consistent overall. 'output' is slightly noun-like compared to 'run', 'list', and 'stop', but it still reads as a clear command.
Tool Count5/5
Four tools is a tight, well-scoped set for a small agent/session management server. Each tool has a clear role with minimal redundancy.
Completeness4/5
The core lifecycle of launch/list/stop/read-output is covered. Minor gaps exist around explicit status checking, session-level output, or cleanup/removal, but agents can likely work around these with list and stop.