Skip to content

Add Muse Code as a first-class agent - #238

Merged
webcoyote merged 1 commit into
webcoyote:mainfrom
jeffbowen:add-muse-code
Sep 5, 2026
Merged

webcoyote merged 1 commit into
webcoyote:mainfrom
jeffbowen:add-muse-code

Conversation

@jeffbowen

Copy link
Copy Markdown
Contributor

Closes #236.

Adds Meta's Muse Code (muse) alongside the existing five agents, following the
conventions established by #182 (pi).

sv muse          # or: sv m
sv -N muse       # native install

What's here

File Change
guest/home/bin/muse New wrapper — Homebrew resolution by default, upstream shell installer under --native-install, execs --yolo
sv m|muse dispatch, help text, install_deps Homebrew arm, --fix-permissions coverage
helpers/agentsview-paths.sh Comment recording why muse is not wired into agentsview, and what to add when upstream supports it
scripts/tests 6 new tests + muse added to the brew-symlink permission loop
README.md Feature list, quick start, agent-flag list, native install, agentsview note, browser/iOS caveats

No CHANGELOG entry — .github/scripts/patch-changelog generates that from commits
at release time.

Three decisions worth reviewing

--yolo

Muse ships both a Seatbelt sandbox and staged approval prompts, ON by default.
--yolo disables both and trusts the workspace for the run.

This makes it the analogue of codex's --dangerously-bypass-approvals-and-sandbox
rather than claude's --dangerously-skip-permissions, because muse's own sandbox
is the part that matters: nested inside sandvault it adds no protection while
defaulting to proxy-only network and workspace-only writes. The narrower
--disable-approval would have kept that nested sandbox.

The README names it as a bypass, as it does for the other agents.

No tool-prompt injection

Muse is the first agent with no mechanism for one, so the wrapper does not source
sv-tool-prompts.sh. Verified against the 1.0.2 binary, not just the docs:

  • no --append-system-prompt (claude, pi)
  • no instructions-file option (codex's model_instructions_file)
  • no extra-context-directory option (gemini's --include-directories)
  • --prompt-file is muse exec only and carries the user prompt

Muse takes instructions solely from rules files — AGENTS.md / CLAUDE.md /
.agents/AGENTS.md / .claude/CLAUDE.md in the workspace, plus "foreign personal
rules" at ~/.claude/CLAUDE.md. Every one of those writes into either the user's
repository or another agent's config, where it would outlive the session and leak
into sv claude too.

So sv -b muse and sv -i muse start the browser and simulator but do not
announce the endpoints. SV_BROWSER_ENDPOINT and SV_IOS_SIMULATOR_ENDPOINT
remain in muse's environment for the agent or the user to read. Both the wrapper
and the README's Browser Automation and iOS Simulator sections say so, so a user
who runs sv -b muse and sees muse ignore the browser knows why, and knows to
mention the endpoint in their prompt or AGENTS.md. Happy to revisit if a
reviewer knows of a mechanism I missed.

Deliberately not wired into agentsview

agentsview 0.42.0 has no muse parser and no muse config key. Its nearest entry,
commandcode_project_dirs (~/.commandcode/projects), is a different tool — muse
keeps sessions under ~/.local/share/muse/sessions. Wiring it would mirror a
directory agentsview cannot read.

helpers/agentsview-paths.sh records the omission and what to add when upstream
ships support; existing opt-in users would then pick it up the documented way
(delete agentsview-export.state, re-run sv-agentsview-setup). A test asserts
the omission stays deliberate rather than half-done — an entry there with no
matching key in agentsview-config.py's VALID_KEYS aborts sv-agentsview-setup
for every agent, not just muse.

Verification

Both install paths tested end-to-end against muse 1.0.2 (1.0.2-R2040.1) on
macOS 24.6.0 / Apple Silicon, including the full sv flow from a host account.

Homebrew path (default) — sv muse

Check Result
Cask token muse-code installs a binary named muse at $(brew --prefix)/bin/muse pass
Binary is quarantined (com.apple.quarantine, "Homebrew Cask") confirmed
muse --help exits 0, so ensure_brew_tool's warm-up succeeds instead of aborting pass
Wrapper resolves $HOMEBREW_PREFIX/bin/muse and execs pass
Composed sv muse from a host account pass

The third row is the one that could have bitten: a cask binary exiting non-zero on
--help would make sv muse abort with a misleading "quarantined and failed to
warm up"
on every host.

The composed run was made falsifiable by deleting the native install first, so the
wrapper had no fallback to hide behind. It never printed Installing Muse Code natively..., confirming it took the Homebrew branch:

==> Installing Cask muse-code
==> Linking Binary 'muse-aarch64-macos' to '/opt/homebrew/bin/muse'
sandvault-jbowen: running muse --yolo

Native install path — sv -N muse

Check Result
Wrapper's install block, cold start (nothing installed anywhere) pass, ~10s
Idempotent — second run reuses the install pass, 0 re-installs
No shell-rc pollution pass, nothing written to .zshrc/.zprofile
No install loop under the #235 layout pass — installed once
Composed sv --rebuild -N muse from a host account pass

The loop row was tested by reproducing the exact conditions that made OpenCode
loop in #235: wrapper at ~/bin/muse, ~/bin ahead of ~/.local/bin on PATH,
invoked by name, with nothing pre-installed. It installed once and ran.

Muse is immune for a structural reason rather than by luck: its installer invokes
its launcher by absolute path (MUSE_LAUNCHER_INSTALL=1 "$launcher"), so it can
never re-enter the wrapper. OpenCode's installer resolves through PATH and does.

The wrapper also resolves by explicit path, never command -v muse, for the reason
the pi wrapper documents — it lives at ~/bin/muse ahead of everything else, so a
PATH search self-resolves and exec-loops.

Runtime

--yolo is effective at runtime, not merely parsed. Inside a live sandvault
session (sandbox-exec active, host home unreadable), muse reports
workspace trust: trusted source=run-flag.

Both composed runs reached an authenticated interactive session on the real
provider (Model set to muse-spark-1.3-contributor, You are logged in.), with
credentials persisting in the sandbox home across both install paths.

Because --yolo disables muse's own Seatbelt, muse never attempts to initialize a
sandbox nested inside sandvault's — the nesting risk is avoided rather than
survived.

One incidental observation: muse loads "foreign personal context" from
~/.claude/, so in the sandbox it picks up sandvault's own /sv skill and reports
Including your 1 Claude Code personal skill. Benign, and arguably useful;
--no-foreign-personal-context would suppress it if that is ever unwanted.

Not verified

Sustained agentic work — a long multi-turn session exercising muse's shell and edit
tools under --yolo. Session startup, authentication and a completed turn are
verified; extended tool use is not.

Note for maintainers

While testing this I hit a separate, unrelated bug, now filed as #237:
scripts/tests deletes its own working tree when the checkout sits at
$SHARED_WORKSPACE/repos/sandvault — the exact path sv-clone produces for this
repo, and the invocation the README documents. Not addressed here, to keep this PR
focused.

🤖 Generated with Claude Code

Closes webcoyote#236.

- guest/home/bin/muse wrapper: Homebrew prefix resolution by default, the
  upstream shell installer under --native-install. Muse is not an npm
  package; its installer drops a self-updating launcher into
  MUSE_INSTALL_DIR (default ~/.local/bin) which then downloads the real
  binary beside itself. MUSE_NO_MODIFY_PATH=1 stops it appending
  "export PATH=..." to .zshrc/.zprofile, which sandvault owns and rewrites
  from guest/home on every build -- the edit would be both lost and noise.
  ~/.local/bin is already on PATH via .zprofile, matching the other wrappers.

  The wrapper resolves by explicit path, never `command -v muse`, for the
  reason the pi wrapper documents: it is installed at ~/bin/muse and $HOME/bin
  precedes ~/.local/bin on PATH, so a PATH search self-resolves and exec-loops.
  The upstream installer is safe here for the same reason -- it invokes its
  launcher by absolute path, so it cannot re-enter this wrapper the way the
  opencode installer does in webcoyote#235.

- The wrapper passes --yolo, which is worth naming precisely. Unlike pi's
  --approve, this *is* a permission bypass, and a broad one: muse ships both
  a Seatbelt sandbox and staged approval prompts, ON by default, and --yolo
  disables both and trusts the workspace for the run. It is the direct
  analogue of codex's --dangerously-bypass-approvals-and-sandbox rather than
  of claude's --dangerously-skip-permissions, because muse's own sandbox is
  the part that matters: nested inside sandvault it adds no protection while
  defaulting to proxy-only network and workspace-only writes. Workspace trust
  is what loads project-local skills and rules, and the sandbox is where to
  make that decision. The README names it as a bypass.

- No tool-prompt injection, and so no sv-tool-prompts.sh source in the
  wrapper -- muse is the first agent with no mechanism for one. Verified
  against the 1.0.2 binary, not just the docs: there is no
  --append-system-prompt (claude, pi), no instructions-file option (codex's
  model_instructions_file) and no extra-context-directory option (gemini's
  --include-directories). --prompt-file is `muse exec` only and carries the
  user prompt. Muse takes instructions solely from rules files -- AGENTS.md /
  CLAUDE.md / .agents/AGENTS.md / .claude/CLAUDE.md in the workspace, plus
  "foreign personal rules" at ~/.claude/CLAUDE.md -- and every one of those
  writes into either the user's repository or another agent's config, where
  it would outlive the session and leak into `sv claude` too. So `sv -b muse`
  and `sv -i muse` start the browser and simulator but do not announce the
  endpoints; SV_BROWSER_ENDPOINT and SV_IOS_SIMULATOR_ENDPOINT remain in
  muse's environment for the agent or the user to read. The wrapper records
  this so the omission does not read as an oversight.

- sv wiring: m|muse dispatch, help text, install_deps, and --fix-permissions
  coverage. muse installs from the homebrew/cask "muse-code" token, whose
  name differs from its "muse" binary -- that is what ensure_brew_tool's
  second argument is for. The native branch needs no arm: muse ships a
  self-contained binary, so unlike codex, gemini and pi it never needs node.

- agentsview session export: muse is deliberately absent from
  AGENTSVIEW_AGENTS. agentsview 0.42.0 has no muse parser and no muse config
  key -- its nearest entry, commandcode_project_dirs (~/.commandcode/projects),
  is a different tool, while muse keeps sessions under
  ~/.local/share/muse/sessions. Wiring it would mirror a directory agentsview
  cannot read. helpers/agentsview-paths.sh records the omission, what to add
  when upstream ships support, and the documented refresh for existing opt-in
  users. A test asserts the omission stays deliberate rather than half-done:
  an entry here with no matching key in agentsview-config.py's VALID_KEYS
  aborts sv-agentsview-setup for every agent, not just muse.

Tests: help listing, --yolo anchored to the exec line (it also appears in a
comment and the startup banner, so a whole-file grep would stay green after
it was dropped from the exec), dispatch arm, Homebrew cask token, native
installer URL and MUSE_NO_MODIFY_PATH, agentsview omission, and muse added to
the brew-symlink permission loop.

No CHANGELOG entry: .github/scripts/patch-changelog generates that section
from commits at release time.

Docs: README feature list, quick start, agent-flag list, native install, and
a note that the agentsview dashboard does not yet cover muse.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@webcoyote
webcoyote merged commit 977a425 into webcoyote:main Sep 5, 2026
2 checks passed
@jeffbowen
jeffbowen deleted the add-muse-code branch September 6, 2026 17:12
@webcoyote webcoyote mentioned this pull request Sep 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: Add Muse Code support

2 participants