# `Omni.Tools.Repl.Sandbox`
[🔗](https://github.com/aaronrussell/omni_tools/blob/v0.1.0/lib/omni/tools/repl/sandbox.ex#L1)

Executes Elixir code in an isolated peer node.

Each invocation starts a fresh Erlang peer node, evaluates the code, captures
IO output and the raw return value, then stops the peer. Clean slate per
execution — no state carries over between calls.

The host's code paths are injected into the peer, so all compiled modules
(including application dependencies) are available. In dev, `Mix.install/1` can
add additional dependencies since each peer is a fresh VM.

The sandbox executes arbitrary code with full system access. It is best-effort
isolation, not a security boundary. For trusted use cases only: agent-driven
experimentation, scratchpad computation — not adversarial input.

## Options

  * `:timeout` - execution timeout in milliseconds (default: `60_000`)
  * `:max_output` - truncation limit in bytes (default: `50_000`)
  * `:setup` - code evaluated in the peer before the user's code.
    Setup runs before IO capture begins, so its output is not included.
    Accepts a string, quoted AST, or a list of either.

## Return values

    {:ok, %{output: "hello\n", result: :ok}}
    {:error, :timeout, %{output: "partial..."}}
    {:error, {:error, %ArithmeticError{}, stacktrace}, %{output: ""}}

On success, `result` is the raw return value of the last expression (not
inspected). On error, the second element is either `:timeout`, `:noconnection`,
or a `{kind, reason, stacktrace}` triple from the caught exception.

# `result`

```elixir
@type result() ::
  {:ok, %{output: String.t(), result: term()}}
  | {:error, :timeout | :noconnection, %{output: String.t()}}
  | {:error, {atom(), term(), Exception.stacktrace()}, %{output: String.t()}}
```

Result of a sandbox evaluation — success, timeout/disconnect, or exception.

# `ensure_distributed!`

```elixir
@spec ensure_distributed!() :: :ok
```

Enables distributed mode on the host VM if not already active.

Peer nodes require the host to be distributed (`Node.alive?()`). When the
VM was started without `--sname` / `--name`, this function starts EPMD and
enables distribution with a unique short name.

Called automatically by `run/2` on each invocation (idempotent). You may
also call it explicitly at application boot to avoid flipping the VM into
distributed mode during a request — that flip can invalidate any PID-based
state (encoded tokens, cached process references, etc.) that was created
before distribution was enabled.

    # In your Application.start/2:
    Omni.Tools.Repl.Sandbox.ensure_distributed!()

# `run`

```elixir
@spec run(
  String.t(),
  keyword()
) :: result()
```

Evaluates `code` in a fresh peer node and returns the captured output
and raw return value.

---

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