Documentation

Scoped permissions

Capability is not handed to “an agent” as a lump. Every grant names an action, a scope and an effect.

The model

action  ×  scope                   ×  effect
db_read ×  db_connection:staging   ×  allow

which reads: may read, that one database, yes. Anything not granted is refused, so the default posture is closed rather than open.

Actions & scopes

Actions

ActionCovers
projects_readListing projects and tasks, reading task state
tasks_writeCreating tasks, spawning them, moving their status
tasks_closeClosing a task — separate, because it is destructive
worktree_readLearning a task's worktree path and branch
diff_readReading a worktree's diff
pr_writeOpening pull requests
hosts_readListing hosts
db_readSchemas, objects, describe, preview, explain, read queries
db_writeStatements that modify data or schema
issues_readListing and fetching tickets
issues_writeCreating tickets and importing them as tasks

Scopes

Each grant applies to any, or to exactly one project, worktree, db_connection, host, integration or repo. "Read the staging database and nothing else" and "touch only this worktree" are both expressible.

any is not a universal wildcard. It matches resources of the action's own kind, and it does not match a call that named no resource at all — so muster_db_schemas with no connection_id is denied even under allow db_read any.

db_read is charged on every database call

Including the ones that only write. A connection granted db_write without a reachable db_read resolves to no access at all: unusable, and hidden from muster_list_db_connections along with it. Grant read alongside write.

Deleting a policy in use is refused

As long as any task, project default, queued run or fork still resolves through it. That is deliberate rather than cascading to NULL: an unset policy means legacy-unscoped, so clearing the pointer would turn deleting a policy into granting unscoped access to a live task.

Effects & layers

EffectBehaviour
allowThe call proceeds.
askThe agent parks; you approve or refuse in the app. One prompt covers every pair a single call needs. A prompt that WAS shown and went unanswered expires as a refusal — never as an allow. What happens when nobody can be asked at all depends on a setting; see below.
denyRefused. A deny at any layer beats an allow at any other.

Layers

Policies stack global → project → task and intersect: each layer can only narrow what the one above allows. A task-level policy therefore cannot widen what the project permits — which matters when a task's policy is authored by a template, or by an agent.

Resolved per request. Permissions are evaluated on every tool call, not captured when the agent started. Tightening or revoking a grant takes effect on an agent that is already mid-run; nothing needs restarting to take capability away.

An empty layer is not an absent one

These mean opposite things, and the distinction is stored deliberately rather than inferred: an absent layer inherits, while a layer that has been authored and left empty denies everything. Clearing a layer and saving an empty one are separate operations for exactly that reason.

Forking a policy

When a task forks a shared policy it becomes a child, not a copy. It stores only its own task layer; the global and project layers are read live from the root ancestor on every resolution. So revoking something on the shared policy still reaches the forked task — a copy would silently have swallowed that revoke.

What happens when nobody can be asked

An ask grant needs a human. Muster distinguishes three ways that can fail, and they do not behave the same:

SituationOutcome
A prompt was shown and went unanswered until it expiredRefused. Never an allow, and recorded as an expiry rather than as your decision.
The approval queue is saturated (16 prompts already parked)Refused. Otherwise flooding the queue would turn every later Ask into an auto-allow — pressure would become permission.
Muster is closed, or has no window that can show a promptDepends on the strict toggle below.
Read this before relying on Ask for unattended work. The setting "require approval even when Muster is closed" defaults to OFF. With it off — the shipped default — an ask grant that cannot be put to a human is ALLOWED, and the audit row records the circumstance as auto_allowed_app_closed. In other words: for a headless agent run with the app closed, ask behaves like allow until you turn that toggle on.

Turn it on and the same situation becomes a refusal instead. Two details make that toggle trustworthy: if the setting itself cannot be read, it is treated as ON (erring toward deny is the toggle's whole purpose), and neither the fallback allow nor the strict refusal is ever cached — otherwise opening Muster would never start prompting, and closing it would never start allowing.

"Allow once" is a 60-second window, not one call

A single agent intention routinely issues dozens of identical reads, so approving "once" coalesces matching requests for about a minute rather than approving literally one call.

Presets & authoring

Presets author a whole layer from a named shape, expanded against the project's real resources — so "analytics, read-only" becomes concrete grants naming your actual connections, which you can then edit row by row.

The policy view shows the resolved result (what the agent actually gets after every layer intersects), the policy's revision, which tasks and projects depend on it, and any rule that can never fire — a grant pointing at a deleted database, for instance.

Applying a task-layer preset from a task's own permissions page, when the policy is shared by several tasks, forks it rather than editing it: the task is repointed at a private child holding only its own task layer, and the shared policy is left byte-identical, revision included, so no sibling task's grants move. Global- and project-layer edits are made on the shared policy itself, which is the point of those layers.

The muster CLI behaves differently on purpose. muster policy apply-preset --layer task writes to the policy you named, so it refuses a shared policy unless you pass --force — a script has no page to fork from, and re-scoping every task on a shared policy is not something to do by accident.

Why database access is safe by default

Three independent gates, so no single mistake opens a write path:

  1. The agent must hold db_write for that connection.
  2. The call must explicitly ask for a write — a read-shaped call cannot become a write by accident.
  3. The statement is classified per engine, and a read runs under a read-only transaction or role. Keyword scanning is advisory only; the transaction is the guarantee.

Redis and MongoDB are classified by command rather than SQL keywords: GET, TTL and {"find": …} are reads; GETSET, GETDEL and an aggregation containing $out or $merge are writes.

This governs access through Muster. An agent with its own shell can still run psql itself — a separate trust boundary. Worktree isolation and profile separation narrow the blast radius, but neither confines a process: a profile gives an agent its own config directories and accounts by redirecting its environment, and an agent running under one can still read any path you can.

New here? Start the 7-day trial — every feature, card up front, $0 today, key on screen. Or buy outright: $39 once per device, no subscription.