# Getting started

SmolBox 0.4.0 defaults to **smolvm 1.20.2** on Linux x86_64 and macOS Apple
Silicon. Published SmolBox 0.3.0 and 0.2.1 keep their 1.19.0 default; 0.2.0 keeps
1.17.0. Pin existing workers and checkpoint approvals to their actual version.
Updating SmolBox does not install smolvm. See the
[1.20.2 qualification and upgrade boundaries](runtime-1.20.2-qualification.md).

This walkthrough runs a Python program in a disposable VM, reads its output file,
and waits for cleanup. It uses an in-memory execution store so you can learn the
API without a database. The final section explains what changes in an application
that needs restart recovery.

This walkthrough does not configure the separately
[tested Linux deployment](resource-qualification.md#subsequent-linux-deployment-validation)
or its external host resource controls.

For a complete browser experience, run the
[community workspace example](https://github.com/hfiguera/smolbox/tree/main/examples/community_workspace).
It consumes the published 0.3.0 package and combines persistent machines, terminals,
files, background launches and a mapped service with PostgreSQL recovery. Its
separate saved state walkthrough demonstrates checkpoints, branch isolation and
explicit cleanup on Linux.

## 1. Install SmolBox

In an existing Elixir Mix application, add this entry to `deps/0` in `mix.exs`:

```elixir
{:smolbox, "~> 0.4.0"}
```

Run `mix deps.get` after adding the dependency. A local checkout can instead be
used with `{:smolbox, path: "../smolbox"}`. Version 0.4.0 defaults to smolvm
**1.20.2**. Existing applications should follow
[Upgrading to 0.4.0](upgrading-to-0.4.0.md); the library does not upgrade
the worker. Applications coming from 0.1.2 or earlier also require the coordinated
[record format upgrade](recovery.md#upgrading-to-0-1-3).
Elixir 1.18 and later are accepted by the
package; use one of the tested Elixir/OTP pairs in [Compatibility](compatibility.md).

For a new application, run `mix new smolbox_demo` and `cd smolbox_demo` first.
SmolBox's application starts its ordinary dependencies, but you explicitly start
each managed runtime under supervision.

## 2. Prepare one local worker

Use Linux x86_64 with KVM or macOS Apple Silicon. This walkthrough runs the Elixir
application on the worker host so it can verify the local artifact file. Remote
workers use a different host configuration; see [Managed host integration](host-integration.md).

You need a **dedicated, empty worker**. Select **smolvm 1.20.2
on Linux x86_64 or macOS Apple Silicon**. Existing 1.19.0, 1.17.0 and other supported deployments require an explicit
matching `runtime_version` (or the environment setting below).
Check the [host preparation prerequisites](compatibility.md#host-preparation-prerequisites),
then use an approved native Python
artifact with neutral `/bin/true` startup. Follow the
[reference-runtime preparation instructions](client.md#preparing-the-reference-runtimes)
to create `python.smolmachine`, record its SHA-256, and start the private worker
with the 1 MiB file-transfer cap. The package does not install smolvm or prepare
runtime images for you. Do not attach this ephemeral demo controller to a worker
managed by another runtime or store.

The example uses the measured reference allocation floor: 20 GiB storage,
10 GiB overlay, and 768 MiB VMM overhead per execution. Check available host
capacity and your actual templates before using it. These are allocation and
admission values, not certified host resource quotas. Workers currently have
development qualification; see [Deployment boundaries](security.md).

In your application directory, set these values using the artifact you approved:

```sh
export SMOLBOX_RUNTIME_URL=http://127.0.0.1:19470
export SMOLBOX_RUNTIME_VERSION=1.20.2
export SMOLBOX_PYTHON_ARTIFACT=/absolute/path/to/python.smolmachine
export SMOLBOX_PYTHON_SHA256=replace_with_the_approved_64_character_sha256
export SMOLBOX_DEMO_DIR="$(mktemp -d)"
chmod 700 "$SMOLBOX_DEMO_DIR"
iex -S mix
```

`SMOLBOX_DEMO_DIR` is a new private directory for input/output objects. Keep it
separate from the runtime image and from all guest-accessible directories.
With SmolBox 0.4.0, the walkthrough selects 1.20.2. Explicitly select an older
supported worker version when retaining an existing installation; updating the
Elixir dependency does not install a worker or migrate checkpoint artifacts.

For a local Unix endpoint, set `SMOLBOX_RUNTIME_URL=http://localhost` and
`SMOLBOX_RUNTIME_SOCKET=/absolute/path/to/smolvm.sock` instead. The example's
60-second operation and 55-second receive budgets allow cold preparation;
the execution profile still controls each stage's deadline separately.

## 3. Run the complete example

Save the following block as `smolbox_demo.exs` in the consuming application, then
run `Code.require_file("smolbox_demo.exs")` in that IEx session. This is the success
path: each match deliberately stops the walkthrough if an assumption fails.
If it fails after runtime startup, keep IEx open, inspect the execution, and use
[Troubleshooting](troubleshooting.md). Do not repeatedly rerun the script or clear
the worker inventory to hide a failure.

```elixir
alias SmolBox.{Command, ExecutionSpec, Files, Profile, Worker}
alias SmolBox.ArtifactStore.Directory
alias SmolBox.Runtime.WorkerConfig
alias SmolBox.Store.Memory

artifact_path = System.fetch_env!("SMOLBOX_PYTHON_ARTIFACT")
approved_sha256 = System.fetch_env!("SMOLBOX_PYTHON_SHA256")

actual_sha256 =
  artifact_path
  |> File.stream!(65_536)
  |> Enum.reduce(:crypto.hash_init(:sha256), &:crypto.hash_update(&2, &1))
  |> :crypto.hash_final()
  |> Base.encode16(case: :lower)

true = actual_sha256 == approved_sha256

{platform, architecture} =
  case :os.type() do
    {:unix, :darwin} -> {:macos, "aarch64"}
    {:unix, :linux} -> {:linux, "x86_64"}
  end

worker_options = [
  allow_insecure_loopback: true,
  operation_timeout_ms: 60_000,
  receive_timeout_ms: 55_000
]

worker_options =
  case System.get_env("SMOLBOX_RUNTIME_SOCKET") do
    nil -> worker_options
    socket -> Keyword.put(worker_options, :unix_socket, socket)
  end

{:ok, worker} =
  Worker.new("demo-worker", System.fetch_env!("SMOLBOX_RUNTIME_URL"), worker_options)

{:ok, client} = SmolBox.Client.new(worker)
runtime_version = System.get_env("SMOLBOX_RUNTIME_VERSION", "1.20.2")
{:ok, %{version: ^runtime_version}} = SmolBox.Client.health(client)
:ok = SmolBox.Client.readiness(client)
{:ok, []} = SmolBox.Client.list(client)

{:ok, profile} =
  Profile.new("demo-offline-v1", storage_gb: 20, overlay_gb: 10, host_overhead_mb: 768)

artifact = %{
  "id" => "demo-python-v1",
  "sha256" => approved_sha256,
  "architecture" => architecture,
  "path" => artifact_path
}

{:ok, configured_worker} =
  WorkerConfig.new(
    client: client,
    platform: platform,
    architecture: architecture,
    runtime_version: runtime_version,
    artifacts: [artifact],
    profiles: [profile],
    allocation_floor: %{storage_gb: 20, overlay_gb: 10, host_overhead_mb: 768},
    capacity: %{slots: 1, cpus: 1, memory_mb: 1024, disk_gb: 30}
  )

{:ok, objects} = Directory.new(System.fetch_env!("SMOLBOX_DEMO_DIR"))

{:ok, supervisor} =
  Supervisor.start_link(
    [
      {Memory, name: SmolBoxDemo.Store},
      {SmolBox,
       name: SmolBoxDemo.Runtime,
       namespace: "sbxdemo",
       mode: :ephemeral,
       store: {Memory, SmolBoxDemo.Store},
       artifact_store: {Directory, objects},
       fingerprint_key: :crypto.strong_rand_bytes(32),
       workers: [configured_worker],
       max_active: 1}
    ],
    strategy: :rest_for_one
  )

scope = "demo"
id = "hello-" <> Base.encode16(:crypto.strong_rand_bytes(6), case: :lower)
source_ref = "source-" <> id

source = """
from pathlib import Path
Path('/workspace/result.txt').write_text('42\\n')
print('hello from SmolBox')
"""

:ok = Directory.seed(objects, scope, source_ref, source)

{:ok, command} = Command.new(["python", "/workspace/main.py"], timeout_secs: 10)

{:ok, spec} =
  ExecutionSpec.new(
    scope: scope,
    id: id,
    artifact: Map.drop(artifact, ["path"]),
    profile: profile,
    command: command,
    inputs: [
      %{
        "source" => source_ref,
        "path" => "/workspace/main.py",
        "size" => byte_size(source),
        "sha256" => Files.sha256(source),
        "mode" => "runtime_default"
      }
    ],
    outputs: [
      %{
        "destination" => "answer",
        "path" => "/workspace/result.txt",
        "max_bytes" => 1024
      }
    ]
  )

{:ok, handle} = SmolBox.submit(SmolBoxDemo.Runtime, spec)
{:ok, ^handle} = SmolBox.submit(SmolBoxDemo.Runtime, spec)
{:ok, record} = SmolBox.await(SmolBoxDemo.Runtime, handle, 90_000)
%{state: :completed, result: %{exit_code: 0}, collection: :complete} = record
IO.write(record.result.stdout)
{:ok, "42\n"} = Directory.read_output(objects, handle, "answer", 1024)

# await/3 returns the command/collection outcome before cleanup may finish.
wait_for_cleanup = fn again, deadline ->
  {:ok, current} = SmolBox.fetch(SmolBoxDemo.Runtime, scope, id)

  cond do
    current.cleanup == :complete and current.reservation == nil ->
      current

    System.monotonic_time(:millisecond) >= deadline ->
      raise "Cleanup is still pending. Keep IEx open and inspect the runtime."

    true ->
      Process.sleep(100)
      again.(again, deadline)
  end
end

cleaned =
  wait_for_cleanup.(wait_for_cleanup, System.monotonic_time(:millisecond) + 60_000)

IO.inspect(Map.take(cleaned, [:state, :collection, :cleanup, :reservation]),
  label: "finished"
)

:ok = Supervisor.stop(supervisor)
```

Expected program output is `hello from SmolBox`, followed by a summary containing
`state: :completed`, `collection: :complete`, `cleanup: :complete`, and
`reservation: nil`. The second submission returns the same handle; it does not
create a second execution. The output file is read through the artifact adapter,
not directly from the worker after cleanup.

The demo stops its supervisor only after cleanup and capacity release are
confirmed. Its in-memory execution records are then lost. The private object
directory still contains the demo's input/output objects; remove that specific
directory yourself when you no longer need them.

## 4. Understand the result

`SmolBox.await/3` returning `{:ok, record}` means a stored outcome is available.
It does not mean your program exited zero. Inspect `record.state`,
`record.result.exit_code` when a result exists, `record.collection`, and
`record.cleanup` independently. A nonzero program exit is still an observed result.
An unknown outcome can have `record.result == nil`.

An `:expired` error from `await/3` ends only that wait. The runtime keeps working;
use `SmolBox.fetch/3` to inspect the original identity. `SmolBox.cancel/3` records
cancellation intent, and later observations establish termination and cleanup.
See [Troubleshooting](troubleshooting.md) for concrete result-handling examples.

## 5. Move into your application

- Put `SmolBox.child_spec/1` under your application's supervision tree. Configure
  workers and approved profiles once in trusted host code.
- Replace `SmolBox.Store.Memory` with a durable adapter when executions must
  survive a restart. Keep the store and fingerprint key stable. The randomly
  generated key above is only for this new ephemeral demo.
- Persist an execution ID for each authorized request. Reuse that ID for the
  same submission; use a new ID only for a deliberately authorized new execution.
- Choose an artifact adapter that meets your storage and retention requirements.
  The directory adapter is a small local implementation.

Continue with [Managed host integration](host-integration.md) and
[Persistence and recovery](recovery.md). The repository's
[minimal host](https://github.com/hfiguera/smolbox/tree/v0.1.1/examples/minimal_host)
and [PostgreSQL host](https://github.com/hfiguera/smolbox/tree/v0.1.1/examples/durable_host)
are complete applications; they are source examples, not modules shipped in the
library package.

For JavaScript, prepare and approve the Node artifact from the client guide, stage
a `.js` file, and use `Command.new(["node", "/workspace/main.js"])`. TypeScript
needs a compiler or runner in the approved image; SmolBox does not transpile it.
