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.Localruns against the host filesystem and spawns real processes viaMuonTrap.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.
Summary
Callbacks
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.
Runs a shell command. Options
Returns paths matching pattern. Options
Searches file contents for pattern (regex). Options
Initializes per-session sandbox state.
Mounts a host directory into the sandbox at vfs_path. Sandboxes that have
no separate VFS (like Local) should return {:error, :not_supported}.
Reads a file's raw bytes.
Releases any resources held by the sandbox state.
Writes raw bytes to a file, creating parent directories as needed.
Functions
Constructs a sandbox handle by initializing module with opts.
Normalizes a user-supplied :sandbox option into a t().
Releases the sandbox state.
Callbacks
@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.
@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
@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
@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
Initializes per-session sandbox state.
@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}.
Reads a file's raw bytes.
@callback shutdown(state :: term()) :: :ok
Releases any resources held by the sandbox state.
@callback write_file(state :: term(), path :: binary(), content :: binary()) :: :ok | {:error, term()}
Writes raw bytes to a file, creating parent directories as needed.
Functions
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).
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}.
Releases the sandbox state.