Runtime overview
Asymptote supports Muse Code, Meta’s terminal coding agent, through native hook payloads sent tobeacon-hooks.
--harness muse-code is also accepted.
Muse Spark is the model; Muse Code is the agent. Asymptote hooks the agent, so the harness name is
muse_code and the Muse Spark model id rides on each event in the model field. Muse Spark spellings are deliberately not accepted as harness aliases: asking to install hooks for a model would otherwise produce an install for the agent under a different name.telemetry.destination setting is an enum (legacy, consolidated, edge, external), not a URL: choosing external with a local OTEL_EXPORTER_OTLP_ENDPOINT causes the binary to detect the non-baseline destination and withhold its credential, disabling export, and the other values name internal destinations unavailable in public builds. Hooks are therefore the only collection path for this runtime, not merely the chosen one.
Prerequisites
Before enabling Muse Code telemetry, make sure:- Muse Code is installed and available as the
museexecutable, on macOS or Linux. There is no Windows build. beacon endpoint installhas run so shared endpoint config and runtime log paths exist.- Muse Code is restarted after hook install or removal so new sessions load the updated registration.
Collection path
Muse Code registers hooks through a managed hooks file named by themanaged_hooks_path key in its own settings.json. Managed hooks are pre-approved and run without a trust step.
$XDG_CONFIG_HOME/muse is used instead when that variable is set, matching how Muse resolves the directory itself.
Install edits settings.json by exactly one key. Unrelated settings — including ones Asymptote has never heard of — survive the round trip byte for byte. A settings.json whose JSON is invalid is reported rather than replaced; its only problem might be a trailing comma.
Registered events
Asymptote registers every Muse Code lifecycle event that carries endpoint signal:Events that are not collected
PreLLMCall and PostLLMCall are deliberately not registered.
Both carry the full messages array and the tool schemas — the entire conversation on every model call, not a preview — so subscribing would turn a telemetry install into a full transcript capture. PostLLMCall is also expected to fire per response chunk, making it by far the highest-volume event Muse emits. UserPromptSubmit already captures prompt text under the usual retention and redaction controls.
The file format fails silently
Everything about Muse’s managed hooks file rejects quietly. There is nomuse hooks validate command, and a bad file produces no warning, no exit code and no log line — so a broken registration looks exactly like a working one. Asymptote’s installer is shaped around that, and each of these is covered by a test:
- The nesting is mandatory. The
hookswrapper and the matcher-group level between the event name and the handlers are both load-bearing. Hoisting event names to the top level, or listing handlers directly under an event name, produces a file Muse ignores entirely. - Unknown keys inside a group or handler are fatal. One unrecognized key and that whole event is skipped while the rest of the file keeps working — so a typo removes monitoring from one event and leaves the install looking healthy.
env,shell, and a group’senabledare all rejected. Asymptote emits onlytype,command,timeout, andstatusMessage. - Unknown keys at the top level are tolerated, which is where Asymptote’s
beaconmarker lives.
The hook environment
Muse runs hook commands with the environment filtered down to a fixed allowlist. NoBEACON_* or OTEL_* shell export reaches a hook, and XDG_CONFIG_HOME is stripped too.
This costs Asymptote nothing, because the endpoint log and config locations are passed to beacon-hooks as --log and --config flags rather than through the environment. It is worth knowing when reading a hook command by hand, and when debugging why a hook cannot find a path you can see from your shell.
Discovery and status
Show Muse Code hook status
muse_code harness with capability hooks. beacon endpoint inventory reports both files, so a half-install is visible as a half-install rather than as a clean one.
Install or configuration support
beacon endpoint install prepares shared endpoint config and runtime log paths. Muse Code hooks are installed separately, because Muse loads them from the user’s own config directory:
.muse/hooks.json, but the shipping build ignores it, so --level project is refused with an error naming the scope that works. Writing that file would report success and collect nothing.
Install refuses to take over an existing registration. Muse reads exactly one managed hooks file, so if managed_hooks_path already names somebody else’s, Asymptote stops and says so rather than replacing it — overwriting would look like a clean install and quietly end their monitoring. The check runs before anything is written, so a refusal leaves nothing behind. The uninstall is the same property in reverse: it clears the key only while it still names Asymptote’s file, and leaves a hooks file Asymptote did not write alone.
Telemetry coverage
Harness names are matched against a closed set of spellings rather than by substring, and this runtime is the sharpest case for that rule: every Muse Spark model id starts with the same four letters as the harness (
muse-spark-1.2, muse-spark-1.3). A substring rule would report any event carrying a Muse Spark model string as a Muse Code session — and because the model and the agent share a brand, a reader could not tell the misattribution from the real thing. Muse Spark spellings are left unmapped so they appear as themselves, which is visible as an anomaly. muse is also an ordinary English word, which the closed set covers for the same reason.
Tool taxonomy
Muse Code sends the Claude Code payload shape but does not publish its tool names. The names that are confirmed are snake_case (read_skill, and the six subagent_* control tools), so Asymptote matches file-edit tools with a substring rule over edit, write, create and patch rather than a guessed literal list — a wrong literal list would capture nothing, silently.
The false-positive direction is bounded: a file block is only attached when the tool’s own arguments carry a path, so a non-file tool whose name happens to contain write yields an action with no fabricated path. None of subagent_spawn, subagent_status, subagent_send_message, subagent_cancel, subagent_wait or subagent_read_result match, so the lead agent’s own fan-out control loop stays out of the file-modification stream.
Approvals and enforcement
Approval telemetry comes from Muse’sPermissionRequest event and is recorded as observed, because an operator was actually asked. Asymptote does not synthesize an approval from a pre-tool notification for this runtime — doing so would put an invented decision next to a reported one for the same call.
Asymptote’s PreToolUse hook answers with an empty object, for two independent reasons. Muse reads a tool decision from hookSpecificOutput, so answering allow would disarm the user’s own permission prompt on a runtime the hook was installed only to watch. And Muse rejects a stdout object carrying keys it does not know, so a differently shaped permissive answer would not read as permissive — it would fail the hook run.
Enforcement stays behind the optional, off-by-default policy provider seam (BEACON_POLICY_PROVIDER), which is inert unless it names an executable and fails open on any error. See Known gaps for the status of Muse’s deny contract.
Data handling
Muse Code content is handled like every other runtime. Prompts, tool arguments, commands, paths, and diffs are retained in local or customer-controlled logs, with local secret redaction and per-string limits applied before writing, and a hash and byte count on each content-bearing event. Events over the 64 KiB limit drop raw and retained content, preserve stable metadata, and setfield_truncated.
Muse carries signal with no endpoint-schema field of its own — most importantly turn_id, which is the only per-turn boundary anything in its payload offers, plus permission_mode on tool events and source on SessionStart. Asymptote preserves the verbatim payload under raw.muse rather than inventing schema fields for one runtime. It travels the event’s normal path through sanitization, so raw is secret-redacted and string-limited like every other field, and it is the first thing dropped when an event exceeds the size ceiling.
Known gaps
- The hook contract is reverse-engineered, not documented. Meta publishes no hook API for Muse Code, and
muse --helpdoes not mention hooks. Asymptote’s integration is built from observed behaviour of a shipped build (muse 0.1.0-R708.1) rather than from a specification. None of it is a supported interface and any of it can change without notice — which is exactly why the tests pin the payload readers and the emitted file shape: the host rejects a bad hook file silently, so a contract change would otherwise appear as telemetry quietly going missing rather than as an error. - Token usage and cost are not collected. Muse’s usage counts arrive on
PostLLMCalland in its own session log, neither of which Asymptote reads. Muse Code activity therefore does not appear inbeacon token-usagereports or the dashboard token view. - No session-end event. Muse emits
Stopat the end of a turn but no session-end lifecycle event, so Asymptote records nosession.endedfor this runtime. - Subagents cannot be correlated to their parent. On
SubagentStartthe payload’s ownsession_idis the child’s, and no parent id is present, sosession.idon a subagent event identifies the subagent’s session rather than the one that spawned it. Muse’s on-disk session log does carryparent_session_id, but reading it is a poll path Asymptote does not implement; synthesizing a parent from a payload that does not name one would be a fabricated join key in a security log. - Assistant text is not collected.
Stopcarrieslast_assistant_message, which is preserved underraw.musebut not promoted to anagent.responseevent. - The policy deny shape is inferred, not measured. Muse is fail-open on everything except
{"decision":"block"}and exit code 2. That JSON shape is confirmed to block, but only onUserPromptSubmit, where blocking cancels the whole turn — emitting it from a tool phase would escalate “do not run this command” into “abandon what the user asked for”, so Asymptote does not use it. The tool-family shape it sends instead (hookSpecificOutput.permissionDecision) comes from the binary’s own validation strings and has never been exercised against a live tool call. Because sending the wrong shape is ignored silently and the seam fails open, the cost of that inference being wrong is a deny that does not take effect — never a tool call blocked for the wrong reason. Exit code 2 is not used: it is a confirmed deny channel, but it would also fire on a hook crash. - No Windows support. Muse Code has no Windows build, so there is no host to hook.
Deployment notes
Settings are read when Muse Code starts, so an install taken while Muse is running takes effect on the next launch. Restart Muse after installing or removing hooks. Confirm status, then generate one Muse Code event and check the log:- Confirm
~/.config/muse/settings.jsonhasmanaged_hooks_pathpointing at Asymptote’s hooks file, and that the file exists. Either half alone collects nothing. - Check
$XDG_CONFIG_HOME: if it is set, Muse reads$XDG_CONFIG_HOME/muse, not~/.config/muse. - Confirm the hooks file contains commands with
--platform muse. - Confirm hook timeouts are second values, not milliseconds.
- Confirm nothing else has claimed
managed_hooks_pathsince the install. Muse reads only one file. - Restart Muse so new sessions pick up the updated registration.
Related
Supported agent harnesses
Return to the runtime support overview.
Agent harness integration model
See how Asymptote discovers and configures each runtime.

