# NAME

marsh, msh - shell for composing agents

# SYNOPSIS

**marsh** [**--shell** **bash**|**zsh**|**marsh**] [**--load** *KIT*[,*KIT*...]|**all**] [**--ephemeral-home**] [**--dev**] [*SHELL_ARGS*...]

**marsh** *COMMAND* [*ARGS*...]

**msh** ...

# DESCRIPTION

**marsh** opens a Linux shell for the current directory on an Apple Silicon
Mac. The shell is a patched Brush, a Bash-compatible shell, running in a
Docker Sandbox VM created with the stock **sbx** command. The project
directory is mounted read/write at its exact Mac path, so **pwd** prints the
same path inside and outside.

Some command names are *registered*. By default these are **claude**,
**codex**, **pi** and **shell**. Each registered command runs as a fresh,
nonroot container from its Kit image, in a warm VM kept for that Kit. The
container is deleted when the command ends.

Registered commands work in pipelines, scripts, **make**, and background jobs
like any other command. A registered command started from inside another one
becomes its child job.

**split** runs commands in parallel on private copies of the project.
**join** prints each branch's output and patch. Registered commands in a split
mount only their fork; use **join --keep** to retain patches for inspection
and application. A shell branch's own code (**-b**) is not confined to its
fork. **fanout** runs branches on shared files, and **collect** prints their
output in declaration order.

Direct jobs can change or delete project files. Jobs using the same guest
home can read one another's settings, history, and saved logins. Keep the
project in Git, and use **split** for agent changes you want to review first. See
https://runmar.sh/docs/security.html.

**msh** is a symbolic link to **marsh** and behaves identically.

With no *COMMAND*, **marsh** starts the shell. Arguments it does not recognize
go to the shell, so **marsh -c** '*command*' and **marsh** *script* work as
they do with Bash. After **-c** or **--**, marsh interprets nothing.

# OPTIONS

**--shell** **bash**|**zsh**|**marsh**
: Run the shell VM's GNU Bash or zsh, with your own startup files, instead of
  Brush (**marsh**, the default). Registered commands and the **marsh** CLI
  work in all three.

**--load** *KIT*[,*KIT*...]|**all**
: Prepare the named Kit VMs before the prompt appears. Otherwise the first call
  to a Kit boots its VM and prints **[starting** *NAME* **worker VM…]**.

**--ephemeral-home**
: Use a blank, private guest home that is not kept after the shell exits. If it
  cannot be released, the slot is retained; see **recover-home**.

**--dev**
: Open a development shell whose **sbx** is a scoped host broker. Its grant
  belongs to the shell, not Kit jobs. For developing marsh itself; requires a
  development-enabled install or daemon.

**-h**, **--help**
: Print a summary of commands and exit.

**-V**, **--version**
: Print the version and exit.

# COMMANDS

**status** [**--json**]
: Show the daemon, its host control directory, per-job limits, shells, and
  Kit workers.

**jobs** [**--all**] [**--tree**] [**--json**]
: List Kit jobs, running first, then newest. **--tree** draws children under
  their parent. In a shell, only that session's jobs; from a host terminal,
  running trees, trees from the last hour, and the five newest. **--all** lists
  every recorded job. **--json** omits the session/recent-jobs filter.
  Inside a job, all forms remain limited to that job's subtree.

**jobs show** *JOB* [**--json**]
: Show one job. *JOB* may be an id prefix.

**run** [**--spawn** *NAME*,...|**--no-spawn**] *NAME* [*ARG*...]
: Run registered command *NAME* as a new job. Inside a job it is a child job.
  **--spawn** and **--no-spawn** narrow what *NAME* may start in turn.

**context**
: Print the text every job is given about marsh (its name, limits, and how to
  start other jobs).

**results** [**--json**]
: List finished jobs, newest first: command, exit status, cleanup, timing.
  **WALL** is the whole request, from the call to verified cleanup. It
  includes any Kit VM boot. **jobs** shows **RUN**, the job's own run in the
  ready VM.

**results show** *CURSOR*|*JOB* [**--json**]
: Show one finished job.

**split** [**-n**] [**-b** *LABEL*=*STRING*]... [**:::** *LABEL* *CMD* [*ARG*...]]...
: Run each branch at once in its own private copy of the current directory and
  wait for all of them. **-b** runs *STRING* with the shell in the shell VM;
  **:::** runs one registered command in a container that sees only its copy.
  **-n** gives branches empty stdin. Prints one handle line, which **join**
  reads on stdin.

**join** [**--json**] [**--keep**] [**--timing**] [**--** *CMD* [*ARG*...]]
: Read a split handle on stdin and print each branch's status, output, and
  patch. Only the split's creator can join it. **--keep** retains the split
  and its patches, even on success; without it, success removes them.
  **--json** prints the manifest instead of the text rendering. **--timing**
  prints phase timings to stderr. With **--** *CMD*, run *CMD* on that output with **SPLIT_ID**,
  **SPLIT_DIR**, **SPLIT_MANIFEST** and **SPLIT_OBJECTS** set; the split is
  removed if *CMD* exits 0, unless **--keep** is set, and kept otherwise.
  *CMD* runs even if a branch failed, and **join** returns its status. Without
  *CMD*, **join** returns the first nonzero branch status.
  *CMD* runs in the caller's environment: on the Mac, in the shell VM, or
  in the job container where **marsh join** was invoked.

**splits** [**--json**] [*ID*]
: List splits. With *ID*, show its state and phase timings, even after joining.

**splits cancel** *ID*
: Cancel a running split.

**splits rm** *ID*
: Remove a kept split and its patches.

**fanout** [**-n**] [**-b** *LABEL*=*STRING*]... [**:::** *LABEL* *CMD* [*ARG*...]]... | **collect** [**--json**] [**--timing**] [**--stderr**]
: Run branches at once on the same files (no copies). Print each branch's
  output in declaration order: its header, its stdout, then a failed branch's
  stderr. **--stderr** prints every branch's stderr. **collect** exits with
  the first nonzero branch status. Limits: 16 branches, 64 MiB of input, 16 MiB
  of combined branch output.

**config shell** [**bash**|**zsh**|**marsh**]
: Set, or with no name print, the default shell for this **MARSH_HOME**.

**kit install** *NAME* **--from** *REPOSITORY*@sha256:*DIGEST*
: Register a published Kit under *NAME* in this scope.

**workers reset** *KIT*[,*KIT*...]|**all**
: Remove idle Kit VMs, including quarantined ones. The next use creates a new
  VM.

**reset** [**--json**]
: Remove every VM this scope owns. The daemon keeps running. Refuses while
  shells or jobs are active. The guest home and results are kept. Exits
  nonzero if any cleanup could not be verified.

**stop** [**--json**]
: Remove every VM this scope owns and stop its daemon. Refuses while shells or
  jobs are active. The guest home and results are kept. Exits nonzero if any
  cleanup could not be verified.

**recover-home** *SLOT* **--discard**
: Discard a retained **--ephemeral-home** slot (0 to 15).

**mcp install** **codex**|**claude**|**sbx**
: Register marsh as an MCP server for this project. **codex** and **claude**
  launch it on demand; **sbx** gives selected sandboxes a shared per-project
  host broker. Run this on the Mac. The server uses a separate managed scope
  root rather than the CLI's default home.

**mcp install-published codex** *NAME*, **mcp remove-published codex** *NAME*
: Register or remove a published pipeline as a tool for host Codex. Only
  **codex** is supported. Run on the Mac from the publishing project with the
  same **MARSH_HOME**. Removing the registration does not revoke the publication.

**mcp serve** [*MCP_OPTIONS*...]
: Serve MCP over stdio from a host process. Without **--scope-root** or
  **--home**, use the CLI's selected home and disable creation of managed scopes.

**mcp export-serve** [*MCP_OPTIONS*...] **--declaration** *PATH*
: Serve only the tool in a publication declaration. Requires **--home**.

**mcp start**, **mcp stop**
: Start or stop the project's host MCP broker. **stop** refuses while a sandbox
  is attached.

**mcp load** *NAME* **--sandbox** *SANDBOX*
: Load an existing publication into a running same-user sandbox. Does not
  replace the publication or invalidate other clients.

**mcp unpublish** *NAME*
: Revoke a publication, including calls from clients that already loaded it.

**acp install-published codex** *NAME*, **acp remove-published codex** *NAME*
: Register or remove a published ACP control tool with host Codex. Only
  **codex** is supported. Run on the Mac from the publishing project with the
  same **MARSH_HOME**.

**help** [*COMMAND*]
: Show general help or help for a host command that has a help section.

# MCP SERVER OPTIONS

**--workspace** *PATH*
: Manage this project directory. Defaults to the current directory.

**--scope-root** *PATH*
: Use a private root for separate managed scopes.

**--home** *PATH*
: Use one **MARSH_HOME**, with managed-scope creation disabled. Cannot be
  combined with **--scope-root**.

**--marsh** *PATH*, **--sbx** *PATH*
: Select the installed marsh and stock Docker Sandboxes executables.

**--allow-full-sbx-control**
: Enable fixed repository qualification gates on the Mac. These execute the
  project's Makefile; use only with a trusted checkout. Does not enable a
  generic host-command tool.

# SHELL COMMANDS

Inside the shell, these commands are also available:

**ps --marsh** [**--verbose**|**--json**], **top --marsh** [**--verbose**] [**--once**]
: Show this project's shell, Kit jobs, and ACP sessions. **top --marsh** cannot show
  CPU or memory.

**acp reserve** *AGENT*
: Reserve a session ID for this shell and print it. Start the session within
  15 seconds or reserve again.

**acp run** [**--reservation** *ID*] *AGENT* **&**
: Start an agent session as a background Kit job. Print its session and job
  IDs to stderr when ready.

**acp list** [**--mine**] [**--wait** [*ID*]] [**--json**]
: List this project's sessions. **--mine** selects those this shell started.
  **--wait** waits at most four minutes for readiness; without an ID it
  requires exactly one live session. Use a reserved ID to wait for a new one.

**acp ask** *ID* *TEXT*
: Send a prompt and print the answer. A sole **-** reads UTF-8 stdin.
  Text and its encoded control messages must each fit 1 MiB. After an
  uncertain failure, inspect **acp status** before retrying through
  **acp prompt --key** with the reported key and identical text.

**acp prompt** [**--key** *UUID*] *ID* *TEXT*
: Submit a prompt without waiting for the answer; **-** reads stdin as for
  **ask**. Use **status** to read updates. Reuse a key only with identical text.

**acp status** *ID* [*CURSOR*] [**--json**]
: Show state, agent updates, and pending permissions. Pass the JSON reply's
  **next_cursor** back unchanged to read only newer updates.

**acp permissions** *ID* [**--json**]
: List requests awaiting one-time approval and the options for answering them.

**acp respond** *ID* *REQUEST* *OPTION*
: Answer a pending permission request with one of its offered options.

**acp cancel** *ID*
: Cancel the current turn without stopping the session.

**acp attach** *ID*, **acp release** *ID*
: Take control of an unowned session or release this shell's control.

**acp stop** *ID*
: Stop the session and its Kit job.

**acp publish** *ID* **--name** *NAME* [**--kit** *KIT*|**--sandbox** *SANDBOX*]
: Publish an idle session as an MCP control tool. Callers can prompt, cancel,
  and answer one-time permissions. With a target, load it there immediately;
  start a new client agent session to discover it.

**acp unpublish** *NAME*
: Revoke the control tool. Unpublish before steering the agent from its shell.

ACP usage errors exit 2. Other failures print **acp:** *MESSAGE* and exit 125.
See https://runmar.sh/docs/acp.html.

**mcp publish** *NAME* [**--description** *TEXT*] [**--kit** *KIT*|**--sandbox** *SANDBOX*] **--** '*PIPELINE*'
: Publish a fixed pipeline as an MCP tool. With **--kit** or **--sandbox** it is
  loaded there now. With neither, every agent Kit VM created from now on loads
  it before its first job. Kit VMs already running are not changed; use
  **mcp load** *NAME* **--kit** *KIT* to load into one now.

**mcp load** *NAME* **--kit** *KIT*|**--sandbox** *SANDBOX*
: Load an existing publication into the named Kit VM or running sandbox.

**mcp unpublish** *NAME*
: Revoke the tool everywhere it was loaded.

**split {** *label*: *commands* ... **} | join** [**--keep**] [| *CMD*], **fanout {** ... **} | collect**
: Brush forms of **split** and **fanout**.

# ENVIRONMENT

**MARSH_HOME**
: Absolute path of the scope root. The guest home is *$MARSH_HOME/home*.
  Default *~/.marsh*.

**MARSH_SHELL**
: The shell to run (**bash**, **zsh**, or **marsh**). **--shell** overrides it.

**MARSH_SBX**
: Path to the stock **sbx** executable. Default: **sbx** on **PATH**. If
  there is none, install Docker Sandboxes with
  **brew install docker/tap/sbx**, then run **sbx login**.

**MARSH_SPAWN**
: Comma-separated registered names that jobs started from this shell may
  start, or **none**. A job cannot widen the list.

**MARSH_CONTROL_HOME**
: Host-only state root: scope control directories and MCP/ACP publications
  live under it. Default: control directories in
  *~/Library/Application Support/marsh/control*, publications in
  *~/Library/Application Support/marsh*.

**MARSH_JOB_CPU_MILLIS**, **MARSH_JOB_MEMORY_BYTES**, **MARSH_JOB_PIDS**, **MARSH_JOB_WRITABLE_BYTES**, **MARSH_JOB_OUTPUT_BYTES**, **MARSH_JOB_WALL_SECONDS**, **MARSH_TREE_KIT_VMS**
: Per-job limits, read when the daemon starts.

**MARSH_PLACE**
: Placement for registered commands. Only **local** is accepted.

# FILES

*$MARSH_HOME/home* (default *~/.marsh/home*)
: Backing store for the guest home.

*$MARSH_HOME/config.json* (default *~/.marsh/config.json*)
: The default shell set by **marsh config shell**.

*$MARSH_HOME/sbxenv.yaml*, *$MARSH_HOME/sbxenvs/NAME.yaml*
: Optional shared and per-command MCP servers and extra directories. Read at
  daemon startup; changes require **marsh stop** and a new launch.
  See https://runmar.sh/docs/configuration.html#servers-and-extra-directories.

*~/Library/Application Support/marsh/control/HASH/*
: Host-only control directory for a scope: **commands.json**,
  **agents.json**, the results journal. **marsh status** prints its path.

*PREFIX/libexec/marsh/*
: Linux guest binaries, the packaged command registry, and the shell image
  reference.

*.marsh/split/ID/out/*
: Results of a split, inside the project.

# EXIT STATUS

As for Bash, with these additions:

0-255
: Registered commands return the container's exit status.

125
: A job start was refused. The reason is on stderr. **acp** failures also exit
  125.

126
: A registered-name link was refused because **MARSH_SPAWN** excludes the
  name. **marsh run** returns 125 for the same refusal.

130
: The job was interrupted.

# EXAMPLES

Start a shell in a project:

    cd ~/src/project && marsh

Ask two agents for independent fixes in private copies and look at both:

    marsh split -n ::: a claude -p 'fix the flaky test' \
                   ::: b pi -p 'fix the flaky test' | marsh join --keep

**join** prints, for each branch, a header, its output, and a line giving the
files changed and the path of its **diff.patch**. **--keep** retains the patches
for inspection and **git apply**; a successful bare **join** removes them.
After applying a patch, remove the retained split with **marsh splits rm** *ID*.
Sample output: https://runmar.sh/docs/split.html.

Talk to one agent across turns, inside the shell:

    id=$(acp reserve pi-session)
    acp run --reservation "$id" pi-session &
    acp list --mine --wait "$id"
    acp ask "$id" 'What project files can you see? Do not change files.'
    acp ask "$id" 'Which of those describe how to run the tests? Do not change files.'
    acp stop "$id"

Run tests and lint at once:

    marsh fanout -n -b test='make test' -b lint='make lint' | marsh collect

Publish a pipeline in the shell, then give it to Codex on the Mac:

    mcp publish lint -- 'make lint 2>&1'

From a Mac terminal in the same project and **MARSH_HOME**:

    marsh mcp install-published codex lint

# SEE ALSO

**sbx**(1), **bash**(1)

Documentation: https://runmar.sh/docs/ (start with
https://runmar.sh/docs/quickstart.html; limits and trust in
https://runmar.sh/docs/security.html)

# HISTORY

marsh began life as the mark shell.

# BUGS

Report bugs at https://github.com/mcavage/marsh/issues.
