Low-level typed operations against an explicitly configured SmolVM worker.
This API does not persist execution identity or authorize cleanup. Callers own machine ownership checks, admission, deadlines, and recovery. No operation is automatically retried. In particular, both exec and file transfers may start a stopped machine upstream; never use them as observational recovery probes.
Streaming captures bounded lossy UTF-8 and optionally delivers synchronous events with backpressure. A crashing callback is detached. A blocking callback is bounded by the overall operation deadline. Neither disconnect nor output overflow implies termination; explicitly stop an owned VM and inspect it.
File transfers preserve bytes. Upload verifies the source digest before any I/O and checks the worker's acknowledgment, but upstream supplies no atomic rename or permission parameter. Managed preparation must verify completion before exec. Path validation is lexical; the API does not provide race-free workspace-only symlink containment inside an untrusted guest. No archive extraction is done.
Summary
Functions
Create an offline machine from an approved prepared artifact on the worker.
Delete an owned machine and validate the worker's deletion acknowledgment.
Download one exact guest workspace file, preserving its bytes.
Execute a command with buffered, byte-preserving stdout and stderr.
Execute through SSE, returning bounded output with encoding: :lossy_utf8.
Read server-reported health and version as {:ok, %SmolBox.Health{}}.
Read a named machine without starting it; absence is a typed :not_found error.
Read up to 1024 machine observations from the configured worker.
Create a client from validated worker configuration without network I/O.
Checks the upstream blocking-pool probe; requires HTTP 200 with an empty body.
Start a machine and return its observation. The caller must establish ownership first.
Stop an owned machine and return its observation.
Upload up to 1 MiB of bytes to an exact guest workspace path.
Types
@type t() :: %SmolBox.Client{transport: module(), worker: SmolBox.Worker.t()}
Functions
@spec create(t(), SmolBox.MachineSpec.t()) :: {:ok, SmolBox.Machine.t()} | {:error, SmolBox.Error.t()}
Create an offline machine from an approved prepared artifact on the worker.
Returns the creation observation after matching name and requested allocations.
Persist intent before this call and creation evidence before further mutations.
A lost or mismatched response can leave creation uncertain; it does not authorize
retry or deletion by name. See SmolBox.MachineSpec.new/3 and the
client lifecycle example.
@spec delete(t(), String.t()) :: :ok | {:error, SmolBox.Error.t()}
Delete an owned machine and validate the worker's deletion acknowledgment.
Returns :ok on a matching acknowledgment. Establish ownership and termination
before calling; verify absence afterward with inspect_machine/2. This low-level
operation does not manage retention or release a managed execution's reservation.
@spec download(t(), String.t(), String.t(), pos_integer()) :: {:ok, binary()} | {:error, SmolBox.Error.t()}
Download one exact guest workspace file, preserving its bytes.
max_bytes is required, from 1 byte through 1 MiB. The smaller of this limit and
the worker response cap applies. Downloads can start a stopped machine, so they
are not passive recovery probes. Lexical path checks do not establish symlink
containment. Managed outputs should be read through the artifact adapter after
collection, rather than reopening the guest after cleanup.
@spec exec(t(), String.t(), SmolBox.Command.t(), keyword()) :: {:ok, SmolBox.Result.t()} | {:error, SmolBox.Error.t()}
Execute a command with buffered, byte-preserving stdout and stderr.
:max_output_bytes defaults to 1 MiB and accepts 1 byte–8 MiB combined output.
The worker's encoded response cap also applies. Bounded UTF-8 stdin is supported
through SmolBox.Command. A nonzero exit returns {:ok, %SmolBox.Result{}}.
Exec can start a stopped VM. A timeout or lost response does not prove the command failed or terminated; never automatically replay an uncertain exec. An output-limit error may retain a known exit code; inspect its evidence.
@spec exec_stream(t(), String.t(), SmolBox.Command.t(), keyword()) :: {:ok, SmolBox.Result.t()} | {:error, SmolBox.Error.t()}
Execute through SSE, returning bounded output with encoding: :lossy_utf8.
Options are :max_output_bytes (default 1 MiB, range 1 byte–8 MiB combined) and
:on_event, an optional one-argument function. It receives {:stdout, text},
{:stderr, text}, and {:exit, integer} synchronously. A crashing callback is
detached; a blocked callback consumes the overall operation deadline. Notifications
are advisory and the encoded response cap includes stream framing.
Streaming stdin is rejected on the pinned worker; use exec/4 or files. A lost
stream is not guest termination. Binary output should use buffered execution.
@spec health(t()) :: {:ok, SmolBox.Health.t()} | {:error, SmolBox.Error.t()}
Read server-reported health and version as {:ok, %SmolBox.Health{}}.
Missing inventory counts remain nil. This observation does not qualify the
worker's artifacts or isolation; use readiness/1 for the separate pool probe.
Managed admission independently requires the pinned version.
@spec inspect_machine(t(), String.t()) :: {:ok, SmolBox.Machine.t()} | {:error, SmolBox.Error.t()}
Read a named machine without starting it; absence is a typed :not_found error.
@spec list(t()) :: {:ok, [SmolBox.Machine.t()]} | {:error, SmolBox.Error.t()}
Read up to 1024 machine observations from the configured worker.
All entries must satisfy the supported offline machine contract; an incompatible entry fails the result. Listing does not establish ownership or authorize cleanup.
@spec new(SmolBox.Worker.t(), keyword()) :: {:ok, t()} | {:error, SmolBox.Error.t()}
Create a client from validated worker configuration without network I/O.
The only option is :transport, a module implementing SmolBox.Transport;
the default is SmolBox.Transport.Req. Client operations return typed
SmolBox.Error values and never automatically retry mutations. For managed
execution identity, observation and cleanup, use SmolBox instead.
@spec readiness(t()) :: :ok | {:error, SmolBox.Error.t()}
Checks the upstream blocking-pool probe; requires HTTP 200 with an empty body.
@spec start(t(), String.t()) :: {:ok, SmolBox.Machine.t()} | {:error, SmolBox.Error.t()}
Start a machine and return its observation. The caller must establish ownership first.
@spec stop(t(), String.t()) :: {:ok, SmolBox.Machine.t()} | {:error, SmolBox.Error.t()}
Stop an owned machine and return its observation.
This does not recover the command's exit code or fence an earlier delayed exec request. The caller must verify the returned state and continue appropriate reconciliation. See Recovery.
@spec upload(t(), String.t(), String.t(), binary(), String.t()) :: :ok | {:error, SmolBox.Error.t()}
Upload up to 1 MiB of bytes to an exact guest workspace path.
sha256 is the lowercase digest from SmolBox.Files.sha256/1. The client verifies
it before sending and checks the worker's path/size acknowledgment. This endpoint
can start a stopped VM; it is a mutation and must not be blindly retried.
Permissions and atomic rename are not caller-configurable through this API.