# `Condukt.Sandbox`
[🔗](https://github.com/tuist/condukt/blob/0.16.5/lib/condukt/sandbox.ex#L1)

Filesystem and process-execution capabilities exposed to tools.

A sandbox is a runtime-swappable backend for the operations a tool needs to
reach the outside world: read/write/edit files, run commands, glob, and grep.
Built-in sandboxes:

- `Condukt.Sandbox.Local` runs against the host filesystem and spawns real
  processes via `MuonTrap`.
- `Condukt.Sandbox.Virtual` (in `:condukt_bashkit_nif`) runs inside an
  in-memory virtual filesystem and a Rust-implemented bash interpreter, with
  no host process spawning by default.

## Why a sandbox

Built-in tools (`Condukt.Tools.Read`, `Tools.Write`, `Tools.Edit`,
`Tools.Bash`, `Tools.Glob`, `Tools.Grep`) are sandbox-agnostic: they declare
one tool name and JSON schema to the LLM and route every primitive call
through the active sandbox. This means the same agent definition can run
against the host filesystem in development and against an isolated virtual
filesystem in production by changing one option at `start_link/1`.

## Configuring

Pass `:sandbox` at session start (or as the agent module's `sandbox/0`
callback). Accepted forms:

    # module only — uses defaults (Local resolves :cwd to File.cwd!())
    sandbox: Condukt.Sandbox.Local

    # module + init opts
    sandbox: {Condukt.Sandbox.Local, cwd: "/path/to/project"}

    # already-initialized struct (advanced)
    sandbox: Condukt.Sandbox.new(Condukt.Sandbox.Local, cwd: "/tmp")

When `:sandbox` is omitted, sessions default to
`{Condukt.Sandbox.Local, cwd: <:cwd opt or File.cwd!()>}` so existing
agents continue to behave as they did before sandboxes existed.

## Writing a custom sandbox

Implement the `Condukt.Sandbox` behaviour. `init/1` builds the per-session
state; `shutdown/1` releases it; the rest are I/O primitives. Tools dispatch
through this module's facade (`Condukt.Sandbox.read/2`, `.exec/3`, etc.) so
custom sandboxes work with every built-in tool automatically.

## Tool authoring rule

If your tool reads or writes files, or runs subprocesses, route through the
`Condukt.Sandbox.*` facade rather than calling `File.*`, `System.cmd/3`, or
`MuonTrap.cmd/3` directly. Direct calls bypass the sandbox and break the
ability to swap one in. Tools that touch unrelated systems (HTTP APIs,
databases, in-process state) have nothing to sandbox and are unaffected.

# `edit_file`

```elixir
@callback edit_file(
  state :: term(),
  path :: binary(),
  old_text :: binary(),
  new_text :: binary()
) ::
  {:ok, %{occurrences: non_neg_integer(), content: binary()}} | {:error, term()}
```

Replaces the unique occurrence of `old_text` with `new_text`.
Returns the count of pre-edit occurrences alongside the post-edit content
so callers can decide how to surface ambiguity or no-op edits.

# `exec`

```elixir
@callback exec(state :: term(), command :: binary(), opts :: keyword()) ::
  {:ok, %{output: binary(), exit_code: integer()}} | {:error, :timeout | term()}
```

Runs a shell command. Options:

  * `:cwd` — working directory
  * `:env` — list of `{key, value}` strings to layer onto the base env
  * `:timeout` — milliseconds before the command is killed

# `glob`
*optional* 

```elixir
@callback glob(state :: term(), pattern :: binary(), opts :: keyword()) ::
  {:ok, [binary()]} | {:error, term()}
```

Returns paths matching `pattern`. Options:

  * `:cwd` — base directory; pattern is resolved relative to it
  * `:limit` — maximum number of paths to return

# `grep`
*optional* 

```elixir
@callback grep(state :: term(), pattern :: binary(), opts :: keyword()) ::
  {:ok, [%{path: binary(), line_number: pos_integer(), line: binary()}]}
  | {:error, term()}
```

Searches file contents for `pattern` (regex). Options:

  * `:path` — directory to search (default: cwd)
  * `:glob` — glob filter applied to file paths
  * `:case_sensitive` — defaults to true
  * `:limit` — maximum matches to return

# `init`

```elixir
@callback init(opts :: keyword()) :: {:ok, state :: term()} | {:error, term()}
```

Initializes per-session sandbox state.

# `mount`
*optional* 

```elixir
@callback mount(state :: term(), host_path :: binary(), vfs_path :: binary()) ::
  :ok | {:error, :not_supported | term()}
```

Mounts a host directory into the sandbox at `vfs_path`. Sandboxes that have
no separate VFS (like `Local`) should return `{:error, :not_supported}`.

# `read_file`

```elixir
@callback read_file(state :: term(), path :: binary()) ::
  {:ok, binary()} | {:error, term()}
```

Reads a file's raw bytes.

# `shutdown`

```elixir
@callback shutdown(state :: term()) :: :ok
```

Releases any resources held by the sandbox state.

# `write_file`

```elixir
@callback write_file(state :: term(), path :: binary(), content :: binary()) ::
  :ok | {:error, term()}
```

Writes raw bytes to a file, creating parent directories as needed.

# `edit`

# `exec`

# `glob`

# `grep`

# `mount`

# `new`

Constructs a sandbox handle by initializing `module` with `opts`.

Returns `{:ok, sandbox}` so callers can surface init failures (an in-memory
sandbox that fails to allocate, a virtual sandbox whose NIF refused to
start, etc).

# `read`

# `resolve`

Normalizes a user-supplied `:sandbox` option into a `t()`.

Accepts an already-built struct, a bare module, or a `{module, opts}` tuple.
Returns `{:ok, sandbox}` or `{:error, reason}`.

# `shutdown`

Releases the sandbox state.

# `write`

---

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