> ## Documentation Index
> Fetch the complete documentation index at: https://struktoai-fix-databricks-volume-token-provider.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Shell

> The `execute()` API, per-call `cwd`/`env` overrides, and mid-flight cancellation.

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()`

<Tabs>
  <Tab title="Python" icon="https://mintcdn.com/struktoai-fix-databricks-volume-token-provider/L29HQXdXZnKHBlaU/images/python-logo.svg?fit=max&auto=format&n=L29HQXdXZnKHBlaU&q=85&s=dbb1acf31edb69679757b0a0f72a0e7c" width="110" height="110" data-path="images/python-logo.svg">
    ```python theme={null}
    async def execute(
        self,
        command: str,
        session_id: str = DEFAULT_SESSION_ID,
        stdin: AsyncIterator[bytes] | bytes | None = None,
        provision: bool = False,
        agent_id: str = DEFAULT_AGENT_ID,
        native: bool | None = None,
        cwd: str | None = None,
        env: dict[str, str] | None = None,
        cancel: asyncio.Event | None = None,
    ) -> IOResult:
        ...
    ```
  </Tab>

  <Tab title="TypeScript" icon="https://mintcdn.com/struktoai-fix-databricks-volume-token-provider/L29HQXdXZnKHBlaU/images/typescript-logo.svg?fit=max&auto=format&n=L29HQXdXZnKHBlaU&q=85&s=6c79cbd74bb1e10eec604f935fc674ee" width="512" height="512" data-path="images/typescript-logo.svg">
    ```typescript theme={null}
    interface ExecuteOptions {
      stdin?: ByteSource | null
      provision?: boolean
      sessionId?: string
      agentId?: string
      native?: boolean
      signal?: AbortSignal
      noHistory?: boolean
      cwd?: string
      env?: Record<string, string>
    }

    async execute(command: string, options?: ExecuteOptions): Promise<ExecuteResult>
    ```
  </Tab>

  <Tab title="CLI" icon="terminal">
    ```bash theme={null}
    mirage execute --workspace demo --command "ls /data"
    mirage execute --workspace demo --session agent-1 --command "pwd"
    ```

    Per-call `cwd` / `env` / `cancel` are SDK-only as named flags. From the CLI, use bash subshell syntax inside the command string for per-call scoping (see below) and `mirage job cancel` to stop background work.
  </Tab>
</Tabs>

## 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.

<Tabs>
  <Tab title="Python​" icon="https://mintcdn.com/struktoai-fix-databricks-volume-token-provider/L29HQXdXZnKHBlaU/images/python-logo.svg?fit=max&auto=format&n=L29HQXdXZnKHBlaU&q=85&s=dbb1acf31edb69679757b0a0f72a0e7c" width="110" height="110" data-path="images/python-logo.svg">
    ```python theme={null}
    # Persistent mutation (no options): like `cd /data; cmd`
    await ws.execute("cd /data")
    await ws.execute("ls")  # sees /data

    # One-shot subshell (with cwd): like `(cd /data && cmd)`
    await ws.execute("ls", cwd="/data")
    # ws.cwd is unchanged; mutations inside don't leak
    ```
  </Tab>

  <Tab title="TypeScript​" icon="https://mintcdn.com/struktoai-fix-databricks-volume-token-provider/L29HQXdXZnKHBlaU/images/typescript-logo.svg?fit=max&auto=format&n=L29HQXdXZnKHBlaU&q=85&s=6c79cbd74bb1e10eec604f935fc674ee" width="512" height="512" data-path="images/typescript-logo.svg">
    ```typescript theme={null}
    // Persistent mutation
    await ws.execute("cd /data")
    await ws.execute("ls")  // sees /data

    // One-shot subshell
    await ws.execute("ls", { cwd: "/data" })
    // ws.cwd is unchanged; mutations inside don't leak
    ```
  </Tab>

  <Tab title="CLI​" icon="terminal">
    ```bash theme={null}
    # Use a real bash subshell inside the command string
    mirage execute -w demo -c "(cd /data && ls)"
    mirage execute -w demo -c "(export FOO=bar; printenv FOO)"
    ```

    The CLI doesn't have `--cwd` / `--env` flags, but bash subshell syntax `(cd ... && cmd)` and `(export FOO=bar; cmd)` give the same per-call isolation. Mutations inside the parens don't leak.
  </Tab>
</Tabs>

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.

<Tabs>
  <Tab title="Python​​" icon="https://mintcdn.com/struktoai-fix-databricks-volume-token-provider/L29HQXdXZnKHBlaU/images/python-logo.svg?fit=max&auto=format&n=L29HQXdXZnKHBlaU&q=85&s=dbb1acf31edb69679757b0a0f72a0e7c" width="110" height="110" data-path="images/python-logo.svg">
    ```python theme={null}
    import asyncio
    from mirage.workspace.abort import MirageAbortError

    cancel = asyncio.Event()

    async def trigger():
        await asyncio.sleep(0.1)
        cancel.set()

    asyncio.create_task(trigger())
    try:
        await ws.execute("sleep 5", cancel=cancel)
    except MirageAbortError:
        print("aborted")
    ```
  </Tab>

  <Tab title="TypeScript​​" icon="https://mintcdn.com/struktoai-fix-databricks-volume-token-provider/L29HQXdXZnKHBlaU/images/typescript-logo.svg?fit=max&auto=format&n=L29HQXdXZnKHBlaU&q=85&s=6c79cbd74bb1e10eec604f935fc674ee" width="512" height="512" data-path="images/typescript-logo.svg">
    ```typescript theme={null}
    try {
      await ws.execute("sleep 5", { signal: AbortSignal.timeout(100) })
    } catch (e) {
      if (e instanceof DOMException && e.name === "AbortError") {
        console.log("aborted")
      }
    }
    ```
  </Tab>

  <Tab title="CLI​​" icon="terminal">
    ```bash theme={null}
    # Background the job, then cancel it
    JOB_ID=$(mirage execute -w demo -c "sleep 60" --bg)
    mirage job cancel "$JOB_ID"
    ```

    Per-call timeout is not a CLI flag yet. Use `--bg` to get a job id and `mirage job cancel` to terminate.
  </Tab>
</Tabs>

## 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

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).

<Tabs>
  <Tab title="Python" icon="https://mintcdn.com/struktoai-fix-databricks-volume-token-provider/L29HQXdXZnKHBlaU/images/python-logo.svg?fit=max&auto=format&n=L29HQXdXZnKHBlaU&q=85&s=dbb1acf31edb69679757b0a0f72a0e7c" width="110" height="110" data-path="images/python-logo.svg">
    ```python theme={null}
    ws = Workspace({
        "/s3": s3,
        "/slack": slack,
        "/linear": linear,
    })

    ws.create_session("slack-agent", allowed_mounts={"/slack"})
    ws.create_session("data-agent", allowed_mounts={"/s3"})

    await ws.execute("ls /slack", session_id="slack-agent")  # ok
    await ws.execute("cat /linear/issues/SEC-42",
                     session_id="slack-agent")
    # exit_code=1, stderr=b"session 'slack-agent' not allowed to "
    #                     b"access mount '/linear'\n"
    ```
  </Tab>

  <Tab title="CLI" icon="terminal">
    ```bash theme={null}
    # Repeat --mount (or -m) per allowed prefix
    mirage session create demo --id slack-agent --mount /slack
    mirage session create demo --id data-agent  -m /s3 -m /github

    mirage execute -w demo -s slack-agent -c "cat /linear/issues/SEC-42"
    # mirage: session 'slack-agent' not allowed to access mount '/linear'
    ```
  </Tab>
</Tabs>

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.

<Tabs>
  <Tab title="Python​​​" icon="https://mintcdn.com/struktoai-fix-databricks-volume-token-provider/L29HQXdXZnKHBlaU/images/python-logo.svg?fit=max&auto=format&n=L29HQXdXZnKHBlaU&q=85&s=dbb1acf31edb69679757b0a0f72a0e7c" width="110" height="110" data-path="images/python-logo.svg">
    ```python theme={null}
    async def tool_call(cmd: str, cwd: str, env: dict[str, str], timeout: float):
        cancel = asyncio.Event()
        asyncio.get_event_loop().call_later(timeout, cancel.set)
        return await ws.execute(cmd, cwd=cwd, env=env, cancel=cancel)

    results = await asyncio.gather(
        tool_call("ls", "/data", {"DEBUG": "1"}, 5.0),
        tool_call("grep foo *.log", "/logs", {"DEBUG": "1"}, 5.0),
    )
    ```
  </Tab>

  <Tab title="TypeScript​​​" icon="https://mintcdn.com/struktoai-fix-databricks-volume-token-provider/L29HQXdXZnKHBlaU/images/typescript-logo.svg?fit=max&auto=format&n=L29HQXdXZnKHBlaU&q=85&s=6c79cbd74bb1e10eec604f935fc674ee" width="512" height="512" data-path="images/typescript-logo.svg">
    ```typescript theme={null}
    async function toolCall(
      cmd: string,
      cwd: string,
      env: Record<string, string>,
      timeoutMs: number,
    ) {
      return ws.execute(cmd, { cwd, env, signal: AbortSignal.timeout(timeoutMs) })
    }

    const results = await Promise.all([
      toolCall("ls", "/data", { DEBUG: "1" }, 5000),
      toolCall("grep foo *.log", "/logs", { DEBUG: "1" }, 5000),
    ])
    ```
  </Tab>
</Tabs>
