Muse Code's hooks aren't where the docs say — we found where they actually live
Run agents? ACP lets you see, control, and price every tool call they make — allow/ask/deny policy, per-session cost, and a full audit log for Claude Code, Cursor, Codex, and OpenClaw, in one command:
curl -sf https://agenticcontrolplane.com/install.sh | bash
Getting started → · see your first governed call → · free up to 5 agents · prefer fully on-device? add --local
Muse Code — Meta Superintelligence Labs’ terminal coding agent, in beta since August 5 — now has ACP support via @agenticcontrolplane/muse-code (npm).
Getting there meant answering a question the docs don’t: where are Muse Code’s hooks, actually? Because the documented interface and the shipped binary disagree — and the answer turned out to be more interesting than a normal integration.
The docs describe a door that isn’t there
Meta’s docs describe a hook system with a muse hooks management CLI — list, validate, trust, run --fixture — and a .muse/hooks.json registration file. We installed the current beta (0.2.1) and went looking for it. The CLI isn’t there:
$ muse hooks list
invalid TUI options: unknown argument `hooks`
A project .muse/hooks.json is silently ignored. The documented front door is real on paper and absent in the binary. Beta means beta — but a control layer can’t be built against a door that doesn’t open, so we kept looking.
Where the hooks actually live
The hook engine is in the binary — you can see the lifecycle events (PreToolUse, PermissionRequest, PostToolUse, Stop, and the rest) and the decision vocabulary right in it. What’s missing is only the management CLI. So the question became: what does register a hook?
The binary answers its own question. It ships a built-in plugin-authoring skill, and inside it a file named native-plugin-contract.md — Meta’s own spec for the interface that’s actually wired. Hooks ship as capabilities of a native plugin, gated behind an experimental flag:
MUSE_EXPERIMENTAL_PLUGINS=1 muse plugins install ./my-plugin --scope user
MUSE_EXPERIMENTAL_PLUGINS=1 muse plugins approve my-plugin
A plugin is a directory with a .muse-plugin/plugin.json manifest; each hook is a capability naming a lifecycle event and an argv command. The flag gates only the management CLI — once a plugin’s hooks are installed and approved, they fire in ordinary sessions with no flag set. That’s the door. It’s just not the one the docs point at yet.
It speaks Claude Code’s contract, exactly
Here’s the part that made the integration fast. We drove a probe hook through Muse’s own capability runner and read back what it accepts, field by field. The wire contract is Claude Code’s hook schema — not similar, the same:
- Payload in (snake_case JSON on stdin):
hook_event_name,tool_name,tool_input,session_id,permission_mode,cwd. - Decision out: a
hookSpecificOutputobject withpermissionDecision(deny/ask/allow) andpermissionDecisionReason. A bare{}is allow.PostToolUseblocks with a legacy{ "decision": "block", "reason": … }. Exit code 2 with stderr is the fallback block channel. Unknown output keys fail the hook — so there’s no guessing; the parser tells you precisely what it takes.
If you’ve written a Claude Code hook, you’ve written a Muse Code hook. That’s a real gift to anyone building tooling across harnesses, and it’s the reason our plugin is thin.
What the ACP plugin does
The plugin is one zero-dependency ESM file. Every tool call Muse makes gets policy-checked against your ACP workspace before it runs:
| Muse event | ACP endpoint | Effect |
|---|---|---|
PreToolUse |
POST /govern/tool-use |
allow / ask / deny before the tool runs |
PermissionRequest |
POST /govern/tool-use |
a policy deny settles Muse’s own approval prompt; anything else lets it proceed |
PostToolUse |
POST /govern/tool-output |
output scanning; a server block becomes feedback the model sees |
Stop |
— | one session receipt with a deep link to the timeline |
We verified every one of those paths against Muse Code’s own muse plugins hook test runner — a real rm -rf / through the real production gateway comes back denied with the hardline-floor reason attached, a benign read passes, an approval request is settled by policy. Not a mock of the harness: the harness’s own hook runtime, driving our actual plugin.
One design note worth its own line. Muse runs hooks with a cleared environment — a deliberate, good hardening choice. It also means an ACP_BEARER_TOKEN you export never reaches the hook. So the plugin reads its credential from ~/.acp/credentials (written once by the installer, free for individuals) and takes operational overrides from ~/.acp/config.json. Zero dependencies isn’t an aesthetic here: a hook running outside Muse’s sandbox with your credential nearby is a supply-chain target, and the smallest possible attack surface is one file you can read in two minutes.
Where Muse Code lands on the coverage table
(The living version of this table is at /coverage.)
Muse Code intercepts everything through one hook pipeline, can express a real ask (its PermissionRequest maps cleanly to policy), and — because PostToolUse can turn a server block into model-visible feedback — can act on output rather than just watch it. It lands in strong company: alongside Claude Code and DeepSeek Harness, it can answer all three coverage questions with a yes.
That’s not our plugin being clever. It’s Meta shipping a genuinely strong default posture — OS sandbox and approvals on from the first run, an LLM approval judge, a hook engine that borrowed the right contract — and then documenting a management CLI that hasn’t caught up to it. We built against what’s actually there. When the documented muse hooks CLI lands, the plugin converges onto it; until then, the plugin system is the wired path, and it works today.
Install
npm install -g @agenticcontrolplane/muse-code
MUSE_EXPERIMENTAL_PLUGINS=1 muse plugins install "$(npm root -g)/@agenticcontrolplane/muse-code" --scope user
MUSE_EXPERIMENTAL_PLUGINS=1 muse plugins approve acp
Full guide, including the cleared-environment credential path and the config overrides: /integrations/muse-code. The control-model reference for Muse Code — approval modes, the LLM judge, the sandbox, and where the docs run ahead of the binary — is at /controls/muse-code.
Muse Code is days into a beta that promises breaking changes; treat the plugin the same way — early, tested against the harness’s own runtime, tracking a fast-moving target. The interception surface underneath is one of the best we’ve integrated, and it was hiding one flag away from where the docs said to look.