Skip to main content
The shell is how agents act on the workspace. execute() parses a bash-style command, looks up the target session, resolves mounts, runs the executor, applies I/O side effects, and records history.

execute()

Per-call overrides: cwd, env

Providing cwd or env runs the command in an ephemeral session clone, like a bash subshell (cd /data && cmd). Mutations like cd or export inside the call do NOT persist back to the workspace’s session. To change persistent state, run the command without these options.
This makes per-call overrides safe under concurrent calls on the same session. Two parallel execute() calls with different cwd see their own cwd without cross-contamination, even on the same session.

Mid-flight cancellation: cancel / signal

Both bindings support cooperative cancellation observed at recursion boundaries (LIST, PIPELINE, FOR/WHILE/UNTIL iterations, COMMAND, subshells, command substitution) and inside sleep. On cancel, the call raises an abort error.

Three Scopes for State

Supported bash syntax

The shell is a tree-sitter-bash parser plus a custom executor. It implements the constructs LLMs reach for most often. What is not supported returns a clear, parseable error so an agent can self-correct on its next turn.

Supported

  • Operators: pipes |, |&; lists &&, ||, ;; background &.
  • Redirects: >, >>, <, 2>, 2>&1, &>, &>>, heredoc <<, herestring <<<.
  • Substitutions: command substitution `cmd` and $(cmd); arithmetic $((expr)); parameter expansion ${VAR}, ${VAR:-default}, ${VAR%suffix}, etc.; input-direction process substitution <(cmd).
  • Control flow: if/elif/else/fi, for, while, until, case, select, function name() {}, break, continue, return.
  • Grouping: subshells (cmd), compound { cmd; }, negation ! cmd.
  • Builtins: cd, pwd, echo, printf, printenv, read, source, ., eval, export, unset, local, set, shift, trap (no-op), test, [, [[, true, false, sleep, xargs, timeout, bash, sh, python, python3.
  • Globs: *, ?, [...] classes and [!...] negation (Python fnmatch semantics in both implementations), resolved by the shell or pushed down to the resource.
  • Comments: #.

Unsupported (returns clear error)

  • Job control: bg, disown. (fg, jobs, wait, kill, ps work; use the --background flag and mirage job CLI for long-running work.)
  • Shell internals: exec, complete, compgen, ulimit.
  • Output process substitution: >(cmd) (the <(cmd) direction works).
Each returns exit_code 2 with stderr mirage: unsupported builtin: <name> or mirage: unsupported: process substitution >(...).

Syntax errors

Commands the parser cannot make sense of return exit_code 2 with stderr mirage: syntax error near '<token>'. Earlier versions silently ran whatever fragment did parse; that no longer happens.

What --background is and isn’t

The daemon’s --background flag detaches a job and returns a job id. It is not the same as the bash & operator, which the shell does support inline (sleep 30 &). Use & for in-shell job parallelism, --background (or mirage job) for long-lived work that should outlive the request.

Per-session mount capability

A session can be created with an explicit allowlist of mount prefixes. Any command whose path resolves to a mount outside that list is rejected with mirage: session 'agent' not allowed to access mount '/X' and exit code 1. Default sessions (no allowlist) keep their current unrestricted behavior, so existing code is unaffected. This is a soft boundary, enforced inside the daemon process, not an OS or process-level isolation. Use it to shrink the blast radius of prompt-injection in multi-agent workspaces: a Slack-only agent cannot pivot to read /linear, /github, or any other mount it was not given. The check fires for every code path that reaches a mount: shell commands (cat, ls, …), redirects (>, <), cross-mount cp/mv, wget -O, curl -o, command substitution $(...), subshells (...), pipes, &&/|| chains, background jobs, and the programmatic ws.ops.read/write/... API. Two infrastructure prefixes are always allowed regardless of the allowlist: the observer prefix (/.sessions, where command history is recorded) and the cache mount (/_default, where stateless text-processing commands like wc live).
The allowlist is a property of the session, so it covers every command issued under that session_id, including subshells, pipelines, and recursive bash -c '...'. It does not change MountMode: a write to a mount in the allowlist is still rejected if the mount is READ. The two checks compose.

Agent Pattern

Agent harnesses commonly fan out tool calls in parallel, each with its own cwd/env/cancel. The clone semantics make this race-free without per-call boilerplate.