# How to run smol cloud commands Complete [installation](installation.adoc). Set `SMOL_CLOUD_TOKEN` to your [smol cloud](https://smolmachines.com/) API token before starting the REPL. Keep the token out of source files and logs. ## Connect and select a machine Create client configuration and request the machine list: ```clojure (require '[ol.smolvm.cloud :as cloud]) (def client (cloud/client {:api-key (System/getenv "SMOL_CLOUD_TOKEN")})) @(cloud/invoke client (cloud/list)) ``` Operation builders return request maps without sending requests. `invoke` sends exactly one asynchronous request and returns a `CompletableFuture`. It does not poll, retry or run cleanup. Use `@` to wait for the decoded result. The SDK does not discover credentials. This example reads the environment variable explicitly and supplies `:api-key`. For multi-request tasks, use the separate [cloud workflow helpers](cloud-workflows.adoc). These also return futures. Replace example IDs and paths before use. Examples can create billable resources. Choose the exact `:id` of a ready machine from the list: ```clojure (def machine-id "replace-with-machine-id") @(cloud/invoke client (cloud/exec machine-id ["echo" "hello"])) ``` Check `:exit-code` in the result. Nonzero command exits are results, not exceptions. Output from smol cloud can be truncated. Check `:stdout-truncated?` and `:stderr-truncated?` before using it as complete output. ## Create a temporary machine If you need a new machine, create it from an OCI image. Send the command with `exec`. The smol cloud service starts a stopped machine automatically for execution: ```clojure (let [machine @(cloud/invoke client (cloud/create {:image "alpine:3.21" :name "command-example"})) id (:id machine)] (try @(cloud/invoke client (cloud/exec id ["echo" "hello"])) (finally @(cloud/invoke client (cloud/delete id))))) ``` **⚠️ WARNING**\ `delete` deletes the smol cloud machine and its storage. The example requests deletion even when execution fails. Requests to smol cloud can fail, including cleanup requests. If cleanup fails, list machines and retry deletion with the exact machine ID. To stop a machine without deleting its disk, use `stop` instead of `delete`. ## Handle request failures Dereferencing a failed request throws `ExecutionException`. Read error data from its cause: ```clojure (try @(cloud/invoke client (cloud/info machine-id)) (catch java.util.concurrent.ExecutionException e (let [cause (.getCause e)] {:message (.getMessage cause) :data (ex-data cause)}))) ``` Error data includes `:code` and, when available, `:status` and `:request-id`. For a limited wait, use timed dereference: ```clojure (def result (cloud/invoke client (cloud/exec machine-id ["echo" "hello"]))) (deref result 1000 ::waiting) ``` A wait timeout does not cancel the request or the guest command. Dereference the same future again to get its result. For remote work that needs explicit cancellation, use `job-start` and `job-cancel`. ## Download a file without buffering the full response Request stream mode and close the returned stream after copying it: ```clojure (require '[clojure.java.io :as io]) (let [operation (cloud/file-read machine-id "/tmp/result" {:as :stream})] (with-open [^java.io.InputStream input @(cloud/invoke client operation) output (io/output-stream "result")] (io/copy input output))) ``` This replaces the local file `result`. The guest file must already exist. ## Consume command output as events Use SSE when output should arrive before the command exits. Close the returned reader even if you stop consuming early: ```clojure (with-open [events @(cloud/invoke client (cloud/exec-stream machine-id ["echo" "hello"]))] (loop [] (when-let [event (cloud/stream-read events)] (case (:kind event) :stdout (print (String. ^bytes (:data event) "UTF-8")) :stderr (binding [*out* *err*] (print (String. ^bytes (:data event) "UTF-8"))) :exit (println "Exit:" (:exit-code event)) :error (println "Remote command reported an error")) (recur)))) ``` `stream-read` blocks on the existing response, not a new request. Exit/error is terminal and closes the body. Later reads return `nil`. One reader thread consumes events. Another thread can close it to release a blocked read. Closing is not a remote command-kill guarantee. Output events contain UTF-8 bytes, not binary-exact process output. Frames are limited to 1 MiB, including ignored lines. Malformed exit data, invalid UTF-8, or EOF without a terminal event fails. HTTP timeout covers response headers, not later body reads. There is no reconnect, replay or alternate-route retry. Service error-event text can contain sensitive data. For buffered binary output, request `(cloud/exec machine-id command {:output :b64})` and read the byte fields. For a detached process, use `exec-background` and inspect its `:pid`. Detached execution does not accept stdin or guest timeout and is not a cancellable job. Use jobs when remote cancellation is required. ## Share access, then revoke it Share links and public port links are credentials. Do not print or log them. `endpoint` only describes access. `port-links` changes access: ```clojure (def public-links @(cloud/invoke client (cloud/port-links machine-id))) ;; After the recipient no longer needs access: @(cloud/invoke client (cloud/port-links-revoke machine-id)) ``` The result maps integer guest ports to URLs. Revocation removes all public port links. For machine sharing, use `share` and revoke with `unshare`. Neither operation schedules automatic revocation. ## Lease a child for a task Choose an application lease ID and keep the returned token private: ```clojure (def source-id "replace-with-provisioned-source-id") (def lease-id "replace-with-application-lease-id") (def assignment @(cloud/invoke client (cloud/assign source-id {:lease-id lease-id :task {"command" "inspect input"} :ttl-ms 60000 :heartbeat-ms 10000}))) @(cloud/invoke client (cloud/lease-heartbeat lease-id (:owner-token assignment))) ;; After the task finishes: @(cloud/invoke client (cloud/lease-complete lease-id (:owner-token assignment) {:reason "done" :result {"ok" true}})) ``` This sends one heartbeat, not a heartbeat schedule. Schedule further heartbeats in your application before the lease expires. `lease-info` inspects status. Task/result JSON maps use string keys. Do not log the assignment, task, files, secrets or token. Completion can trigger child cleanup. Verify resource state separately, including after an uncertain completion response. ## Use a versioned cache independently of a machine Caches require a supporting Pro+ plan. Create a cache, then attach it by ID or name when creating a machine: ```clojure (def cache @(cloud/invoke client (cloud/cache-disk-create "example-cache" {:size-gb 1 :mount-path "/cache"}))) (def cached-machine @(cloud/invoke client (cloud/create {:image "alpine:3.21" :cache-disk (:id cache)}))) ;; Start and use the machine, then stop it before publishing: @(cloud/invoke client (cloud/stop (:id cached-machine))) (def version @(cloud/invoke client (cloud/cache-disk-publish (:id cached-machine)))) @(cloud/invoke client (cloud/cache-disk-info (:id cache))) ``` Attachment selects the latest version at the cache’s configured mount path. Publishing appends a version. It does not create/configure the cache resource. Deleting a machine does not delete its cache: ```clojure @(cloud/invoke client (cloud/delete (:id cached-machine))) ;; Only when no other workload needs this cache: @(cloud/invoke client (cloud/cache-disk-delete (:id cache))) ``` **⚠️ WARNING**\ Cache deletion removes **all versions**, not just the published version. The service refuses deletion while a running machine uses the cache. Verify deletion with `cache-disk-info` and expect a not-found response. `artifact-export` also requires a stopped machine and can create durable data. No artifact cleanup endpoint is established. Agree on retention and cleanup before invoking it. Do not treat machine deletion as artifact deletion. ## Validated live scope On 2026-11-07, revision `94708893c6d0a573b765c03a01f0769b46657ce9` completed live checks on JVM and actual Babashka. Each runtime recorded 48 passed, five partial and four blocked statuses across all 57 public cloud functions. Neither run had a final failed status. This is not full live coverage: * Sharing and public-port issuance/revocation completed, but anonymous access and effective revocation were not verified. An earlier HTTP 302 was not followed. * Artifact export covered nonexistent-ID rejection only. Successful export was excluded by the cleanup policy. * Cache creation returned HTTP 402. All four cache operations were blocked. * Raw and automatic checkpoint uploads each used one part and one attempt, with successful marker restoration. Live multi-part concurrency and injected upload failures were not tested. Independent final checks found all 33 machines and six checkpoints from the expanded attempts and probe absent, with zero machines in the account. Two completed synthetic lease-history records were explicitly approved. No cache or exported artifact was allocated. See [cloud workflows](cloud-workflows.adoc) for branching, readiness, jobs and checkpoint upload, and [smol cloud API reference](api/ol-smolvm-cloud.adoc) for all operations and options.