# `SuperPi.Env.SandboxRunner`
[🔗](https://github.com/oleksandr/super-pi-beam/blob/v0.1.0/lib/super_pi/env/sandbox_runner.ex#L1)

4-Layer Safe Tool & Process Execution Sandbox (`REQ-SAFE-01`..`REQ-SAFE-03`, `REQ-PBT-05`).

Guarantees that tool execution NEVER crashes the calling `:gen_statem` conversation process or
the BEAM VM:
- **Layer 1 (BEAM Process & Heap Isolation)**: Runs every tool function inside an unlinked
  `Task.Supervisor.async_nolink(SuperPi.ToolTaskSupervisor, ...)` process with a configurable
  `:max_heap_size` flag (`%{size: max_heap_words, kill: true, error_logger: true}`).
  If a tool raises an exception, calls `exit/1`, times out, or exceeds `:max_heap_size`,
  the supervisor catches the `:DOWN` message and returns `{:error, %{type: ..., reason: ...}}`.
- **Layer 2 (Backpressure OS Process Execution + 4 MiB Spill Files)**: Executes external commands
  with streaming output capture and automatically spills outputs exceeding `4 MiB` (`4_194_304` bytes)
  to temporary files while returning a bounded preview.

# `exec_command`

```elixir
@spec exec_command(String.t(), [String.t()], keyword()) ::
  {:ok,
   %{exit_code: integer(), output: String.t(), spilled_path: String.t() | nil}}
  | {:error, map()}
```

Executes an external OS command safely using `Exile` streaming with backpressure and `4 MiB` spill-to-disk
protection (`REQ-SAFE-02`).

# `run_isolated`

```elixir
@spec run_isolated((-&gt; term()), keyword()) :: {:ok, term()} | {:error, map()}
```

Runs `fun.()` inside an unlinked supervised task with `:max_heap_size` enforcement and a strict timeout.
Never raises or crashes the caller.

# `start_owned`

```elixir
@spec start_owned((-&gt; term()) | (pid() -&gt; term())) :: Task.t()
```

Starts `fun` as an unlinked task under `SuperPi.ToolTaskSupervisor` that does not outlive its
caller: a watcher process kills the task when the caller exits. A task that runs an OS command
sends `{:os_pid, pid}` to the watcher (an arity-1 `fun` gets the watcher pid), and the watcher
kills that process tree first. The caller receives `{ref, result}` or `{:DOWN, ref, ...}`, as
with `Task.Supervisor.async_nolink/2`.

Nested tasks chain: cancelling a conversation kills its tool task, which ends the sandbox task
under it, which ends the command and its OS processes.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
