Skip to content
Agentic Control Plane

The Muse Code control model, explained

Muse Code shipped in beta on August 5, 2026 — Meta Superintelligence Labs’ terminal coding agent, built on muse-spark-1.2, installed as a native binary (no source repository is published). Among the majors it arrived with the strongest default posture: an OS sandbox and approvals both on from the first run, and an append-only local event log underneath. This page is the reference: what Muse Code ships natively for controlling tool execution, exactly how each mechanism behaves per Meta’s own docs, and where the model ends — including the places where the beta’s documentation is ahead of the shipped binary.

This page covers Muse Code’s own controls. Our integration for Muse Code is in development; for the cross-harness picture, see the controls comparison.

The two-layer model

Muse Code’s permissions doc draws one distinction cleanly, and everything else on this page hangs off it: approval decides whether a command runs; the sandbox limits what it can access when it does. The two layers are independent — disabling one leaves the other active — and both are on by default. That independence is the design’s best property: most of the escape-hatch analysis below reduces to asking which layer a given flag removes.

Approval modes and the judge

--approval-mode selects one of three postures:

Mode What holds for review
on-request (default) Only the dangerous setrm -f, rm -rf, or sudo preceding either. Everything else runs automatically, contained by the sandbox.
untrusted Any shell stage without a matching allow rule. Escalates shell execution only.
never Nothing. No prompts; the sandbox is the only control.

In every mode, file reads and in-workspace write_file/edit_file pass without prompting — the sandbox is what bounds them. Read that default carefully: on-request is a sandbox-first posture, not an approval-first one. Three commands hold for a human; the rest of the shell runs.

Then there’s the piece that makes Muse Code’s approval layer unusual: a built-in LLM approval judge reviews prompt-bound calls automatically, so the human only sees what the judge escalates. --approval-judge off routes every decision back to the human. The fatigue math favors judges — but notice where this one sits. It isn’t a third layer; the docs are explicit that it belongs to the approval layer itself. A model is adjudicating a model’s tool calls, inside the mechanism that exists to check the model. That’s a defensible trade for attended use, and it’s one you should make knowingly — especially since the docs describe no record of what the judge decided or why.

Staged shell review

Compound commands get the most careful treatment we’ve seen a harness document. Muse Code parses a compound command into ordered stages and checks each one individually; execution blocks at the first unapprovable stage. In wc -l report.log && echo cleaning && rm -rf report.log, the two read-only stages pass and the rm -rf holds at stage 3 of 3 — and the command executes as a single unit only once all stages pass. Rejecting the held stage means nothing runs, including the earlier safe stages. No partial execution, no “the harmless half already happened.”

Stages the parser can’t reduce to a complete, static command — command substitutions, non-read-only redirects, variable-assignment prefixes — are handled by mode: in untrusted they hold for review; in on-request only dangerous-set matches hold and the rest passes to the sandbox. That’s the honest version of shell parsing: the parser knows what it can’t see, and the strict mode treats unparseable as unapprovable.

Trust scopes and rule precedence

When a stage holds, the prompt offers three resolutions:

  • Allow once — one-time, nothing saved.
  • Always allow in this workspace — saves a prefix rule scoped to the workspace root; it doesn’t travel to other projects or cover different commands.
  • Reject — denies the entire command.

Two hardening details worth stealing: a deny rule always overrides an allow rule, whatever the specificity, and interpreter prefixes (python, bash, node) can’t be saved as broad allows, because what follows them is arbitrary code. That second rule closes the classic allowlist hole — always allow python is an allow-everything rule wearing a disguise, and Muse Code refuses to write it.

Workspace trust is the outer gate: on first open, Muse Code asks whether to trust the workspace, and only a trusted workspace loads project-local skills, rules, and hooks (remembered per workspace root). One caveat from the configuration docs: committed project memory under .agents/memory/ is read even in an untrusted workspace — Meta’s own docs tell you to treat a repo’s MEMORY.md as a prompt-injection surface.

The sandbox

The sandbox is OS-enforced — Seatbelt on macOS, a bundled bubblewrap helper on Linux — and its rules are simple:

  • Writable: the workspace plus a temp directory. Everything else is read-only, and out-of-bounds writes fail at the OS level.
  • Read-only even inside the workspace: .git, .muse, and .agents — the agent can’t rewrite its own history, configuration, or memory. That’s a detail no other surveyed harness ships, and it closes a real hole: an agent that can edit its own rules file can approve itself.
  • Fail-closed: if the sandbox can’t be verified — Seatbelt check at session startup, bubblewrap probe on Linux — Muse Code refuses shell execution entirely rather than running unconfined.

Network gets its own dial, --sandbox-network: proxy-only (the default) holds the first connection to each new host, port, or protocol for review like a shell command; restricted is no network; enabled is full egress. Per-destination review as the default is a genuinely strong choice — most harnesses’ network story is a binary, and the ones that default open never ask.

Escape hatches

Three flags, and the two-layer model tells you what each one costs:

  • --disable-approval — prompts off, sandbox stays on.
  • --disable-sandbox — approval stays on, sandbox off. But note the fine print: this also removes workspace confinement from the file tools (write_file/edit_file can write anywhere) and forces the network to full egress, overriding --sandbox-network. One flag, three controls gone.
  • --yolo — both layers off, and the workspace is trusted for the run, which loads the checkout’s AGENTS.md, rules, and skills. Meta’s docs recommend it only for disposable CI containers and carry the right warning: on a PR or fork checkout, those files are attacker-controlled instructions. Trusting the workspace is part of what the flag disables — that’s the part people miss.

One question the docs don’t answer: the empty chair. What happens to a held approval when muse exec runs headless and nobody’s there — deny, timeout, hang — is not documented. Until Meta documents it, don’t hand Muse Code an unattended job in any mode where something can hold. (Our test for this is the empty-chair test; dsh is the harness that answers it natively.)

Hooks: the coverage is wide, the boundary is elsewhere

The hooks system covers the full lifecycle — SessionStart, UserPromptSubmit, PreToolUse, PermissionRequest, PostToolUse, PreLLMCall, PostLLMCall, PreCompact, PostCompact, SubagentStart, SubagentStop, Stop — and a hook can block an action before it happens. PreLLMCall/PostLLMCall and the subagent lifecycle events are broader coverage than most harnesses expose, and PermissionRequest means a hook can stand inside the approval flow itself.

Registration comes from three sources with three trust levels:

Source Location Trust
Project <project-root>/.muse/hooks.json explicit trust required (muse hooks trust <key>)
User machine-wide settings file explicit trust required
Managed file at the managed_hooks_path setting pre-approved, no trust step

Managed hooks are the first fleet-level control primitive we’ve seen ship in a major harness — centrally administered hooks that apply without per-machine consent. The docs state the flip side plainly: whoever controls the managed hooks file controls what executes. Treat that path like you treat sudoers.

Two honest caveats, one from the docs and one from a real install:

  1. Hooks run outside the sandbox. A hook’s command executes directly through your shell, outside both the sandbox and approval; the only hardening is a cleared environment with a small allowlist. The same is true of MCP tools — approval applies, containment does not. The strongest sandbox in the ecosystem has two documented doors around it, and both are the extension points.
  2. The docs and the binary describe two different front doors. The documented muse hooks management CLI (list, validate, trust, run --fixture) is not exposed by the shipped 0.2.1 beta binary. What the binary has instead — we verified this on a real install — is a plugin system behind a MUSE_EXPERIMENTAL_PLUGINS flag: hooks register as capabilities of a native plugin (.muse-plugin/plugin.json), installed and trusted through muse plugins install / approve, with a per-hook fixture runner (muse plugins hook test). The flag gates only that management CLI; once a plugin’s hooks are approved, they fire in ordinary unflagged sessions. The hook payloads and decision outputs follow the same schema Claude Code hooks use, down to the field names. Beta means beta — expect this to converge on the documented CLI.

Multi-agent

A lead session spawns child agents for bounded tasks. By default, children share the lead’s workspace — parallel writes can collide. Worktree isolation is opt-in via muse --subagent-worktree-isolation, which gives each child its own git worktree checked out from the lead’s commit; in a non-git workspace the flag is silently ignored. Children can’t spawn their own children, concurrency is capped around core count, and the runtime journals every spawn, status change, and control action. If you’ve seen coverage saying subagents “each run in their own worktree,” that’s the flag, not the default — check which one your pipeline runs.

The event log

Underneath it all, Muse Code keeps an append-only local event log of every model call, tool run, approval, and edit — Meta describes it as replay-exact and restart-safe, and it’s one of only two native records in our comparison that isn’t just a transcript. The limits are the usual ones: it lives on the machine that ran the agent, it’s authored by the process it describes, and nothing in the docs says it records what the approval judge decided or against what. A strong crash-recovery journal is not yet a decision ledger.

Where the native model ends

Muse Code’s native model is the best default posture shipping today: sandbox on, approvals on, fail-closed when the sandbox can’t be verified, deny-beats-allow, staged shell review, and self-protection for its own config. What it doesn’t have:

  • The judge is unexplained. The LLM approval judge decides automatically, and no documented record captures what it approved or why.
  • The extension points bypass the boundary. Hooks and MCP tools run outside the sandbox — the places you’d add controls are the places the containment doesn’t reach.
  • The empty chair is undocumented. Headless approval resolution is unspecified; unattended use is a guess.
  • Everything is per-machine. Prefix rules are scoped to a workspace root on one machine; the event log stays local; managed hooks are the one org-level primitive, and they’re a pointer to a file, not a policy you can query.
  • One flag undoes both layers, and nothing independent records that it was passed.

Those gaps are structural, and they’re the same four we find in every harness we’ve surveyed: self-authored records, per-machine policy, escape hatches without evidence, rules that don’t travel to the next harness. That’s the job of a layer above — one policy, enforced identically, with an independent off-machine record, across every harness you run. Our integration for Muse Code is in development; the coverage matrix tracks where each harness handoff lands as we ship them.