Host operations
This page is for whoever runs the host process: the client that spawns
muse serve, or a supervisor that keeps it running. It collects what that job
needs in one place — what to pass the host, how to stop it, how to watch it,
where it keeps its files, and the security model it runs under. The SDK spawn
helpers already follow these rules; read on when you manage the process
yourself or need to diagnose one.
One host, one client
Section titled “One host, one client”muse serve is a Muse Session Protocol host over stdio. The process that
spawns it owns its stdin and stdout and is its only connection: a host accepts
exactly one client.
A host can load many sessions, but each session is loaded by one host at a
time. The host holds a writer lease on every session it loads — a
.session.lock file next to the session’s log, held with an operating-system
file lock — so two hosts never write one session. Another host that needs the
session is refused with
sessionInUse, and the
lease frees itself when its holder exits, crashes included.
Starting the host
Section titled “Starting the host”Command-line options
Section titled “Command-line options”Options are fixed for the host’s lifetime, so a client that needs two
different settings runs two hosts. Run muse serve --help for the exact
options your installed version accepts. They fall into four groups:
- Durability.
--no-session-logmakes the host ephemeral: it writes nothing to disk, and its sessions end with the process. The handshake reports the choice assessionDurabilityon theinitializeresult; see durability profiles. - Sandbox posture. The sandbox is on by default.
--disable-sandbox,--sandbox-network <MODE>,--disable-write,--disable-shell, and--trust-workspacechange what the agent may do on this host. The posture and the durability choice apply to every session the host loads, and no protocol method can change them; the security model below says what each boundary covers. - Provider and model.
--providerand--modelchoose the startup provider and model for this process only. They never rewrite saved settings. - Configuration overrides.
-c <KEY=VALUE>(long form--config) overrides one configuration value for every session the host loads. Keys underpermissionsare refused.
There is no approval flag. Approval mode is chosen per session over the wire —
the mode field on
session/start and
session/setApprovalMode
— from the modes the user’s configuration defines; see
approvals.
If the host does not accept an argument, it exits 2 with a usage line on
stderr, before anything is constructed.
Environment
Section titled “Environment”The host runs with the environment it was spawned with. Both SDKs treat an
environment you pass as the host’s whole environment, PATH included:
extend your own environment rather than passing only the variables you add,
or pass none to inherit yours. The option is env on MuseClientSpawnOptions
in both the TypeScript and the
Python SDK references.
HOME, XDG_CONFIG_HOME, and XDG_DATA_HOME decide where the host keeps its
files; the table below shows how.
MUSE_EXPERIMENTAL_SDK_ENABLED is an off-switch, so leave it unset: set to
off, it makes every muse serve invocation exit 5 before reading its
arguments.
Working directory
Section titled “Working directory”Name each session’s working directory explicitly: pass an absolute
workspaceRoot on
session/start, or
workspaceRoots, whose first entry plays the same role. That directory is
where the session works. It is recorded with the session, reported as
workspaceRoot on the session object,
and restored when the session is resumed. Do not rely on the directory you
spawned the host in to choose it.
A workspace root selects where a session works, never what the host may do there: the sandbox posture is still the one fixed at spawn.
The agent reads and writes the files on disk directly. An editor client with unsaved buffers should tell the user that the agent sees the saved version of each file.
Stopping the host
Section titled “Stopping the host”Stop a host by closing its stdin. The host then cancels any active run,
recording the turn as cancelled; finishes writing what was in flight; writes
a session-end record for every session it has loaded; releases its leases;
and exits 0. The whole sequence is bounded by a drain window, 30 seconds by
default. Past it, the rest of the shutdown has crash semantics.
SIGTERM and SIGINT start the same orderly wind-down, so a service manager’s
stop or a terminal’s Ctrl-C does not crash the host. When a signal starts the
wind-down, the exit code names it: 143 for SIGTERM, 130 for SIGINT. Those
codes have no row of their own in the
exit-code list, so a client that
classifies by exit code alone — the SDKs included — reads them as a crash,
even though the sessions were closed in order. Close stdin when you hold the
pipe.
A hard kill — SIGKILL, or the out-of-memory killer — ends the process with no session-end record. What the host already wrote stays: the next host that loads the session reconciles what was in flight.
The SDKs do all of this for you. close() closes stdin, waits out the drain
window, then sends SIGTERM, and SIGKILL two seconds later if the host is still
running. The wait is shutdownTimeoutMs in TypeScript and
shutdown_timeout_ms in Python, 30 seconds by default.
Watching a running host
Section titled “Watching a running host”- Capture stderr, and never parse it. The host writes human-readable diagnostics there, and the format is not a contract. Keep it where a person can read it; the SDKs attach a bounded tail of it to every exit classification except a clean shutdown. See exit classification.
- Branch on the exit classification, not on raw codes. Classify every way the host can exit shows the whole switch.
- Restart only when a restart can help. Exits
2,3, and5repeat until something outside the host changes: a client argument, the user’s configuration, or the build. Exit4clears when the process that holds the session exits. - After a durable host dies, respawn and resume. Its sessions survive; an ephemeral host’s do not. See survive the host dying under you.
Where the host keeps its files
Section titled “Where the host keeps its files”| What | Default location | Moved by |
|---|---|---|
| Settings and trust decisions | ~/.config/muse/ |
XDG_CONFIG_HOME |
| The data directory: sessions, memory, snapshots, diagnostic logs | ~/.local/share/muse/ |
XDG_DATA_HOME |
HOME anchors both defaults. The host reports the data directory it uses as
museHome in its
initialize result: read it
there rather than assuming the default.
Sessions
Section titled “Sessions”Each session is a directory under the data directory’s sessions folder,
partitioned by date:
<data directory>/sessions/<YYYY>/<MM>/<DD>/<session id>/session.jsonl<data directory>/sessions/<YYYY>/<MM>/<DD>/<session id>/.session.locksession.jsonl is the session: everything a client can learn about it is read
back from this log. .session.lock is the writer lease. Every
session object reports its log’s exact
location as path; on an ephemeral host that is the empty string, because no
log exists.
Retention
Section titled “Retention”No protocol method deletes a session. Two housekeeping rules remove sessions on their own:
- Expiry, when it is turned on. With
session_retention_daysset to a positive number of days, in the user’s settings or in managed settings, a session that has not been opened or used for that long — no turn and no resume — is deleted, folder and all. Without the setting, expiry is off and sessions are kept for good. Expiry never deletes a loaded session, and it runs on Unix hosts only for now. - Empty sessions. A session that holds nothing but its own start records is reclaimed during a later host’s startup housekeeping, once no host has it loaded; see survive the host dying under you.
Within a kept session, older history can be pruned behind compaction boundaries, and a read anchored in the pruned range gets a typed error that says where to start again; see resume. An ephemeral host writes no session files at all.
Diagnostic logs
Section titled “Diagnostic logs”Besides stderr, the host keeps a private diagnostic log under
<data directory>/local-tracing/: one file per process, readable only by your
user, rotated as it grows. Each process deletes diagnostic files older than
eight days when it starts. These files are for diagnosis, not a format to
parse.
Security model
Section titled “Security model”The spawn boundary. The host has no authentication, by design. Only the
process that spawned it — or one that process handed the pipes to — can talk
to it, and that party already holds your user’s authority: it could run the
agent binary itself, edit your files, and run commands as you. So the host
does not check it again. The clientInfo a client sends in initialize is
for diagnostics, never for authorization.
Approvals bound the agent. What the agent may do without asking is set by
the session’s approval mode, which a client selects from the modes the user’s
configuration defines and can never author. A client may select even
allowAll, which is safe only because of the spawn boundary. See
approvals.
The sandbox bounds the agent’s commands. The sandbox posture is fixed when
the host is spawned. The host checks once, at startup, whether it can enforce
the sandbox, and reports the answer as
sandboxAvailability in its
initialize result, with a
sandboxUnavailableReason
when it cannot. If the sandbox is on but cannot be enforced, the agent’s shell
commands fail with an environment error rather than run unsandboxed; running
without the sandbox is the explicit --disable-sandbox choice.
Workspace content. --trust-workspace makes the host load each session
workspace’s own skills and rules.
A shell as the user. A client granted the
userShell capability can run a
command with
session/userShell. That
command runs with the user’s own authority, outside the approval flow: the
client asked, and it already holds that authority.