execute() parses a bash-style command, looks up the target session, resolves mounts, runs the executor, applies I/O side effects, and records history through the Observer.
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.
Python
TypeScript
- CLI
execute() calls with different cwd see their own cwd without cross-contamination, even on the same session.
Subshells (...)
Wrapping commands in ( ... ) runs them in an isolated copy of the session: cd, export, and other mutations inside the parens do not leak back. It is the same isolation as the cwd / env overrides above, and the CLI’s stand-in for them (there are no --cwd / --env flags).
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.
Python
TypeScript
- CLI
Three Scopes for State
| Need | API | Bash equivalent |
|---|---|---|
| One isolated command | execute(cmd, cwd=..., env=...) | (cd /data && cmd) |
| Many isolated commands sharing scoped state | session_id=... (Py) / sessionId (TS) | a separate terminal |
| Persistent shell mutations | run without options | cd /data; cmd |
Supported bash syntax
Mirage Bash 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 (Pythonfnmatchsemantics 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,pswork; use the--backgroundflag andmirage jobCLI for long-running work.) - Shell internals:
exec,complete,compgen,ulimit. - Output process substitution:
>(cmd)(the<(cmd)direction works).
exit_code 2 with stderr mirage: unsupported builtin: <name> or mirage: unsupported: process substitution >(...).
Syntax errors
Commands the parser cannot make sense of returnexit_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 withmirage: 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 history view (/.bash_history, which the history builtin and the GNU histfile render from) and the cache mount (/_default, where stateless text-processing commands like wc live).
Python
- CLI
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 owncwd/env/cancel. The clone semantics make this race-free without per-call boilerplate. From the CLI, a subshell per call gives the same isolation.
Python
TypeScript
- CLI